fix SchedulerService isolate-spawn race (T-106)
_startTicker fired Isolate.spawn(...).then((iso) => _isolate = iso) and returned. If _stopTicker landed before the spawn future resolved, _isolate was still null at kill time and the just-spawned isolate (with its Timer.periodic) leaked forever. Track the spawn as _isolateReady and have _stopTicker await it before killing. Same shape as the NativePty fix from T-96. dispose() is now async; the single caller in facade.dart already sat inside an async dispose chain and just needed the await. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -220,5 +220,22 @@ void main() {
|
||||
bus.emit(const ProjectClosed());
|
||||
// Smoke — no throw.
|
||||
});
|
||||
|
||||
test('dispose immediately after ProjectOpened awaits the in-flight spawn (T-106)', () async {
|
||||
// Race window: ProjectOpened triggers _startTicker which calls
|
||||
// `Isolate.spawn(...)`; if dispose lands before the spawn future
|
||||
// resolves, the old code left _isolate=null and the just-spawned
|
||||
// ticker leaked forever. Now dispose awaits _isolateReady before
|
||||
// killing.
|
||||
final bus = DaemonBus();
|
||||
final s = SchedulerService(bus);
|
||||
s.start();
|
||||
bus.emit(const ProjectOpened(path: '/tmp/x'));
|
||||
// Don't wait for the spawn to settle — dispose must do that itself.
|
||||
await s.dispose();
|
||||
// No throw, no leaked isolate (test runner would flag a lingering
|
||||
// isolate by failing to exit cleanly). Reaching this line is the
|
||||
// assertion.
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user