-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Description
We've seen multiple instances of errors, with the following exception being thrown when using WebSocket from dart:io:
SocketException: SocketException: Reading from a closed socket
File "secure_socket.dart", line 770, in _RawSecureSocket.read
File "socket_patch.dart", line 2323, in _Socket._onData
File "zone.dart", line 1399, in _rootRunUnary
File "zone.dart", line 1300, in _CustomZone.runUnary
File "zone.dart", line 1209, in _CustomZone.runUnaryGuarded
File "stream_impl.dart", line 339, in _BufferingStreamSubscription._sendData
File "stream_impl.dart", line 271, in _BufferingStreamSubscription._add
File "stream_controller.dart", line 774, in _SyncStreamControllerDispatch._sendData
File "stream_controller.dart", line 648, in _StreamController._add
File "stream_controller.dart", line 596, in _StreamController.add
File "secure_socket.dart", line 1107, in _RawSecureSocket._sendReadEvent
File "zone.dart", line 1383, in _rootRun
File "zone.dart", line 1293, in _CustomZone.run
File "zone.dart", line 1201, in _CustomZone.runGuarded
File "zone.dart", line 1241, in _CustomZone.bindCallbackGuarded.<fn>
File "zone.dart", line 1391, in _rootRun
File "zone.dart", line 1293, in _CustomZone.run
File "zone.dart", line 1225, in _CustomZone.bindCallback.<fn>
File "timer_patch.dart", line 18, in Timer._createTimer.<fn>
File "timer_impl.dart", line 398, in _Timer._runTimers
File "timer_impl.dart", line 429, in _Timer._handleMessage
File "isolate_patch.dart", line 192, in _RawReceivePortImpl._handleMessage
We did some investigation, and managed to create a small(ish) reproduction which uses a tiny nodejs server to echo back messages, but you could probably reproduce with other websocket servers.
Versions:
Dart SDK version: 2.19.5 (stable) (Mon Mar 20 17:09:37 2023 +0000) on "macos_arm64"
The following code seems to produce the issue fairly reliably, though we're pretty sure it's a race condition so your mileage my vary. Please note we haven't managed to reproduce this on a non-secure websocket channel, and we don't think it will reproducable. The exception seems to be raised here
sdk/sdk/lib/io/secure_socket.dart
Lines 776 to 777 in 7d93401
if (_closedRead) { | |
throw new SocketException("Reading from a closed socket"); |
Repro code:
import 'dart:async';
import 'dart:io';
class MyHttpOverrides extends HttpOverrides {
@override
HttpClient createHttpClient(SecurityContext? context) {
return super.createHttpClient(context)
..badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
}
}
void main() async {
HttpOverrides.global = MyHttpOverrides();
await connect();
}
Future<void> connect() async {
StreamSubscription<dynamic>? sub;
var sock = await WebSocket.connect(
"wss://localhost:8080",
protocols: ["echo-protocol"],
);
sub = sock.listen((event) {
print("got event: $event");
sub?.cancel();
});
sock.add("Hello!");
}
and here is the small echo server we wrote to test against.
var WebSocketServer = require('websocket').server;
var https = require('https');
const fs = require('fs');
const options = {
key: fs.readFileSync('domain.key'),
cert: fs.readFileSync('domain.crt'),
passphrase: 'password'
};
var server = https.createServer(options, function(request, response) {
console.log((new Date()) + ' Received request for ' + request.url);
response.writeHead(404);
response.end();
});
server.listen(8080, function() {
console.log((new Date()) + ' Server is listening on port 8080');
});
var wsServer = new WebSocketServer({
httpServer: server,
// You should not use autoAcceptConnections for production
// applications, as it defeats all standard cross-origin protection
// facilities built into the protocol and the browser. You should
// *always* verify the connection's origin and decide whether or not
// to accept it.
autoAcceptConnections: false
});
wsServer.on('request', function(request) {
var connection = request.accept('echo-protocol', request.origin);
console.log((new Date()) + ' Connection accepted.');
connection.on('message', function(message) {
if (message.type === 'utf8') {
console.log('Received Message: ' + message.utf8Data);
connection.sendUTF(message.utf8Data);
connection.sendUTF(message.utf8Data);
connection.sendUTF(message.utf8Data);
connection.sendUTF(message.utf8Data);
connection.close();
}
});
connection.on('close', function(reasonCode, description) {
console.log((new Date()) + ' Peer ' + connection.remoteAddress + ' disconnected.');
});
});