@furystack/utils 8.2.0 → 8.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/esm/event-hub.spec.js +2 -0
- package/esm/event-hub.spec.js.map +1 -1
- package/esm/semaphore.spec.d.ts.map +1 -1
- package/esm/semaphore.spec.js +231 -230
- package/esm/semaphore.spec.js.map +1 -1
- package/package.json +1 -1
- package/src/event-hub.spec.ts +2 -0
- package/src/semaphore.spec.ts +255 -255
package/src/semaphore.spec.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest'
|
|
2
2
|
import { sleepAsync } from './sleep-async.js'
|
|
3
3
|
import { using } from './using.js'
|
|
4
|
+
import { usingAsync } from './using-async.js'
|
|
4
5
|
import { Semaphore, SemaphoreDisposedError } from './semaphore.js'
|
|
5
6
|
|
|
6
7
|
export const semaphoreTests = describe('Semaphore', () => {
|
|
@@ -16,375 +17,374 @@ export const semaphoreTests = describe('Semaphore', () => {
|
|
|
16
17
|
})
|
|
17
18
|
|
|
18
19
|
it('should execute a single task and return its result', async () => {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
await usingAsync(new Semaphore(2), async (s) => {
|
|
21
|
+
const result = await s.execute(async () => 42)
|
|
22
|
+
expect(result).toBe(42)
|
|
23
|
+
expect(s.completedCount.getValue()).toBe(1)
|
|
24
|
+
expect(s.runningCount.getValue()).toBe(0)
|
|
25
|
+
})
|
|
25
26
|
})
|
|
26
27
|
|
|
27
28
|
it('should execute up to N tasks concurrently and queue the rest', async () => {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const createTask = (name: string) =>
|
|
33
|
-
s.execute(async () => {
|
|
34
|
-
running.push(name)
|
|
35
|
-
await new Promise<void>((resolve) => resolvers.push(resolve))
|
|
36
|
-
return name
|
|
37
|
-
})
|
|
29
|
+
await usingAsync(new Semaphore(2), async (s) => {
|
|
30
|
+
const running: string[] = []
|
|
31
|
+
const resolvers: Array<() => void> = []
|
|
38
32
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
33
|
+
const createTask = (name: string) =>
|
|
34
|
+
s.execute(async () => {
|
|
35
|
+
running.push(name)
|
|
36
|
+
await new Promise<void>((resolve) => resolvers.push(resolve))
|
|
37
|
+
return name
|
|
38
|
+
})
|
|
42
39
|
|
|
43
|
-
|
|
40
|
+
const p1 = createTask('a')
|
|
41
|
+
const p2 = createTask('b')
|
|
42
|
+
const p3 = createTask('c')
|
|
44
43
|
|
|
45
|
-
|
|
46
|
-
expect(s.runningCount.getValue()).toBe(2)
|
|
47
|
-
expect(s.pendingCount.getValue()).toBe(1)
|
|
44
|
+
await sleepAsync(10)
|
|
48
45
|
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
expect(running).toEqual(['a', 'b'])
|
|
47
|
+
expect(s.runningCount.getValue()).toBe(2)
|
|
48
|
+
expect(s.pendingCount.getValue()).toBe(1)
|
|
51
49
|
|
|
52
|
-
|
|
50
|
+
resolvers[0]()
|
|
51
|
+
await p1
|
|
53
52
|
|
|
54
|
-
|
|
55
|
-
expect(s.runningCount.getValue()).toBe(2)
|
|
56
|
-
expect(s.pendingCount.getValue()).toBe(0)
|
|
53
|
+
await sleepAsync(10)
|
|
57
54
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
55
|
+
expect(running).toEqual(['a', 'b', 'c'])
|
|
56
|
+
expect(s.runningCount.getValue()).toBe(2)
|
|
57
|
+
expect(s.pendingCount.getValue()).toBe(0)
|
|
61
58
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
resolvers[1]()
|
|
60
|
+
resolvers[2]()
|
|
61
|
+
await Promise.all([p2, p3])
|
|
62
|
+
|
|
63
|
+
expect(s.completedCount.getValue()).toBe(3)
|
|
64
|
+
expect(s.runningCount.getValue()).toBe(0)
|
|
65
|
+
})
|
|
65
66
|
})
|
|
66
67
|
|
|
67
68
|
it('should propagate task rejection to the caller and continue processing', async () => {
|
|
68
|
-
|
|
69
|
-
|
|
69
|
+
await usingAsync(new Semaphore(1), async (s) => {
|
|
70
|
+
const taskError = new Error('task failed')
|
|
70
71
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
const p1 = s.execute(async () => {
|
|
73
|
+
throw taskError
|
|
74
|
+
})
|
|
75
|
+
const p2 = s.execute(async () => 'ok')
|
|
75
76
|
|
|
76
|
-
|
|
77
|
+
await expect(p1).rejects.toThrow('task failed')
|
|
77
78
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
const result = await p2
|
|
80
|
+
expect(result).toBe('ok')
|
|
81
|
+
expect(s.failedCount.getValue()).toBe(1)
|
|
82
|
+
expect(s.completedCount.getValue()).toBe(1)
|
|
83
|
+
})
|
|
83
84
|
})
|
|
84
85
|
|
|
85
86
|
describe('ObservableValue counters', () => {
|
|
86
87
|
it('should update pendingCount and runningCount on transitions', async () => {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
await usingAsync(new Semaphore(1), async (s) => {
|
|
89
|
+
const pendingChanges: number[] = []
|
|
90
|
+
const runningChanges: number[] = []
|
|
90
91
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
92
|
+
s.pendingCount.subscribe((v) => {
|
|
93
|
+
pendingChanges.push(v)
|
|
94
|
+
})
|
|
95
|
+
s.runningCount.subscribe((v) => {
|
|
96
|
+
runningChanges.push(v)
|
|
97
|
+
})
|
|
97
98
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
let resolve!: () => void
|
|
100
|
+
const p1 = s.execute(async () => {
|
|
101
|
+
await new Promise<void>((r) => (resolve = r))
|
|
102
|
+
})
|
|
102
103
|
|
|
103
|
-
|
|
104
|
+
const p2 = s.execute(async () => 'done')
|
|
104
105
|
|
|
105
|
-
|
|
106
|
+
await sleepAsync(10)
|
|
106
107
|
|
|
107
|
-
|
|
108
|
-
|
|
108
|
+
expect(pendingChanges).toContain(1)
|
|
109
|
+
expect(runningChanges).toContain(1)
|
|
109
110
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
111
|
+
resolve()
|
|
112
|
+
await p1
|
|
113
|
+
await sleepAsync(10)
|
|
114
|
+
await p2
|
|
114
115
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
expect(s.pendingCount.getValue()).toBe(0)
|
|
117
|
+
expect(s.runningCount.getValue()).toBe(0)
|
|
118
|
+
expect(s.completedCount.getValue()).toBe(2)
|
|
119
|
+
})
|
|
119
120
|
})
|
|
120
121
|
|
|
121
122
|
it('should update completedCount and failedCount correctly', async () => {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
.catch(() => {})
|
|
123
|
+
await usingAsync(new Semaphore(2), async (s) => {
|
|
124
|
+
await s.execute(async () => 'ok')
|
|
125
|
+
await s
|
|
126
|
+
.execute(async () => {
|
|
127
|
+
throw new Error('fail')
|
|
128
|
+
})
|
|
129
|
+
.catch(() => {})
|
|
130
130
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
131
|
+
expect(s.completedCount.getValue()).toBe(1)
|
|
132
|
+
expect(s.failedCount.getValue()).toBe(1)
|
|
133
|
+
})
|
|
134
134
|
})
|
|
135
135
|
})
|
|
136
136
|
|
|
137
137
|
describe('AbortSignal support', () => {
|
|
138
138
|
it('should abort a pending task when the caller signal aborts', async () => {
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
await usingAsync(new Semaphore(1), async (s) => {
|
|
140
|
+
let resolve!: () => void
|
|
141
141
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
142
|
+
const p1 = s.execute(async () => {
|
|
143
|
+
await new Promise<void>((r) => (resolve = r))
|
|
144
|
+
})
|
|
145
145
|
|
|
146
|
-
|
|
147
|
-
|
|
146
|
+
const controller = new AbortController()
|
|
147
|
+
const p2 = s.execute(async () => 'should not run', { signal: controller.signal })
|
|
148
148
|
|
|
149
|
-
|
|
150
|
-
|
|
149
|
+
await sleepAsync(10)
|
|
150
|
+
expect(s.pendingCount.getValue()).toBe(1)
|
|
151
151
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
152
|
+
controller.abort(new Error('cancelled'))
|
|
153
|
+
await expect(p2).rejects.toThrow('cancelled')
|
|
154
|
+
expect(s.pendingCount.getValue()).toBe(0)
|
|
155
155
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
156
|
+
resolve()
|
|
157
|
+
await p1
|
|
158
|
+
})
|
|
159
159
|
})
|
|
160
160
|
|
|
161
161
|
it('should reject immediately if the caller signal is already aborted', async () => {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
162
|
+
await usingAsync(new Semaphore(1), async (s) => {
|
|
163
|
+
const controller = new AbortController()
|
|
164
|
+
controller.abort(new Error('pre-aborted'))
|
|
165
165
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
166
|
+
await expect(s.execute(async () => 'should not run', { signal: controller.signal })).rejects.toThrow(
|
|
167
|
+
'pre-aborted',
|
|
168
|
+
)
|
|
169
169
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
170
|
+
expect(s.pendingCount.getValue()).toBe(0)
|
|
171
|
+
expect(s.runningCount.getValue()).toBe(0)
|
|
172
|
+
})
|
|
173
173
|
})
|
|
174
174
|
|
|
175
175
|
it('should clean up the caller signal listener when the task completes normally', async () => {
|
|
176
|
-
|
|
177
|
-
|
|
176
|
+
await usingAsync(new Semaphore(1), async (s) => {
|
|
177
|
+
const controller = new AbortController()
|
|
178
178
|
|
|
179
|
-
|
|
179
|
+
const removeSpy = vi.spyOn(controller.signal, 'removeEventListener')
|
|
180
180
|
|
|
181
|
-
|
|
181
|
+
await s.execute(async () => 'done', { signal: controller.signal })
|
|
182
182
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
183
|
+
expect(removeSpy).toHaveBeenCalledWith('abort', expect.any(Function))
|
|
184
|
+
removeSpy.mockRestore()
|
|
185
|
+
})
|
|
186
186
|
})
|
|
187
187
|
|
|
188
188
|
it('should abort the signal passed to a running task when the caller signal aborts', async () => {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
189
|
+
await usingAsync(new Semaphore(1), async (s) => {
|
|
190
|
+
const signalAborted = vi.fn()
|
|
191
|
+
|
|
192
|
+
const controller = new AbortController()
|
|
193
|
+
const p = s.execute(
|
|
194
|
+
async ({ signal }) => {
|
|
195
|
+
signal.addEventListener('abort', signalAborted)
|
|
196
|
+
await new Promise<void>((resolve) => {
|
|
197
|
+
signal.addEventListener('abort', () => resolve())
|
|
198
|
+
})
|
|
199
|
+
throw signal.reason
|
|
200
|
+
},
|
|
201
|
+
{ signal: controller.signal },
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
await sleepAsync(10)
|
|
205
|
+
expect(s.runningCount.getValue()).toBe(1)
|
|
206
|
+
|
|
207
|
+
controller.abort(new Error('stop'))
|
|
208
|
+
await expect(p).rejects.toThrow('stop')
|
|
209
|
+
expect(signalAborted).toBeCalledTimes(1)
|
|
210
|
+
})
|
|
211
211
|
})
|
|
212
212
|
})
|
|
213
213
|
|
|
214
214
|
describe('EventHub events', () => {
|
|
215
215
|
it('should emit taskStarted when a task begins running', async () => {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
216
|
+
await usingAsync(new Semaphore(1), async (s) => {
|
|
217
|
+
const listener = vi.fn()
|
|
218
|
+
s.subscribe('taskStarted', listener)
|
|
219
219
|
|
|
220
|
-
|
|
220
|
+
await s.execute(async () => 'done')
|
|
221
221
|
|
|
222
|
-
|
|
223
|
-
|
|
222
|
+
expect(listener).toBeCalledTimes(1)
|
|
223
|
+
})
|
|
224
224
|
})
|
|
225
225
|
|
|
226
226
|
it('should emit taskCompleted when a task resolves', async () => {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
227
|
+
await usingAsync(new Semaphore(1), async (s) => {
|
|
228
|
+
const listener = vi.fn()
|
|
229
|
+
s.subscribe('taskCompleted', listener)
|
|
230
230
|
|
|
231
|
-
|
|
231
|
+
await s.execute(async () => 'done')
|
|
232
232
|
|
|
233
|
-
|
|
234
|
-
|
|
233
|
+
expect(listener).toBeCalledTimes(1)
|
|
234
|
+
})
|
|
235
235
|
})
|
|
236
236
|
|
|
237
237
|
it('should emit taskFailed with the error when a task rejects', async () => {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
238
|
+
await usingAsync(new Semaphore(1), async (s) => {
|
|
239
|
+
const listener = vi.fn()
|
|
240
|
+
s.subscribe('taskFailed', listener)
|
|
241
|
+
|
|
242
|
+
const taskError = new Error('boom')
|
|
243
|
+
await s
|
|
244
|
+
.execute(async () => {
|
|
245
|
+
throw taskError
|
|
246
|
+
})
|
|
247
|
+
.catch(() => {})
|
|
248
248
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
249
|
+
expect(listener).toBeCalledTimes(1)
|
|
250
|
+
expect(listener).toBeCalledWith({ error: taskError })
|
|
251
|
+
})
|
|
252
252
|
})
|
|
253
253
|
|
|
254
254
|
it('should emit events in correct order for queued tasks', async () => {
|
|
255
|
-
|
|
256
|
-
|
|
255
|
+
await usingAsync(new Semaphore(1), async (s) => {
|
|
256
|
+
const events: string[] = []
|
|
257
257
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
258
|
+
s.subscribe('taskStarted', () => {
|
|
259
|
+
events.push('started')
|
|
260
|
+
})
|
|
261
|
+
s.subscribe('taskCompleted', () => {
|
|
262
|
+
events.push('completed')
|
|
263
|
+
})
|
|
264
264
|
|
|
265
|
-
|
|
265
|
+
await Promise.all([s.execute(async () => 'a'), s.execute(async () => 'b')])
|
|
266
266
|
|
|
267
|
-
|
|
267
|
+
await sleepAsync(10)
|
|
268
268
|
|
|
269
|
-
|
|
270
|
-
|
|
269
|
+
expect(events).toEqual(['started', 'completed', 'started', 'completed'])
|
|
270
|
+
})
|
|
271
271
|
})
|
|
272
272
|
})
|
|
273
273
|
|
|
274
274
|
describe('setMaxConcurrent', () => {
|
|
275
275
|
it('should return the updated value from getMaxConcurrent', () => {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
276
|
+
using(new Semaphore(2), (s) => {
|
|
277
|
+
s.setMaxConcurrent(5)
|
|
278
|
+
expect(s.getMaxConcurrent()).toBe(5)
|
|
279
|
+
})
|
|
280
280
|
})
|
|
281
281
|
|
|
282
282
|
it('should throw when given a non-positive integer', () => {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
283
|
+
using(new Semaphore(2), (s) => {
|
|
284
|
+
expect(() => s.setMaxConcurrent(0)).toThrow('maxConcurrent must be a positive integer')
|
|
285
|
+
expect(() => s.setMaxConcurrent(-1)).toThrow('maxConcurrent must be a positive integer')
|
|
286
|
+
expect(() => s.setMaxConcurrent(1.5)).toThrow('maxConcurrent must be a positive integer')
|
|
287
|
+
})
|
|
288
288
|
})
|
|
289
289
|
|
|
290
290
|
it('should immediately start queued tasks when increased', async () => {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
291
|
+
await usingAsync(new Semaphore(1), async (s) => {
|
|
292
|
+
const running: string[] = []
|
|
293
|
+
const resolvers: Array<() => void> = []
|
|
294
|
+
|
|
295
|
+
const createTask = (name: string) =>
|
|
296
|
+
s.execute(async () => {
|
|
297
|
+
running.push(name)
|
|
298
|
+
await new Promise<void>((resolve) => resolvers.push(resolve))
|
|
299
|
+
return name
|
|
300
|
+
})
|
|
294
301
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
await new Promise<void>((resolve) => resolvers.push(resolve))
|
|
299
|
-
return name
|
|
300
|
-
})
|
|
302
|
+
const p1 = createTask('a')
|
|
303
|
+
const p2 = createTask('b')
|
|
304
|
+
const p3 = createTask('c')
|
|
301
305
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
306
|
+
await sleepAsync(10)
|
|
307
|
+
expect(running).toEqual(['a'])
|
|
308
|
+
expect(s.runningCount.getValue()).toBe(1)
|
|
309
|
+
expect(s.pendingCount.getValue()).toBe(2)
|
|
305
310
|
|
|
306
|
-
|
|
307
|
-
expect(running).toEqual(['a'])
|
|
308
|
-
expect(s.runningCount.getValue()).toBe(1)
|
|
309
|
-
expect(s.pendingCount.getValue()).toBe(2)
|
|
310
|
-
|
|
311
|
-
s.setMaxConcurrent(3)
|
|
311
|
+
s.setMaxConcurrent(3)
|
|
312
312
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
313
|
+
await sleepAsync(10)
|
|
314
|
+
expect(running).toEqual(['a', 'b', 'c'])
|
|
315
|
+
expect(s.runningCount.getValue()).toBe(3)
|
|
316
|
+
expect(s.pendingCount.getValue()).toBe(0)
|
|
317
317
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
318
|
+
resolvers.forEach((r) => r())
|
|
319
|
+
await Promise.all([p1, p2, p3])
|
|
320
|
+
})
|
|
321
321
|
})
|
|
322
322
|
|
|
323
323
|
it('should not abort running tasks when decreased', async () => {
|
|
324
|
-
|
|
325
|
-
|
|
324
|
+
await usingAsync(new Semaphore(3), async (s) => {
|
|
325
|
+
const resolvers: Array<() => void> = []
|
|
326
326
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
327
|
+
const createTask = () =>
|
|
328
|
+
s.execute(async () => {
|
|
329
|
+
await new Promise<void>((resolve) => resolvers.push(resolve))
|
|
330
|
+
})
|
|
331
331
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
332
|
+
const p1 = createTask()
|
|
333
|
+
const p2 = createTask()
|
|
334
|
+
const p3 = createTask()
|
|
335
335
|
|
|
336
|
-
|
|
337
|
-
|
|
336
|
+
await sleepAsync(10)
|
|
337
|
+
expect(s.runningCount.getValue()).toBe(3)
|
|
338
338
|
|
|
339
|
-
|
|
339
|
+
s.setMaxConcurrent(1)
|
|
340
340
|
|
|
341
|
-
|
|
341
|
+
expect(s.runningCount.getValue()).toBe(3)
|
|
342
342
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
343
|
+
resolvers.forEach((r) => r())
|
|
344
|
+
await Promise.all([p1, p2, p3])
|
|
345
|
+
expect(s.completedCount.getValue()).toBe(3)
|
|
346
|
+
})
|
|
347
347
|
})
|
|
348
348
|
|
|
349
349
|
it('should not start new tasks until running count drops below new lower limit', async () => {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
350
|
+
await usingAsync(new Semaphore(2), async (s) => {
|
|
351
|
+
const running: string[] = []
|
|
352
|
+
const resolvers: Array<() => void> = []
|
|
353
|
+
|
|
354
|
+
const createTask = (name: string) =>
|
|
355
|
+
s.execute(async () => {
|
|
356
|
+
running.push(name)
|
|
357
|
+
await new Promise<void>((resolve) => resolvers.push(resolve))
|
|
358
|
+
return name
|
|
359
|
+
})
|
|
360
360
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
361
|
+
const p1 = createTask('a')
|
|
362
|
+
const p2 = createTask('b')
|
|
363
|
+
const p3 = createTask('c')
|
|
364
364
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
365
|
+
await sleepAsync(10)
|
|
366
|
+
expect(running).toEqual(['a', 'b'])
|
|
367
|
+
expect(s.pendingCount.getValue()).toBe(1)
|
|
368
368
|
|
|
369
|
-
|
|
369
|
+
s.setMaxConcurrent(1)
|
|
370
370
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
371
|
+
resolvers[0]()
|
|
372
|
+
await p1
|
|
373
|
+
await sleepAsync(10)
|
|
374
374
|
|
|
375
|
-
|
|
376
|
-
|
|
375
|
+
expect(running).toEqual(['a', 'b'])
|
|
376
|
+
expect(s.pendingCount.getValue()).toBe(1)
|
|
377
377
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
378
|
+
resolvers[1]()
|
|
379
|
+
await p2
|
|
380
|
+
await sleepAsync(10)
|
|
381
381
|
|
|
382
|
-
|
|
383
|
-
|
|
382
|
+
expect(running).toEqual(['a', 'b', 'c'])
|
|
383
|
+
expect(s.pendingCount.getValue()).toBe(0)
|
|
384
384
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
385
|
+
resolvers[2]()
|
|
386
|
+
await p3
|
|
387
|
+
})
|
|
388
388
|
})
|
|
389
389
|
})
|
|
390
390
|
|
|
@@ -460,7 +460,7 @@ export const semaphoreTests = describe('Semaphore', () => {
|
|
|
460
460
|
|
|
461
461
|
s[Symbol.dispose]()
|
|
462
462
|
|
|
463
|
-
s.emit('taskStarted', undefined)
|
|
463
|
+
expect(() => s.emit('taskStarted', undefined)).not.toThrow()
|
|
464
464
|
expect(listener).not.toBeCalled()
|
|
465
465
|
})
|
|
466
466
|
})
|