@@ -99,4 +99,121 @@ describe("RunLocker", () => {
9999 }
100100 }
101101 ) ;
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+ ) ;
102219} ) ;
0 commit comments