@@ -99,4 +99,121 @@ describe("RunLocker", () => {
99
99
}
100
100
}
101
101
) ;
102
+
103
+ redisTest ( "Test lock throws when it times out" , { timeout : 15_000 } , async ( { redisOptions } ) => {
104
+ const redis = createRedisClient ( redisOptions ) ;
105
+ try {
106
+ const runLock = new RunLocker ( { redis } ) ;
107
+
108
+ // First, ensure we can acquire the lock normally
109
+ let firstLockAcquired = false ;
110
+ await runLock . lock ( [ "test-1" ] , 5000 , async ( ) => {
111
+ firstLockAcquired = true ;
112
+ } ) ;
113
+ //wait for 20ms
114
+ await new Promise ( ( resolve ) => setTimeout ( resolve , 20 ) ) ;
115
+
116
+ expect ( firstLockAcquired ) . toBe ( true ) ;
117
+
118
+ // Now create a long-running lock
119
+ const lockPromise1 = runLock . lock ( [ "test-1" ] , 5000 , async ( ) => {
120
+ // Hold the lock longer than all possible retry attempts
121
+ // (10 retries * (200ms delay + 200ms max jitter) = ~4000ms max)
122
+ await new Promise ( ( resolve ) => setTimeout ( resolve , 5000 ) ) ;
123
+ } ) ;
124
+
125
+ // Try to acquire same lock immediately
126
+ await expect (
127
+ runLock . lock ( [ "test-1" ] , 5000 , async ( ) => {
128
+ // This should never execute
129
+ expect ( true ) . toBe ( false ) ;
130
+ } )
131
+ ) . rejects . toThrow ( "unable to achieve a quorum" ) ;
132
+
133
+ // Complete the first lock
134
+ await lockPromise1 ;
135
+
136
+ // Verify final state
137
+ expect ( runLock . isInsideLock ( ) ) . toBe ( false ) ;
138
+ } finally {
139
+ await redis . quit ( ) ;
140
+ }
141
+ } ) ;
142
+
143
+ redisTest (
144
+ "Test nested lock with same resources doesn't timeout" ,
145
+ { timeout : 15_000 } ,
146
+ async ( { redisOptions } ) => {
147
+ const redis = createRedisClient ( redisOptions ) ;
148
+ try {
149
+ const runLock = new RunLocker ( { redis } ) ;
150
+
151
+ await runLock . lock ( [ "test-1" ] , 5000 , async ( ) => {
152
+ // First lock acquired
153
+ expect ( runLock . isInsideLock ( ) ) . toBe ( true ) ;
154
+
155
+ // Try to acquire the same resource with a very short timeout
156
+ // This should work because we already hold the lock
157
+ await runLock . lock ( [ "test-1" ] , 100 , async ( ) => {
158
+ expect ( runLock . isInsideLock ( ) ) . toBe ( true ) ;
159
+ // Wait longer than the timeout to prove it doesn't matter
160
+ await new Promise ( ( resolve ) => setTimeout ( resolve , 500 ) ) ;
161
+ } ) ;
162
+ } ) ;
163
+
164
+ // Verify final state
165
+ expect ( runLock . isInsideLock ( ) ) . toBe ( false ) ;
166
+ } finally {
167
+ await redis . quit ( ) ;
168
+ }
169
+ }
170
+ ) ;
171
+
172
+ redisTest (
173
+ "Test nested lock with same resource works regardless of retries" ,
174
+ { timeout : 15_000 } ,
175
+ async ( { redisOptions } ) => {
176
+ const redis = createRedisClient ( redisOptions ) ;
177
+ try {
178
+ const runLock = new RunLocker ( { redis } ) ;
179
+
180
+ // First verify we can acquire the lock normally
181
+ let firstLockAcquired = false ;
182
+ await runLock . lock ( [ "test-1" ] , 5000 , async ( ) => {
183
+ firstLockAcquired = true ;
184
+ } ) ;
185
+ expect ( firstLockAcquired ) . toBe ( true ) ;
186
+
187
+ // Now test the nested lock behavior
188
+ let outerLockExecuted = false ;
189
+ let innerLockExecuted = false ;
190
+
191
+ await runLock . lock ( [ "test-1" ] , 5000 , async ( ) => {
192
+ outerLockExecuted = true ;
193
+ expect ( runLock . isInsideLock ( ) ) . toBe ( true ) ;
194
+ expect ( runLock . getCurrentResources ( ) ) . toBe ( "test-1" ) ;
195
+
196
+ // Try to acquire the same resource in a nested lock
197
+ // This should work immediately without any retries
198
+ // because we already hold the lock
199
+ await runLock . lock ( [ "test-1" ] , 5000 , async ( ) => {
200
+ innerLockExecuted = true ;
201
+ expect ( runLock . isInsideLock ( ) ) . toBe ( true ) ;
202
+ expect ( runLock . getCurrentResources ( ) ) . toBe ( "test-1" ) ;
203
+
204
+ // Sleep longer than retry attempts would take
205
+ // (10 retries * (200ms delay + 200ms max jitter) = ~4000ms max)
206
+ await new Promise ( ( resolve ) => setTimeout ( resolve , 5000 ) ) ;
207
+ } ) ;
208
+ } ) ;
209
+
210
+ // Verify both locks executed
211
+ expect ( outerLockExecuted ) . toBe ( true ) ;
212
+ expect ( innerLockExecuted ) . toBe ( true ) ;
213
+ expect ( runLock . isInsideLock ( ) ) . toBe ( false ) ;
214
+ } finally {
215
+ await redis . quit ( ) ;
216
+ }
217
+ }
218
+ ) ;
102
219
} ) ;
0 commit comments