Skip to content

Commit cb29144

Browse files
Merge pull request #736 from cypherstack/rpc
Fix rpc timeout issue and improved logging
2 parents 3c8e220 + fcf9719 commit cb29144

File tree

4 files changed

+83
-55
lines changed

4 files changed

+83
-55
lines changed

lib/electrumx_rpc/rpc.dart

Lines changed: 66 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -80,18 +80,32 @@ class JsonRPC {
8080
void _sendNextAvailableRequest() {
8181
_requestQueue.nextIncompleteReq.then((req) {
8282
if (req != null) {
83-
// \r\n required by electrumx server
84-
if (_socket != null) {
83+
if (!Prefs.instance.useTor) {
84+
if (_socket == null) {
85+
Logging.instance.log(
86+
"JsonRPC _sendNextAvailableRequest attempted with"
87+
" _socket=null on $host:$port",
88+
level: LogLevel.Error,
89+
);
90+
}
91+
// \r\n required by electrumx server
8592
_socket!.write('${req.jsonRequest}\r\n');
86-
}
87-
if (_socksSocket != null) {
88-
_socksSocket!.write('${req.jsonRequest}\r\n');
93+
} else {
94+
if (_socksSocket == null) {
95+
Logging.instance.log(
96+
"JsonRPC _sendNextAvailableRequest attempted with"
97+
" _socksSocket=null on $host:$port",
98+
level: LogLevel.Error,
99+
);
100+
}
101+
// \r\n required by electrumx server
102+
_socksSocket?.write('${req.jsonRequest}\r\n');
89103
}
90104

91105
// TODO different timeout length?
92106
req.initiateTimeout(
93107
onTimedOut: () {
94-
_requestQueue.remove(req);
108+
_onReqCompleted(req);
95109
},
96110
);
97111
}
@@ -109,7 +123,7 @@ class JsonRPC {
109123
"JsonRPC request: opening socket $host:$port",
110124
level: LogLevel.Info,
111125
);
112-
await connect().timeout(requestTimeout, onTimeout: () {
126+
await _connect().timeout(requestTimeout, onTimeout: () {
113127
throw Exception("Request timeout: $jsonRpcRequest");
114128
});
115129
}
@@ -119,7 +133,7 @@ class JsonRPC {
119133
"JsonRPC request: opening SOCKS socket to $host:$port",
120134
level: LogLevel.Info,
121135
);
122-
await connect().timeout(requestTimeout, onTimeout: () {
136+
await _connect().timeout(requestTimeout, onTimeout: () {
123137
throw Exception("Request timeout: $jsonRpcRequest");
124138
});
125139
}
@@ -156,23 +170,42 @@ class JsonRPC {
156170
return future;
157171
}
158172

159-
Future<void> disconnect({required String reason}) async {
160-
await _requestMutex.protect(() async {
161-
await _subscription?.cancel();
162-
_subscription = null;
163-
_socket?.destroy();
164-
_socket = null;
165-
await _socksSocket?.close();
166-
_socksSocket = null;
167-
168-
// clean up remaining queue
169-
await _requestQueue.completeRemainingWithError(
170-
"JsonRPC disconnect() called with reason: \"$reason\"",
171-
);
172-
});
173+
/// DO NOT set [ignoreMutex] to true unless fully aware of the consequences
174+
Future<void> disconnect({
175+
required String reason,
176+
bool ignoreMutex = false,
177+
}) async {
178+
if (ignoreMutex) {
179+
await _disconnectHelper(reason: reason);
180+
} else {
181+
await _requestMutex.protect(() async {
182+
await _disconnectHelper(reason: reason);
183+
});
184+
}
185+
}
186+
187+
Future<void> _disconnectHelper({required String reason}) async {
188+
await _subscription?.cancel();
189+
_subscription = null;
190+
_socket?.destroy();
191+
_socket = null;
192+
await _socksSocket?.close();
193+
_socksSocket = null;
194+
195+
// clean up remaining queue
196+
await _requestQueue.completeRemainingWithError(
197+
"JsonRPC disconnect() called with reason: \"$reason\"",
198+
);
173199
}
174200

175-
Future<void> connect() async {
201+
Future<void> _connect() async {
202+
// ignore mutex is set to true here as _connect is already called within
203+
// the mutex.protect block. Setting to false here leads to a deadlock
204+
await disconnect(
205+
reason: "New connection requested",
206+
ignoreMutex: true,
207+
);
208+
176209
if (!Prefs.instance.useTor) {
177210
if (useSSL) {
178211
_socket = await SecureSocket.connect(
@@ -352,17 +385,20 @@ class _JsonRPCRequest {
352385
}
353386

354387
void initiateTimeout({
355-
VoidCallback? onTimedOut,
388+
required VoidCallback onTimedOut,
356389
}) {
357390
Future<void>.delayed(requestTimeout).then((_) {
358391
if (!isComplete) {
359-
try {
360-
throw JsonRpcException("_JsonRPCRequest timed out: $jsonRequest");
361-
} catch (e, s) {
362-
completer.completeError(e, s);
363-
onTimedOut?.call();
364-
}
392+
completer.complete(
393+
JsonRPCResponse(
394+
data: null,
395+
exception: JsonRpcException(
396+
"_JsonRPCRequest timed out: $jsonRequest",
397+
),
398+
),
399+
);
365400
}
401+
onTimedOut.call();
366402
});
367403
}
368404

@@ -375,14 +411,3 @@ class JsonRPCResponse {
375411

376412
JsonRPCResponse({this.data, this.exception});
377413
}
378-
379-
bool isIpAddress(String host) {
380-
try {
381-
// if the string can be parsed into an InternetAddress, it's an IP.
382-
InternetAddress(host);
383-
return true;
384-
} catch (e) {
385-
// if parsing fails, it's not an IP.
386-
return false;
387-
}
388-
}

lib/wallets/wallet/wallet.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,11 @@ abstract class Wallet<T extends CryptoCurrency> {
482482
),
483483
);
484484

485+
// add some small buffer before making calls.
486+
// this can probably be removed in the future but was added as a
487+
// debugging feature
488+
await Future<void>.delayed(const Duration(milliseconds: 300));
489+
485490
// TODO: [prio=low] handle this differently. Extra modification of this file for coin specific functionality should be avoided.
486491
final Set<String> codesToCheck = {};
487492
if (this is PaynymInterface) {

lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1702,7 +1702,7 @@ mixin ElectrumXInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
17021702
try {
17031703
final features = await electrumXClient
17041704
.getServerFeatures()
1705-
.timeout(const Duration(seconds: 4));
1705+
.timeout(const Duration(seconds: 5));
17061706

17071707
Logging.instance.log("features: $features", level: LogLevel.Info);
17081708

@@ -1715,8 +1715,8 @@ mixin ElectrumXInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
17151715
} catch (e, s) {
17161716
// do nothing, still allow user into wallet
17171717
Logging.instance.log(
1718-
"$runtimeType init() failed: $e\n$s",
1719-
level: LogLevel.Error,
1718+
"$runtimeType init() did not complete: $e\n$s",
1719+
level: LogLevel.Warning,
17201720
);
17211721
}
17221722

test/electrumx_test.mocks.dart

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -140,20 +140,18 @@ class MockJsonRPC extends _i1.Mock implements _i2.JsonRPC {
140140
)),
141141
) as _i5.Future<_i2.JsonRPCResponse>);
142142
@override
143-
_i5.Future<void> disconnect({required String? reason}) => (super.noSuchMethod(
143+
_i5.Future<void> disconnect({
144+
required String? reason,
145+
bool? ignoreMutex = false,
146+
}) =>
147+
(super.noSuchMethod(
144148
Invocation.method(
145149
#disconnect,
146150
[],
147-
{#reason: reason},
148-
),
149-
returnValue: _i5.Future<void>.value(),
150-
returnValueForMissingStub: _i5.Future<void>.value(),
151-
) as _i5.Future<void>);
152-
@override
153-
_i5.Future<void> connect() => (super.noSuchMethod(
154-
Invocation.method(
155-
#connect,
156-
[],
151+
{
152+
#reason: reason,
153+
#ignoreMutex: ignoreMutex,
154+
},
157155
),
158156
returnValue: _i5.Future<void>.value(),
159157
returnValueForMissingStub: _i5.Future<void>.value(),

0 commit comments

Comments
 (0)