From d3aa74a2f6179737f0d11b2b990a3a0795d66b5a Mon Sep 17 00:00:00 2001 From: Ryan Macnak Date: Mon, 30 Jun 2025 14:23:13 -0700 Subject: [PATCH] [dart:io] Create one IOService port per process instead of per isolate. Nothing cleaned up this port when the creating isolate died, so the port itself and the last unjoined thread in the corresponding thread pool were leaked until VM shutdown. Cf. 42d796f7c1b11238d7e72cb75b73959baf7adbc3 TEST=testandalone/io/leak_io_service_test Change-Id: Icea27804baf9175b1ff5eb9c50970e0f8d07de2c Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/437160 Reviewed-by: Brian Quinlan Commit-Queue: Ryan Macnak --- runtime/bin/dart_embedder_api_impl.cc | 2 ++ runtime/bin/io_service.cc | 16 +++++++++-- runtime/bin/io_service.h | 4 +++ tests/standalone/io/leak_io_service_test.dart | 28 +++++++++++++++++++ 4 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 tests/standalone/io/leak_io_service_test.dart diff --git a/runtime/bin/dart_embedder_api_impl.cc b/runtime/bin/dart_embedder_api_impl.cc index 92081c694d9..c280fc8e2df 100644 --- a/runtime/bin/dart_embedder_api_impl.cc +++ b/runtime/bin/dart_embedder_api_impl.cc @@ -6,6 +6,7 @@ #include "bin/dartutils.h" #include "bin/eventhandler.h" +#include "bin/io_service.h" #include "bin/isolate_data.h" #include "bin/process.h" #include "bin/secure_socket_filter.h" @@ -54,6 +55,7 @@ void Cleanup() { bin::SSLFilter::Cleanup(); #endif bin::Process::Cleanup(); + bin::IOService::Cleanup(); } Dart_Isolate CreateKernelServiceIsolate(const IsolateCreationData& data, diff --git a/runtime/bin/io_service.cc b/runtime/bin/io_service.cc index 673a0cee1f1..0afa15ef1b1 100644 --- a/runtime/bin/io_service.cc +++ b/runtime/bin/io_service.cc @@ -55,10 +55,22 @@ void IOServiceCallback(Dart_Port dest_port_id, Dart_CObject* message) { } intptr_t IOService::max_concurrency_ = 32; +std::atomic IOService::port_ = ILLEGAL_PORT; Dart_Port IOService::GetServicePort() { - return Dart_NewConcurrentNativePort("IOService", IOServiceCallback, - max_concurrency_); + Dart_Port port = port_; + if (port == ILLEGAL_PORT) { + port = Dart_NewConcurrentNativePort("IOService", IOServiceCallback, + max_concurrency_); + Dart_Port expected = ILLEGAL_PORT; + if (!port_.compare_exchange_strong(expected, port)) { + // Lost the initialization race. Use the winner's port and close our port. + // The winner's port is eventually implicitly closed by VM shutdown. + Dart_CloseNativePort(port); + return expected; + } + } + return port; } void FUNCTION_NAME(IOService_NewServicePort)(Dart_NativeArguments args) { diff --git a/runtime/bin/io_service.h b/runtime/bin/io_service.h index d440f0f738f..5e92251a113 100644 --- a/runtime/bin/io_service.h +++ b/runtime/bin/io_service.h @@ -9,6 +9,8 @@ #error "io_service.h can only be included on builds with IO and SSL enabled" #endif +#include + #include "bin/builtin.h" #include "bin/utils.h" @@ -69,12 +71,14 @@ class IOService { enum { IO_SERVICE_REQUEST_LIST(DECLARE_REQUEST) }; static Dart_Port GetServicePort(); + static void Cleanup() { port_ = ILLEGAL_PORT; } static intptr_t max_concurrency() { return max_concurrency_; } static void set_max_concurrency(intptr_t value) { max_concurrency_ = value; } private: static intptr_t max_concurrency_; + static std::atomic port_; DISALLOW_ALLOCATION(); DISALLOW_IMPLICIT_CONSTRUCTORS(IOService); diff --git a/tests/standalone/io/leak_io_service_test.dart b/tests/standalone/io/leak_io_service_test.dart new file mode 100644 index 00000000000..0c3a2c57948 --- /dev/null +++ b/tests/standalone/io/leak_io_service_test.dart @@ -0,0 +1,28 @@ +// Copyright (c) 2025, 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. + +import "dart:async"; +import "dart:io"; +import "dart:isolate"; + +child(replyPort) async { + var ops = []; + for (var i = 0; i < 32; i++) { + ops.add(File(Platform.executable).stat()); // Uses the IO Service. + } + await Future.wait(ops); + replyPort.send(null); +} + +main() { + var pending = 1000; + var port = new RawReceivePort(); + port.handler = (_) { + pending--; + if (pending == 0) port.close(); + }; + for (var i = 0; i < pending; i++) { + Isolate.spawn(child, port.sendPort); + } +}