Skip to content

Commit aa59a5f

Browse files
ntkmenex3
andauthored
Fix race condition between spawning and killing isolates during shutdown (#2007)
Co-authored-by: Natalie Weizenbaum <[email protected]>
1 parent 760fa2e commit aa59a5f

File tree

3 files changed

+23
-4
lines changed

3 files changed

+23
-4
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
### Embedded Sass
44

5+
* Fix a race condition where closing standard input while requests are in-flight
6+
could sometimes cause the process to hang rather than shutting down
7+
gracefully.
8+
59
* Properly include the root stylesheet's URL in the set of loaded URLs when it
610
fails to parse.
711

lib/src/embedded/isolate_dispatcher.dart

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class IsolateDispatcher {
4646
/// The actual isolate objects that have been spawned.
4747
///
4848
/// Only used for cleaning up the process when the underlying channel closes.
49-
final _allIsolates = <Isolate>[];
49+
final _allIsolates = <Future<Isolate>>[];
5050

5151
/// A pool controlling how many isolates (and thus concurrent compilations)
5252
/// may be live at once.
@@ -101,10 +101,10 @@ class IsolateDispatcher {
101101
}
102102
}, onError: (Object error, StackTrace stackTrace) {
103103
_handleError(error, stackTrace);
104-
}, onDone: () {
104+
}, onDone: () async {
105105
_closed = true;
106106
for (var isolate in _allIsolates) {
107-
isolate.kill();
107+
(await isolate).kill();
108108
}
109109

110110
// Killing isolates isn't sufficient to make sure the process closes; we
@@ -130,7 +130,9 @@ class IsolateDispatcher {
130130
}
131131

132132
var receivePort = ReceivePort();
133-
_allIsolates.add(await Isolate.spawn(_isolateMain, receivePort.sendPort));
133+
var future = Isolate.spawn(_isolateMain, receivePort.sendPort);
134+
_allIsolates.add(future);
135+
await future;
134136

135137
var channel = IsolateChannel<_InitialMessage?>.connectReceive(receivePort)
136138
.transform(const ExplicitCloseTransformer());

test/embedded/protocol_test.dart

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,19 @@ void main() {
224224
await process.shouldExit(0);
225225
});
226226

227+
test("closes gracefully with many in-flight compilations", () async {
228+
// This should always be equal to the size of
229+
// [IsolateDispatcher._isolatePool], since that's as many concurrent
230+
// compilations as we can realistically have anyway.
231+
var totalRequests = 15;
232+
for (var i = 1; i <= totalRequests; i++) {
233+
process.inbound
234+
.add((i, compileString("a {b: foo() + 2px}", functions: [r"foo()"])));
235+
}
236+
237+
await process.close();
238+
}, skip: "Enable once dart-lang/stream_channel#92 is released");
239+
227240
test("doesn't include a source map by default", () async {
228241
process.send(compileString("a {b: 1px + 2px}"));
229242
await expectSuccess(process, "a { b: 3px; }", sourceMap: isEmpty);

0 commit comments

Comments
 (0)