[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 <kustermann@google.com>
Commit-Queue: Slava Egorov <vegorov@google.com>
This commit is contained in:
Slava Egorov
2026-01-07 03:41:35 -08:00
committed by Commit Queue
parent c552af0a9e
commit 7b5bd07e87
6 changed files with 97 additions and 13 deletions
+4 -1
View File
@@ -834,6 +834,7 @@ class _RawSecureSocket extends Stream<RawSocketEvent>
void set readEventsEnabled(bool value) {
_readEventsEnabled = value;
_socket.readEventsEnabled = value;
_scheduleReadEvent();
}
@@ -1084,7 +1085,9 @@ class _RawSecureSocket extends Stream<RawSocketEvent>
_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);
@@ -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(),
]);
});
}
@@ -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;
@@ -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();
}
@@ -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();
});
}
@@ -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();
},
);