From 7b5bd07e87dc8228e7ea5c443da8d3d8cba76b5e Mon Sep 17 00:00:00 2001 From: Slava Egorov Date: Wed, 7 Jan 2026 03:41:35 -0800 Subject: [PATCH] [vm/io] Fix pausing read on SecureSocket Pausing read on the SecureSocket should pause read on the underlying socket, because otherwise we will be stuck calling _readHandler which will not read anything but still schedule an SSL filter wasting CPU time. Fixes https://github.com/dart-lang/sdk/issues/62037 CoreLibraryReviewExempt: No API changes, VM specific implementation change Change-Id: I4144815d34c1e77a68533b6ebcb66c146a6a6964 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/467804 Reviewed-by: Martin Kustermann Commit-Queue: Slava Egorov --- sdk/lib/io/secure_socket.dart | 5 +- tests/standalone/io/regress_62037_test.dart | 67 +++++++++++++++++++ .../io/secure_bad_certificate_test.dart | 1 + ...secure_server_client_certificate_test.dart | 2 + .../io/secure_server_closing_test.dart | 3 + .../io/secure_server_socket_test.dart | 32 +++++---- 6 files changed, 97 insertions(+), 13 deletions(-) create mode 100644 tests/standalone/io/regress_62037_test.dart diff --git a/sdk/lib/io/secure_socket.dart b/sdk/lib/io/secure_socket.dart index c03236249dc..eb87468648c 100644 --- a/sdk/lib/io/secure_socket.dart +++ b/sdk/lib/io/secure_socket.dart @@ -834,6 +834,7 @@ class _RawSecureSocket extends Stream void set readEventsEnabled(bool value) { _readEventsEnabled = value; + _socket.readEventsEnabled = value; _scheduleReadEvent(); } @@ -1084,7 +1085,9 @@ class _RawSecureSocket extends Stream _secureFilter = null; return; } - _socket.readEventsEnabled = true; + if (_readEventsEnabled) { + _socket.readEventsEnabled = true; + } if (_filterStatus.writeEmpty && _closedWrite && !_socketClosedWrite) { // Checks for and handles all cases of partially closed sockets. shutdown(SocketDirection.send); diff --git a/tests/standalone/io/regress_62037_test.dart b/tests/standalone/io/regress_62037_test.dart new file mode 100644 index 00000000000..71ae59d044c --- /dev/null +++ b/tests/standalone/io/regress_62037_test.dart @@ -0,0 +1,67 @@ +// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +// +// OtherResources=certificates/server_chain.pem +// OtherResources=certificates/server_key.pem + +// This test verifies that setting readEventsEnabled to false on +// RawSecureSocket correctly updates readEventsEnabled on the underlying +// RawSocket. + +import 'dart:async'; +import 'dart:io'; + +import 'package:async_helper/async_helper.dart'; +import 'package:expect/expect.dart'; + +String relativeToScript(path) => Platform.script.resolve(path).toFilePath(); + +final String serverCert = relativeToScript('certificates/server_chain.pem'); +final String serverKey = relativeToScript('certificates/server_key.pem'); + +void main() { + asyncTest(() async { + final serverContext = SecurityContext() + ..useCertificateChain(serverCert) + ..usePrivateKey(serverKey, password: 'dartdart'); + + final secureServer = await SecureServerSocket.bind( + InternetAddress.loopbackIPv4, + 0, + serverContext, + ); + + final connection = secureServer.first; + + final clientSocket = await RawSocket.connect( + InternetAddress.loopbackIPv4, + secureServer.port, + ); + final clientSecureSocket = await RawSecureSocket.secure( + clientSocket, + context: SecurityContext()..setTrustedCertificates(serverCert), + ); + + clientSecureSocket.listen((event) {}); + + final connected = await connection; + connected.drain(); + + Expect.isTrue(clientSocket.readEventsEnabled); + + for (var i = 0; i < 2; i++) { + clientSecureSocket.readEventsEnabled = false; + Expect.isFalse(clientSocket.readEventsEnabled); + + clientSecureSocket.readEventsEnabled = true; + Expect.isTrue(clientSocket.readEventsEnabled); + } + + await Future.wait([ + clientSecureSocket.close(), + secureServer.close(), + connected.close(), + ]); + }); +} diff --git a/tests/standalone/io/secure_bad_certificate_test.dart b/tests/standalone/io/secure_bad_certificate_test.dart index 10e15b9ce36..728508b133b 100644 --- a/tests/standalone/io/secure_bad_certificate_test.dart +++ b/tests/standalone/io/secure_bad_certificate_test.dart @@ -114,6 +114,7 @@ Future runClient( onBadCertificate: badCertificateCallback, ); Expect.equals('pass', result); // Is rethrown below + socket.drain(); await socket.close(); } catch (error) { if (error is ExpectException) rethrow; diff --git a/tests/standalone/io/secure_server_client_certificate_test.dart b/tests/standalone/io/secure_server_client_certificate_test.dart index 26070b844e3..80a5b9ea2af 100644 --- a/tests/standalone/io/secure_server_client_certificate_test.dart +++ b/tests/standalone/io/secure_server_client_certificate_test.dart @@ -127,7 +127,9 @@ Future testClientCertificate({ X509Certificate serverCertificate = clientEnd.peerCertificate!; Expect.isTrue(serverCertificate.subject.contains("localhost")); Expect.isTrue(serverCertificate.issuer.contains("intermediateauthority")); + clientEnd.drain(); clientEnd.close(); + serverEnd.drain(); serverEnd.close(); } diff --git a/tests/standalone/io/secure_server_closing_test.dart b/tests/standalone/io/secure_server_closing_test.dart index d4023db9666..3f6b5c2d73d 100644 --- a/tests/standalone/io/secure_server_closing_test.dart +++ b/tests/standalone/io/secure_server_closing_test.dart @@ -118,6 +118,7 @@ testPauseServerSocket() { var subscription; subscription = server.listen((connection) { Expect.isTrue(resumed); + connection.drain(); connection.close(); if (++acceptCount == 2 * socketCount) { server.close(); @@ -133,6 +134,7 @@ testPauseServerSocket() { SecureSocket.connect(HOST, server.port, context: clientContext).then(( connection, ) { + connection.drain(); connection.close(); }); } @@ -143,6 +145,7 @@ testPauseServerSocket() { SecureSocket.connect(HOST, server.port, context: clientContext).then(( connection, ) { + connection.drain(); connection.close(); }); } diff --git a/tests/standalone/io/secure_server_socket_test.dart b/tests/standalone/io/secure_server_socket_test.dart index 0b51ef8746f..864cdd0c5c2 100644 --- a/tests/standalone/io/secure_server_socket_test.dart +++ b/tests/standalone/io/secure_server_socket_test.dart @@ -88,15 +88,19 @@ void testSimpleConnect() { context: clientContext, ); server.listen((serverEnd) { - clientEndFuture.then((clientEnd) { + clientEndFuture.then((clientEnd) async { var x5 = clientEnd.peerCertificate!; print(x5.subject); print(x5.issuer); print(x5.startValidity); print(x5.endValidity); - clientEnd.close(); - serverEnd.close(); - server.close(); + await Future.wait([ + clientEnd.drain(), + clientEnd.close(), + serverEnd.drain(), + serverEnd.close(), + server.close(), + ]); asyncEnd(); }); }); @@ -137,8 +141,8 @@ void testSimpleConnectFail( error is HandshakeException || error is SocketException, ); - clientEndFuture.then((_) { - if (!cancelOnError) server.close(); + clientEndFuture.then((_) async { + if (!cancelOnError) await server.close(); asyncEnd(); }); }, @@ -158,10 +162,14 @@ void testServerListenAfterConnect() { ); new Timer(const Duration(milliseconds: 500), () { server.listen((serverEnd) { - clientEndFuture.then((clientEnd) { - clientEnd.close(); - serverEnd.close(); - server.close(); + clientEndFuture.then((clientEnd) async { + await Future.wait([ + clientEnd.drain(), + clientEnd.close(), + serverEnd.drain(), + serverEnd.close(), + server.close(), + ]); asyncEnd(); }); }); @@ -231,9 +239,9 @@ void testSimpleReadWrite() { dataReceived.setRange(bytesRead, bytesRead + buffer.length, buffer); bytesRead += buffer.length; }, - onDone: () { + onDone: () async { verifyTestData(dataReceived); - socket.close(); + await socket.close(); asyncEnd(); }, );