1
1
import { Mutex } from "./mutex" ;
2
+ import { sleep } from "../../support/sleep" ;
2
3
import { timeFunction } from "../../support/time-function" ;
3
4
import {
4
5
ConcurrencyExceedTimeoutException ,
7
8
} from "../exceptions" ;
8
9
9
10
describe ( "Mutex" , ( ) => {
11
+ const delay = 50 ;
12
+ const offset = 5 ;
13
+
10
14
describe ( "Input validation" , ( ) => {
11
15
const mutex = new Mutex ( ) ;
12
16
@@ -20,9 +24,6 @@ describe("Mutex", () => {
20
24
} ) ;
21
25
22
26
describe ( "`lock` usage" , ( ) => {
23
- const delay = 50 ;
24
- const offset = 5 ;
25
-
26
27
it ( "should work with a basic usage" , async ( ) => {
27
28
// semaphore as a mutex
28
29
const mutex = new Mutex ( ) ;
@@ -113,9 +114,6 @@ describe("Mutex", () => {
113
114
} ) ;
114
115
115
116
describe ( "`tryLock` usage" , ( ) => {
116
- const delay = 50 ;
117
- const offset = 5 ;
118
-
119
117
it ( "should work with tryLock/unlock" , async ( ) => {
120
118
const mutex = new Mutex ( ) ;
121
119
@@ -152,6 +150,106 @@ describe("Mutex", () => {
152
150
} ) ;
153
151
} ) ;
154
152
153
+ describe ( "`lockWith` usage" , ( ) => {
154
+ it ( "should work like a regular `lock`/`unlock`" , async ( ) => {
155
+ const mutex = new Mutex ( ) ;
156
+
157
+ const [ elapsed ] = await timeFunction ( async ( ) => {
158
+ expect ( mutex . isLocked ) . toBeFalse ( ) ;
159
+ expect ( mutex . queueLength ) . toBe ( 0 ) ;
160
+
161
+ void sleep ( delay / 2 ) . then ( ( ) => {
162
+ // In the first mutex (that locked)
163
+ expect ( mutex . isLocked ) . toBeTrue ( ) ;
164
+ expect ( mutex . queueLength ) . toBe ( 1 ) ;
165
+ } ) ;
166
+
167
+ void sleep ( ( delay / 2 ) * 3 ) . then ( ( ) => {
168
+ // In the second mutex (that locked)
169
+ expect ( mutex . isLocked ) . toBeTrue ( ) ;
170
+ expect ( mutex . queueLength ) . toBe ( 0 ) ;
171
+ } ) ;
172
+
173
+ await Promise . all ( [
174
+ mutex . lockWith ( ( ) => sleep ( delay ) ) ,
175
+ mutex . lockWith ( ( ) => sleep ( delay ) )
176
+ ] ) ;
177
+
178
+ expect ( mutex . isLocked ) . toBeFalse ( ) ;
179
+ expect ( mutex . queueLength ) . toBe ( 0 ) ;
180
+ } ) ;
181
+
182
+ expect ( elapsed ) . toBeGreaterThanOrEqual ( delay * 2 - offset ) ;
183
+ expect ( elapsed ) . toBeLessThanOrEqual ( delay * 2 + offset ) ;
184
+ } ) ;
185
+
186
+ it ( "should return the value from the critical section" , async ( ) => {
187
+ const mutex = new Mutex ( ) ;
188
+
189
+ const [ v1 , v2 , v3 ] = await Promise . all ( [
190
+ sleep ( Math . random ( ) * delay ) . then ( ( ) => mutex . lockWith ( ( ) => 1 ) ) ,
191
+ sleep ( Math . random ( ) * delay ) . then ( ( ) => mutex . lockWith ( ( ) => 2 ) ) ,
192
+ sleep ( Math . random ( ) * delay ) . then ( ( ) => mutex . lockWith ( ( ) => 3 ) )
193
+ ] ) ;
194
+
195
+ expect ( v1 ) . toBe ( 1 ) ;
196
+ expect ( v2 ) . toBe ( 2 ) ;
197
+ expect ( v3 ) . toBe ( 3 ) ;
198
+ } ) ;
199
+ } ) ;
200
+
201
+ describe ( "`tryLockWith` usage" , ( ) => {
202
+ it ( "should work like a regular `tryLock`/`unlock`" , async ( ) => {
203
+ const mutex = new Mutex ( ) ;
204
+
205
+ const [ elapsed ] = await timeFunction ( ( ) =>
206
+ Promise . all ( [
207
+ mutex . tryLockWith ( delay , ( ) => sleep ( delay / 2 ) ) ,
208
+ mutex . tryLockWith ( delay , ( ) => sleep ( delay / 2 ) )
209
+ ] )
210
+ ) ;
211
+
212
+ // each `tryLockWith` keep the lock for delay / 2 time
213
+ expect ( elapsed ) . toBeGreaterThanOrEqual ( delay - offset ) ;
214
+ expect ( elapsed ) . toBeLessThanOrEqual ( delay + offset ) ;
215
+ } ) ;
216
+
217
+ it ( "should thrown an error when the time exceeds" , async ( ) => {
218
+ const mutex = new Mutex ( ) ;
219
+
220
+ await mutex . lock ( ) ;
221
+ expect ( mutex . isLocked ) . toBeTrue ( ) ;
222
+ expect ( mutex . queueLength ) . toBe ( 0 ) ;
223
+
224
+ setTimeout ( ( ) => {
225
+ expect ( mutex . isLocked ) . toBeTrue ( ) ;
226
+ expect ( mutex . queueLength ) . toBe ( 1 ) ;
227
+ } , delay / 2 ) ;
228
+
229
+ await expect ( ( ) => mutex . tryLockWith ( delay , ( ) => 1 ) ) . rejects . toThrow (
230
+ ConcurrencyExceedTimeoutException
231
+ ) ;
232
+ expect ( mutex . isLocked ) . toBeTrue ( ) ;
233
+ expect ( mutex . queueLength ) . toBe ( 0 ) ;
234
+
235
+ mutex . unlock ( ) ;
236
+ } ) ;
237
+
238
+ it ( "should return the value from the critical section" , async ( ) => {
239
+ const mutex = new Mutex ( ) ;
240
+
241
+ const [ v1 , v2 , v3 ] = await Promise . all ( [
242
+ sleep ( Math . random ( ) * delay ) . then ( ( ) => mutex . tryLockWith ( 5 , ( ) => 1 ) ) ,
243
+ sleep ( Math . random ( ) * delay ) . then ( ( ) => mutex . tryLockWith ( 5 , ( ) => 2 ) ) ,
244
+ sleep ( Math . random ( ) * delay ) . then ( ( ) => mutex . tryLockWith ( 5 , ( ) => 3 ) )
245
+ ] ) ;
246
+
247
+ expect ( v1 ) . toBe ( 1 ) ;
248
+ expect ( v2 ) . toBe ( 2 ) ;
249
+ expect ( v3 ) . toBe ( 3 ) ;
250
+ } ) ;
251
+ } ) ;
252
+
155
253
it ( "should interrupt all" , async ( ) => {
156
254
const delay = 100 ;
157
255
const mutex = new Mutex ( ) ;
@@ -162,14 +260,15 @@ describe("Mutex", () => {
162
260
163
261
setTimeout ( ( ) => {
164
262
expect ( mutex . isLocked ) . toBeTrue ( ) ;
165
- expect ( mutex . queueLength ) . toBe ( 3 ) ;
263
+ expect ( mutex . queueLength ) . toBe ( 4 ) ;
166
264
mutex . interrupt ( reason ) ;
167
265
} , delay ) ;
168
266
169
267
const errors = await Promise . all ( [
170
268
mutex . lock ( ) . catch ( ( err : unknown ) => err ) ,
171
269
mutex . tryLock ( delay * 2 ) . catch ( ( err : unknown ) => err ) ,
172
- mutex . lock ( ) . catch ( ( err : unknown ) => err )
270
+ mutex . lockWith ( ( ) => 0 ) . catch ( ( err : unknown ) => err ) ,
271
+ mutex . tryLockWith ( delay * 2 , ( ) => 0 ) . catch ( ( err : unknown ) => err )
173
272
] ) ;
174
273
175
274
for ( const error of errors ) {
0 commit comments