Skip to content

Dart owner-isolate checks do not match the C API's owner-thread checks #412

Description

@sargunv-bot

NativeHandleState records Isolate.current.hashCode and validates against it (bindings/dart/lib/src/internal/lifecycle/lifecycle.dart:66,113). The C API validates std::thread::id. These are not equivalent: a Dart isolate migrates between OS threads while its hash stays constant.

Measured with pthread_self through dart:ffi, one isolate, awaiting file I/O in a loop:

isolate=970152377 initial os=135237815236288
MIGRATED at iteration 11: os 135237815236288 -> 135237808420544 (isolate hash unchanged: 970152377)
MIGRATED at iteration 19: os 135237808420544 -> 135237815236288 (isolate hash unchanged: 970152377)
MIGRATED at iteration 29: os 135237815236288 -> 135237808420544 (isolate hash unchanged: 970152377)

It reproduces on the main isolate, Isolate.run, and Isolate.spawn. Future.delayed alone did not move it; awaiting I/O did, so the isolate appears to resume on whichever VM thread completed the I/O.

The consequence, through the public binding:

final runtime = RuntimeHandle.create();
runtime.pump();                                  // OK
while (!migrated) {                              // any awaited I/O
  await File('/proc/self/stat').readAsString();
  migrated = osThread() != start;
}
runtime.pump();   // wrongThread (-3): runtime call must be made on its owner thread
runtime.close();  // wrongThread (-3): runtime must be destroyed on its owner thread

Both fail. Because close fails too, the runtime becomes undestroyable and mln_runtime_destroy refuses for the rest of the process, so the runtime, its run loop, and everything under it leak.

This affects any thread-affine handle (runtime, map, projection, render session) held across an awaited I/O operation, which is ordinary Dart code.

The existing test suite does not catch it because tests do their native calls without awaiting I/O in between.

I do not have a fix to propose that fits inside the current design. Options seem to be pinning native calls to a thread the binding owns and routing them there, or documenting a hard constraint that no thread-affine handle may be held across an await. Flagging it rather than guessing.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is not workingdartBinding or example for dart

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions