Skip to content

Commit 01e4017

Browse files
authored
Merge pull request #119 from cloudnc/fix/worker-kept-open-when-it-should-not
fix: worker kept open when it shouldn't
2 parents 2c9fe41 + 8b68204 commit 01e4017

File tree

5 files changed

+65
-22
lines changed

5 files changed

+65
-22
lines changed

projects/observable-webworker/src/lib/from-worker-pool.spec.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,17 @@ describe('fromWorkerPool', () => {
6060
sub.unsubscribe();
6161
});
6262

63+
it('does not send input close notification to ensure the workers are kept alive', () => {
64+
const subscriptionSpy = jasmine.createSpy('subscriptionSpy');
65+
const sub = stubbedWorkerStream.subscribe(subscriptionSpy);
66+
67+
input$.next(1);
68+
69+
expect(stubbedWorkers[0].postMessage).not.toHaveBeenCalledWith(jasmine.objectContaining({ kind: 'C' }));
70+
71+
sub.unsubscribe();
72+
});
73+
6374
it('shuts down workers when subscriber unsubscribes', () => {
6475
const subscriptionSpy = jasmine.createSpy('subscriptionSpy');
6576
const sub = stubbedWorkerStream.subscribe(subscriptionSpy);

projects/observable-webworker/src/lib/from-worker-pool.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Observable, ObservableInput, of, Subject, zip } from 'rxjs';
1+
import { concat, NEVER, Observable, ObservableInput, of, Subject, zip } from 'rxjs';
22
import { finalize, map, mergeAll, tap } from 'rxjs/operators';
33
import { fromWorker } from './from-worker';
44

@@ -69,7 +69,11 @@ export function fromWorkerPool<I, O>(
6969
}),
7070
map(
7171
([worker, unitWork]): Observable<O> => {
72-
return fromWorker<I, O>(() => worker.factory(), of(unitWork), selectTransferables, {
72+
// input should not complete to ensure the worker doesn't send back completion notifications when work unit is
73+
// processed, otherwise these would cause the fromWorker to unsubscribe from the result.
74+
const input$ = concat(of(unitWork), NEVER);
75+
// const input$ = of(unitWork);
76+
return fromWorker<I, O>(() => worker.factory(), input$, selectTransferables, {
7377
terminateOnComplete: false,
7478
}).pipe(
7579
finalize(() => {

projects/observable-webworker/src/lib/run-worker.spec.ts

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { fakeAsync, tick } from '@angular/core/testing';
22
import { BehaviorSubject, Notification, Observable, of } from 'rxjs';
3+
import { map } from 'rxjs/operators';
34
import {
45
DoTransferableWork,
56
DoTransferableWorkUnit,
@@ -76,12 +77,6 @@ describe('runWorker', () => {
7677
}),
7778
);
7879

79-
expect(postMessageSpy).toHaveBeenCalledWith(
80-
jasmine.objectContaining({
81-
kind: 'C',
82-
}),
83-
);
84-
8580
sub.unsubscribe();
8681
});
8782

@@ -128,13 +123,51 @@ describe('runWorker', () => {
128123
[expected.buffer] as any,
129124
);
130125

126+
sub.unsubscribe();
127+
});
128+
129+
// https://github.com/cloudnc/observable-webworker/issues/116
130+
it('should complete the notification stream when the worker completes', () => {
131+
const postMessageSpy = spyOn(window, 'postMessage');
132+
postMessageSpy.calls.reset();
133+
134+
class TestWorker implements DoWork<number, number> {
135+
public work(input$: Observable<number>): Observable<number> {
136+
// here nothing should keep the subscription alive when input$ completes
137+
return input$.pipe(map(input => input * 2));
138+
}
139+
}
140+
141+
const sub = runWorker(TestWorker);
142+
143+
const notificationEvent: WorkerMessageNotification<number> = new MessageEvent('message', {
144+
data: new Notification('N', 10),
145+
});
146+
147+
self.dispatchEvent(notificationEvent);
148+
149+
expect(postMessageSpy).toHaveBeenCalledWith(
150+
jasmine.objectContaining({
151+
kind: 'N',
152+
value: 20,
153+
}),
154+
);
155+
156+
const completeEvent: WorkerMessageNotification<number> = new MessageEvent('message', {
157+
data: new Notification('C'),
158+
});
159+
160+
self.dispatchEvent(completeEvent);
161+
131162
expect(postMessageSpy).toHaveBeenCalledWith(
132163
jasmine.objectContaining({
133164
kind: 'C',
134165
}),
135166
);
136167

137-
sub.unsubscribe();
168+
// do note here that instead of manually closing the subscription
169+
// we check it's already closed as expected
170+
expect(sub.closed).toBeTrue();
138171
});
139172

140173
it('should not complete the notification stream if the worker does not complete', () => {
@@ -197,12 +230,6 @@ describe('runWorker', () => {
197230
}),
198231
);
199232

200-
expect(postMessageSpy).toHaveBeenCalledWith(
201-
jasmine.objectContaining({
202-
kind: 'C',
203-
}),
204-
);
205-
206233
sub.unsubscribe();
207234
}));
208235
});

projects/observable-webworker/src/lib/run-worker.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { from, fromEvent, Notification, Observable, Subscription } from 'rxjs';
2-
import { concatMap, dematerialize, filter, map, materialize } from 'rxjs/operators';
2+
import { concatMap, dematerialize, map, materialize } from 'rxjs/operators';
33
import { DoTransferableWork, DoWork, DoWorkUnit, WorkerMessageNotification } from './observable-worker.types';
44

55
export type ObservableWorkerConstructor<I = any, O = any> = new (...args: any[]) => DoWork<I, O> | DoWorkUnit<I, O>;
@@ -25,15 +25,15 @@ export function getWorkerResult<I, O>(
2525
incomingMessages$: Observable<WorkerMessageNotification<I>>,
2626
): Observable<Notification<O>> {
2727
const input$ = incomingMessages$.pipe(
28-
map((e: WorkerMessageNotification<I>): Notification<I> => e.data),
29-
map((n: Notification<I>) => new Notification(n.kind, n.value, n.error)),
30-
// ignore complete, the calling thread will manage termination of the stream
31-
filter(n => n.kind !== 'C'),
28+
map(
29+
(e: WorkerMessageNotification<I>): Notification<I> => new Notification(e.data.kind, e.data.value, e.data.error),
30+
),
3231
dematerialize(),
3332
);
3433

3534
return workerIsUnitType(worker)
36-
? input$.pipe(concatMap(input => from(worker.workUnit(input)).pipe(materialize())))
35+
? // note we intentionally materialize the inner observable so the main thread can reassemble the multiple stream values per input observable
36+
input$.pipe(concatMap(input => from(worker.workUnit(input)).pipe(materialize())))
3737
: worker.work(input$).pipe(materialize());
3838
}
3939

projects/observable-webworker/tsconfig.lib.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"inlineSources": true,
88
"types": [],
99
"lib": ["dom", "es2018"],
10-
"strict": true
10+
"strict": true,
11+
"removeComments": true
1112
},
1213
"angularCompilerOptions": {
1314
"skipTemplateCodegen": true,

0 commit comments

Comments
 (0)