dart-lang / sdk

The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.
https://dart.dev
BSD 3-Clause "New" or "Revised" License
10.28k stars 1.58k forks source link

Synchronous I/O functions from dart:io don't prevent an isolate group from hanging #57119

Open abitofevrything opened 3 days ago

abitofevrything commented 3 days ago

This issue is in spirit a continuation of #51254; I'm just picking up where that issue left off.

I would have assumed that since that issue was closed and Dart_EnterIsolate/Dart_ExitIsolate are now part of the public API, dart:io's sync functions would have been updated to call these methods themselves. However this doesn't seem to be the case, and calling sync functions in many isolates at once can still cause an isolate group to hang.

Example:

import 'dart:async';
import 'dart:io';
import 'dart:isolate';

void main() async {
  print('Main isolate alive!');

  Timer.periodic(
    Duration(milliseconds: 100),
    (_) => print('Main isolate alive!'),
  );

  // 16 is the limit, as documented in #51254. Setting this to 15 allows the main isolate to keep running
  for (int i = 0; i < 16; i++) {
    Isolate.spawn((_) {
      while (true) {
        print('Isolate $i alive');

        // Any synchronous function call that blocks the isolate. Here are some examples:
        // Process.runSync('sleep', ['1']);
        // File.fromUri(Platform.script).readAsBytesSync();
        // Directory.current.listSync();
        // Directory.current.createSync(recursive: true);
        sleep(Duration(seconds: 1));
      }
    }, null);
  }
}

I would expect these functions to call Dart_ExitIsolate (or some internal equivalent) before running the blocking call (whether it be poll in the case of Process.runSync, sleep in the case of sleep or any other blocking native call) in order to allow other isolates in the isolate group to run. Maybe this could also be done when calling into FFI isLeaf: true calls?

I don't know of any other blocking functions in the SDK other than dart:io's, but if there are some it goes without saying the same should be done there.

Tested on dart version 3.7.0-132.0.dev and 73adec7b6f6d544b6b58752922f3b5938ad463e9

dart-github-bot commented 3 days ago

Summary: Synchronous dart:io functions don't release the isolate group, causing hangs when many isolates use them concurrently. The issue persists despite Dart_EnterIsolate/Dart_ExitIsolate being public.

abitofevrything commented 3 days ago

Maybe the overhead of entering/exiting the isolate isn't worth it for some very quick calls - but I can definitely see it being useful on longer calls (notably Process.runSync, which is where we noticed this issue).