Removing deprecated DMAAP library
[policy/drools-pdp.git] / policy-management / src / test / java / org / onap / policy / drools / system / internal / SimpleLockManagerTest.java
1 /*
2  * ============LICENSE_START=======================================================
3  * ONAP
4  * ================================================================================
5  * Copyright (C) 2019-2021 AT&T Intellectual Property. All rights reserved.
6  * Modifications Copyright (C) 2023-2024 Nordix Foundation.
7  * ================================================================================
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  * ============LICENSE_END=========================================================
20  */
21
22 package org.onap.policy.drools.system.internal;
23
24 import static org.assertj.core.api.Assertions.assertThat;
25 import static org.assertj.core.api.Assertions.assertThatCode;
26 import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
27 import static org.assertj.core.api.Assertions.assertThatThrownBy;
28 import static org.junit.jupiter.api.Assertions.assertEquals;
29 import static org.junit.jupiter.api.Assertions.assertFalse;
30 import static org.junit.jupiter.api.Assertions.assertNotNull;
31 import static org.junit.jupiter.api.Assertions.assertNull;
32 import static org.junit.jupiter.api.Assertions.assertSame;
33 import static org.junit.jupiter.api.Assertions.assertTrue;
34 import static org.mockito.ArgumentMatchers.any;
35 import static org.mockito.ArgumentMatchers.anyBoolean;
36 import static org.mockito.ArgumentMatchers.anyLong;
37 import static org.mockito.Mockito.mock;
38 import static org.mockito.Mockito.never;
39 import static org.mockito.Mockito.times;
40 import static org.mockito.Mockito.verify;
41 import static org.mockito.Mockito.when;
42
43 import java.io.ByteArrayInputStream;
44 import java.io.ByteArrayOutputStream;
45 import java.io.ObjectInputStream;
46 import java.io.ObjectOutputStream;
47 import java.io.Serial;
48 import java.util.ArrayList;
49 import java.util.List;
50 import java.util.Properties;
51 import java.util.concurrent.Executors;
52 import java.util.concurrent.ScheduledExecutorService;
53 import java.util.concurrent.ScheduledFuture;
54 import java.util.concurrent.Semaphore;
55 import java.util.concurrent.TimeUnit;
56 import java.util.concurrent.atomic.AtomicInteger;
57 import org.junit.jupiter.api.AfterAll;
58 import org.junit.jupiter.api.AfterEach;
59 import org.junit.jupiter.api.BeforeAll;
60 import org.junit.jupiter.api.BeforeEach;
61 import org.junit.jupiter.api.Test;
62 import org.junit.jupiter.api.extension.ExtendWith;
63 import org.kie.api.runtime.KieSession;
64 import org.mockito.ArgumentCaptor;
65 import org.mockito.Mock;
66 import org.mockito.MockitoAnnotations;
67 import org.mockito.junit.jupiter.MockitoExtension;
68 import org.onap.policy.common.utils.time.CurrentTime;
69 import org.onap.policy.common.utils.time.TestTime;
70 import org.onap.policy.drools.core.PolicySession;
71 import org.onap.policy.drools.core.lock.Lock;
72 import org.onap.policy.drools.core.lock.LockCallback;
73 import org.onap.policy.drools.core.lock.LockState;
74 import org.onap.policy.drools.system.PolicyEngineConstants;
75 import org.onap.policy.drools.system.internal.SimpleLockManager.SimpleLock;
76 import org.springframework.test.util.ReflectionTestUtils;
77
78 @ExtendWith(MockitoExtension.class)
79 class SimpleLockManagerTest {
80     private static final String POLICY_ENGINE_EXECUTOR_FIELD = "executorService";
81     private static final String TIME_FIELD = "currentTime";
82     private static final String OWNER_KEY = "my key";
83     private static final String RESOURCE = "my resource";
84     private static final String RESOURCE2 = "my resource #2";
85     private static final String RESOURCE3 = "my resource #3";
86     private static final int HOLD_SEC = 100;
87     private static final int HOLD_SEC2 = 120;
88     private static final int HOLD_MS = HOLD_SEC * 1000;
89     private static final int HOLD_MS2 = HOLD_SEC2 * 1000;
90     private static final int MAX_THREADS = 10;
91     private static final int MAX_LOOPS = 50;
92
93     private static CurrentTime saveTime;
94     private static ScheduledExecutorService saveExec;
95     private static ScheduledExecutorService realExec;
96
97     private TestTime testTime;
98     private AtomicInteger nactive;
99     private AtomicInteger nsuccesses;
100     private SimpleLockManager feature;
101
102     @Mock
103     private KieSession kieSess;
104
105     @Mock
106     private ScheduledExecutorService exsvc;
107
108     @Mock
109     private ScheduledFuture<?> future;
110
111     @Mock
112     private LockCallback callback;
113
114     AutoCloseable closeable;
115
116     /**
117      * Saves static fields and configures the location of the property files.
118      */
119     @BeforeAll
120     static void setUpBeforeClass() {
121         saveTime = (CurrentTime) ReflectionTestUtils.getField(SimpleLockManager.class, TIME_FIELD);
122         saveExec = (ScheduledExecutorService) ReflectionTestUtils.getField(PolicyEngineConstants.getManager(),
123             POLICY_ENGINE_EXECUTOR_FIELD);
124
125         realExec = Executors.newScheduledThreadPool(3);
126     }
127
128     /**
129      * Restores static fields.
130      */
131     @AfterAll
132     static void tearDownAfterClass() {
133         ReflectionTestUtils.setField(SimpleLockManager.class, TIME_FIELD, saveTime);
134         ReflectionTestUtils.setField(PolicyEngineConstants.getManager(), POLICY_ENGINE_EXECUTOR_FIELD, saveExec);
135
136         realExec.shutdown();
137     }
138
139     /**
140      * Initializes the mocks and creates a feature that uses {@link #exsvc} to execute
141      * tasks.
142      */
143     @BeforeEach
144     void setUp() {
145         closeable = MockitoAnnotations.openMocks(this);
146         // grant() and deny() calls will come through here and be immediately executed
147         PolicySession session = new PolicySession(null, null, kieSess) {
148             @Override
149             public void insertDrools(Object object) {
150                 ((Runnable) object).run();
151             }
152         };
153
154         session.setPolicySession();
155
156         testTime = new TestTime();
157         nactive = new AtomicInteger(0);
158         nsuccesses = new AtomicInteger(0);
159
160         ReflectionTestUtils.setField(SimpleLockManager.class, TIME_FIELD, testTime);
161
162         ReflectionTestUtils.setField(PolicyEngineConstants.getManager(), POLICY_ENGINE_EXECUTOR_FIELD, exsvc);
163
164         feature = new MyLockingFeature();
165         feature.start();
166     }
167
168     @AfterEach
169     void closeMocks() throws Exception {
170         closeable.close();
171     }
172
173     /**
174      * Tests constructor() when properties are invalid.
175      */
176     @Test
177     void testSimpleLockManagerInvalidProperties() {
178         // use properties containing an invalid value
179         Properties props = new Properties();
180         props.setProperty(SimpleLockProperties.EXPIRE_CHECK_SEC, "abc");
181
182         assertThatThrownBy(() -> new MyLockingFeature(props)).isInstanceOf(SimpleLockManagerException.class);
183     }
184
185     @Test
186     void testStart() {
187         assertTrue(feature.isAlive());
188         verify(exsvc).scheduleWithFixedDelay(any(), anyLong(), anyLong(), any());
189
190         assertFalse(feature.start());
191
192         feature.stop();
193         assertTrue(feature.start());
194     }
195
196     @Test
197     void testStop() {
198         assertTrue(feature.stop());
199         assertFalse(feature.isAlive());
200         verify(future).cancel(true);
201
202         assertFalse(feature.stop());
203
204         // no more invocations
205         verify(future).cancel(anyBoolean());
206     }
207
208     @Test
209     void testShutdown() {
210         feature.shutdown();
211
212         verify(future).cancel(true);
213     }
214
215     @Test
216     void testCreateLock() {
217         // this lock should be granted immediately
218         SimpleLock lock = getLock(RESOURCE, HOLD_SEC, callback);
219         assertTrue(lock.isActive());
220         assertEquals(testTime.getMillis() + HOLD_MS, lock.getHoldUntilMs());
221
222         verify(callback).lockAvailable(lock);
223         verify(callback, never()).lockUnavailable(lock);
224
225         // this time it should be busy
226         Lock lock2 = feature.createLock(RESOURCE, OWNER_KEY, HOLD_SEC, callback, false);
227         assertFalse(lock2.isActive());
228         assertTrue(lock2.isUnavailable());
229
230         verify(callback, never()).lockAvailable(lock2);
231         verify(callback).lockUnavailable(lock2);
232
233         // should have been no change to the original lock
234         assertTrue(lock.isActive());
235         verify(callback).lockAvailable(lock);
236         verify(callback, never()).lockUnavailable(lock);
237
238         // should work with "true" value also
239         Lock lock3 = feature.createLock(RESOURCE2, OWNER_KEY, HOLD_SEC, callback, true);
240         assertTrue(lock3.isActive());
241         verify(callback).lockAvailable(lock3);
242         verify(callback, never()).lockUnavailable(lock3);
243     }
244
245     /**
246      * Tests createLock() when the feature is not the latest instance.
247      */
248     @Test
249     void testCreateLockNotLatestInstance() {
250         SimpleLockManager.setLatestInstance(null);
251
252         Lock lock = feature.createLock(RESOURCE, OWNER_KEY, HOLD_SEC, callback, false);
253         assertTrue(lock.isUnavailable());
254         verify(callback, never()).lockAvailable(any());
255         verify(callback).lockUnavailable(lock);
256     }
257
258     @Test
259     void testCheckExpired() throws InterruptedException {
260         final SimpleLock lock = getLock(RESOURCE, HOLD_SEC, callback);
261         final SimpleLock lock2 = getLock(RESOURCE2, HOLD_SEC, callback);
262         final SimpleLock lock3 = getLock(RESOURCE3, HOLD_SEC2, callback);
263
264         ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
265         verify(exsvc).scheduleWithFixedDelay(captor.capture(), anyLong(), anyLong(), any());
266
267         Runnable checker = captor.getValue();
268
269         // time unchanged - checker should have no impact
270         checker.run();
271         assertTrue(lock.isActive());
272         assertTrue(lock2.isActive());
273         assertTrue(lock3.isActive());
274
275         // expire the first two locks
276         testTime.sleep(HOLD_MS);
277         checker.run();
278         assertFalse(lock.isActive());
279         assertFalse(lock2.isActive());
280         assertTrue(lock3.isActive());
281
282         verify(callback).lockUnavailable(lock);
283         verify(callback).lockUnavailable(lock2);
284         verify(callback, never()).lockUnavailable(lock3);
285
286         // should be able to get a lock on the first two resources
287         assertTrue(feature.createLock(RESOURCE, OWNER_KEY, HOLD_SEC + HOLD_SEC2, callback, false).isActive());
288         assertTrue(feature.createLock(RESOURCE2, OWNER_KEY, HOLD_SEC + HOLD_SEC2, callback, false).isActive());
289
290         // lock is still busy on the last resource
291         assertFalse(feature.createLock(RESOURCE3, OWNER_KEY, HOLD_SEC + HOLD_SEC2, callback, false).isActive());
292
293         // expire the last lock
294         testTime.sleep(HOLD_MS2);
295         checker.run();
296         assertFalse(lock3.isActive());
297
298         verify(callback).lockUnavailable(lock3);
299     }
300
301     /**
302      * Tests checkExpired(), where the lock is removed from the map between invoking
303      * expired() and compute(). Should cause "null" to be returned by compute().
304      *
305      */
306     @Test
307     void testCheckExpiredLockDeleted() {
308         feature = new MyLockingFeature() {
309             @Override
310             protected SimpleLock makeLock(LockState waiting, String resourceId, String ownerKey, int holdSec,
311                 LockCallback callback) {
312                 return new SimpleLock(waiting, resourceId, ownerKey, holdSec, callback, feature) {
313                     @Serial
314                     private static final long serialVersionUID = 1L;
315
316                     @Override
317                     public boolean expired(long currentMs) {
318                         // remove the lock from the map
319                         free();
320                         return true;
321                     }
322                 };
323             }
324         };
325
326         feature.start();
327
328         feature.createLock(RESOURCE, OWNER_KEY, HOLD_SEC, callback, false);
329
330         ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
331         verify(exsvc).scheduleWithFixedDelay(captor.capture(), anyLong(), anyLong(), any());
332
333         Runnable checker = captor.getValue();
334
335         checker.run();
336
337         // lock should now be gone and we should be able to get another
338         feature.createLock(RESOURCE, OWNER_KEY, HOLD_SEC, callback, false);
339
340         // should have succeeded twice
341         verify(callback, times(2)).lockAvailable(any());
342
343         // lock should not be available now
344         feature.createLock(RESOURCE, OWNER_KEY, HOLD_SEC, callback, false);
345         verify(callback).lockUnavailable(any());
346     }
347
348     /**
349      * Tests checkExpired(), where the lock is removed from the map and replaced with a
350      * new lock, between invoking expired() and compute(). Should cause the new lock to be
351      * returned.
352      *
353      * @throws InterruptedException if the test is interrupted
354      */
355     @Test
356     void testCheckExpiredLockReplaced() throws InterruptedException {
357         feature = new MyLockingFeature() {
358             private boolean madeLock = false;
359
360             @Override
361             protected SimpleLock makeLock(LockState waiting, String resourceId, String ownerKey, int holdSec,
362                 LockCallback callback) {
363                 if (madeLock) {
364                     return new SimpleLock(waiting, resourceId, ownerKey, holdSec, callback, feature);
365                 }
366
367                 madeLock = true;
368
369                 return new SimpleLock(waiting, resourceId, ownerKey, holdSec, callback, feature) {
370                     @Serial
371                     private static final long serialVersionUID = 1L;
372
373                     @Override
374                     public boolean expired(long currentMs) {
375                         // remove the lock from the map and add a new lock
376                         free();
377                         feature.createLock(RESOURCE, OWNER_KEY, HOLD_SEC, callback, false);
378
379                         return true;
380                     }
381                 };
382             }
383         };
384
385         feature.start();
386
387         feature.createLock(RESOURCE, OWNER_KEY, HOLD_SEC, callback, false);
388
389         ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
390         verify(exsvc).scheduleWithFixedDelay(captor.capture(), anyLong(), anyLong(), any());
391
392         Runnable checker = captor.getValue();
393
394         checker.run();
395
396         // lock should not be available now
397         feature.createLock(RESOURCE, OWNER_KEY, HOLD_SEC, callback, false);
398         verify(callback).lockUnavailable(any());
399     }
400
401     @Test
402     void testGetThreadPool() {
403         // use a real feature
404         feature = new SimpleLockManager(null, new Properties());
405
406         // load properties
407         feature.start();
408
409         // should create thread pool
410         feature.createLock(RESOURCE, OWNER_KEY, HOLD_SEC, callback, false);
411
412         // should shut down thread pool
413         assertThatCode(() -> feature.stop()).doesNotThrowAnyException();
414     }
415
416     @Test
417     void testSimpleLockNoArgs() {
418         SimpleLock lock = new SimpleLock();
419         assertNull(lock.getResourceId());
420         assertNull(lock.getOwnerKey());
421         assertNull(lock.getCallback());
422         assertEquals(0, lock.getHoldSec());
423
424         assertEquals(0, lock.getHoldUntilMs());
425     }
426
427     @Test
428     void testSimpleLockSimpleLock() {
429         SimpleLock lock = getLock(RESOURCE, HOLD_SEC, callback);
430         assertEquals(RESOURCE, lock.getResourceId());
431         assertEquals(OWNER_KEY, lock.getOwnerKey());
432         assertSame(callback, lock.getCallback());
433         assertEquals(HOLD_SEC, lock.getHoldSec());
434
435         assertThatIllegalArgumentException()
436             .isThrownBy(() -> feature.createLock(RESOURCE, OWNER_KEY, -1, callback, false))
437             .withMessageContaining("holdSec");
438     }
439
440     @Test
441     void testSimpleLockSerializable() throws Exception {
442         SimpleLock lock = getLock(RESOURCE, HOLD_SEC, callback);
443         lock = roundTrip(lock);
444
445         assertTrue(lock.isActive());
446
447         assertEquals(RESOURCE, lock.getResourceId());
448         assertEquals(OWNER_KEY, lock.getOwnerKey());
449         assertNull(lock.getCallback());
450         assertEquals(HOLD_SEC, lock.getHoldSec());
451     }
452
453     @Test
454     void testSimpleLockExpired() {
455         SimpleLock lock = getLock(RESOURCE, HOLD_SEC, callback);
456         lock.grant();
457
458         assertFalse(lock.expired(testTime.getMillis()));
459         assertFalse(lock.expired(testTime.getMillis() + HOLD_MS - 1));
460         assertTrue(lock.expired(testTime.getMillis() + HOLD_MS));
461     }
462
463     @Test
464     void testSimpleLockFree() {
465         final SimpleLock lock = getLock(RESOURCE, HOLD_SEC, callback);
466
467         // lock2 should be denied
468         SimpleLock lock2 = getLock(RESOURCE, HOLD_SEC, callback);
469         verify(callback, never()).lockAvailable(lock2);
470         verify(callback).lockUnavailable(lock2);
471
472         // lock2 was denied, so nothing new should happen when freed
473         assertFalse(lock2.free());
474
475         // force lock2 to be active - still nothing should happen
476         ReflectionTestUtils.setField(lock2, "state", LockState.ACTIVE);
477         assertFalse(lock2.free());
478
479         // now free the first lock
480         assertTrue(lock.free());
481         assertEquals(LockState.UNAVAILABLE, lock.getState());
482
483         // should be able to get the lock now
484         SimpleLock lock3 = getLock(RESOURCE, HOLD_SEC, callback);
485         assertTrue(lock3.isActive());
486
487         verify(callback).lockAvailable(lock3);
488         verify(callback, never()).lockUnavailable(lock3);
489     }
490
491     /**
492      * Tests that free() works on a serialized lock with a new feature.
493      *
494      * @throws Exception if an error occurs
495      */
496     @Test
497     void testSimpleLockFreeSerialized() throws Exception {
498         SimpleLock lock = getLock(RESOURCE, HOLD_SEC, callback);
499
500         feature = new MyLockingFeature();
501         feature.start();
502
503         lock = roundTrip(lock);
504         assertTrue(lock.free());
505         assertTrue(lock.isUnavailable());
506     }
507
508     @Test
509     void testSimpleLockExtend() {
510         final SimpleLock lock = getLock(RESOURCE, HOLD_SEC, callback);
511
512         // lock2 should be denied
513         SimpleLock lock2 = getLock(RESOURCE, HOLD_SEC, callback);
514         verify(callback, never()).lockAvailable(lock2);
515         verify(callback).lockUnavailable(lock2);
516
517         // lock2 will still be denied
518         lock2.extend(HOLD_SEC, callback);
519         verify(callback, times(2)).lockUnavailable(lock2);
520
521         // force lock2 to be active - should still be denied
522         ReflectionTestUtils.setField(lock2, "state", LockState.ACTIVE);
523         lock2.extend(HOLD_SEC, callback);
524         verify(callback, times(3)).lockUnavailable(lock2);
525
526         assertThatIllegalArgumentException().isThrownBy(() -> lock.extend(-1, callback))
527             .withMessageContaining("holdSec");
528
529         // now extend the first lock
530         lock.extend(HOLD_SEC2, callback);
531         assertEquals(HOLD_SEC2, lock.getHoldSec());
532         assertEquals(testTime.getMillis() + HOLD_MS2, lock.getHoldUntilMs());
533         verify(callback, times(2)).lockAvailable(lock);
534         verify(callback, never()).lockUnavailable(lock);
535     }
536
537     /**
538      * Tests that extend() works on a serialized lock with a new feature.
539      *
540      * @throws Exception if an error occurs
541      */
542     @Test
543     void testSimpleLockExtendSerialized() throws Exception {
544         SimpleLock lock = getLock(RESOURCE, HOLD_SEC, callback);
545
546         feature = new MyLockingFeature();
547         feature.start();
548
549         lock = roundTrip(lock);
550         LockCallback scallback = mock(LockCallback.class);
551
552         lock.extend(HOLD_SEC, scallback);
553         assertTrue(lock.isActive());
554
555         verify(scallback).lockAvailable(lock);
556         verify(scallback, never()).lockUnavailable(lock);
557     }
558
559     /**
560      * Tests that extend() fails when there is no feature.
561      *
562      * @throws Exception if an error occurs
563      */
564     @Test
565     void testSimpleLockExtendNoFeature() throws Exception {
566         SimpleLock lock = getLock(RESOURCE, HOLD_SEC, callback);
567
568         SimpleLockManager.setLatestInstance(null);
569
570         lock = roundTrip(lock);
571         LockCallback scallback = mock(LockCallback.class);
572
573         lock.extend(HOLD_SEC, scallback);
574         assertTrue(lock.isUnavailable());
575
576         verify(scallback, never()).lockAvailable(lock);
577         verify(scallback).lockUnavailable(lock);
578     }
579
580     @Test
581     void testSimpleLockToString() {
582         String text = feature.createLock(RESOURCE, OWNER_KEY, HOLD_SEC, callback, false).toString();
583         assertNotNull(text);
584         assertThat(text).contains("holdUntil").doesNotContain("ownerInfo").doesNotContain("callback");
585     }
586
587     /**
588      * Performs a multi-threaded test of the locking facility.
589      *
590      * @throws InterruptedException if the current thread is interrupted while waiting for
591      *         the background threads to complete
592      */
593     @Test
594     void testMultiThreaded() throws InterruptedException {
595         ReflectionTestUtils.setField(SimpleLockManager.class, TIME_FIELD, testTime);
596         ReflectionTestUtils.setField(PolicyEngineConstants.getManager(), POLICY_ENGINE_EXECUTOR_FIELD, realExec);
597         feature = new SimpleLockManager(null, new Properties());
598         feature.start();
599
600         List<MyThread> threads = new ArrayList<>(MAX_THREADS);
601         for (int x = 0; x < MAX_THREADS; ++x) {
602             threads.add(new MyThread());
603         }
604
605         threads.forEach(Thread::start);
606
607         for (MyThread thread : threads) {
608             thread.join(6000);
609             assertFalse(thread.isAlive());
610         }
611
612         for (MyThread thread : threads) {
613             if (thread.err != null) {
614                 throw thread.err;
615             }
616         }
617
618         assertTrue(nsuccesses.get() > 0);
619     }
620
621     private SimpleLock getLock(String resource, int holdSec, LockCallback callback) {
622         return (SimpleLock) feature.createLock(resource, SimpleLockManagerTest.OWNER_KEY, holdSec, callback, false);
623     }
624
625     private SimpleLock roundTrip(SimpleLock lock) throws Exception {
626         ByteArrayOutputStream baos = new ByteArrayOutputStream();
627         try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
628             oos.writeObject(lock);
629         }
630
631         ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
632         try (ObjectInputStream ois = new ObjectInputStream(bais)) {
633             return (SimpleLock) ois.readObject();
634         }
635     }
636
637     /**
638      * Feature that uses <i>exsvc</i> to execute requests.
639      */
640     private class MyLockingFeature extends SimpleLockManager {
641
642         public MyLockingFeature() {
643             this(new Properties());
644         }
645
646         public MyLockingFeature(Properties props) {
647             super(null, props);
648
649             exsvc = mock(ScheduledExecutorService.class);
650             ReflectionTestUtils.setField(PolicyEngineConstants.getManager(), POLICY_ENGINE_EXECUTOR_FIELD, exsvc);
651
652             when(exsvc.scheduleWithFixedDelay(any(), anyLong(), anyLong(), any())).thenAnswer(answer -> {
653                 return future;
654             });
655         }
656     }
657
658     /**
659      * Thread used with the multithreaded test. It repeatedly attempts to get a lock,
660      * extend it, and then unlock it.
661      */
662     private class MyThread extends Thread {
663         AssertionError err = null;
664
665         public MyThread() {
666             setDaemon(true);
667         }
668
669         @Override
670         public void run() {
671             try {
672                 for (int x = 0; x < MAX_LOOPS; ++x) {
673                     makeAttempt();
674                 }
675
676             } catch (AssertionError e) {
677                 err = e;
678             }
679         }
680
681         private void makeAttempt() {
682             try {
683                 Semaphore sem = new Semaphore(0);
684
685                 LockCallback cb = new LockCallback() {
686                     @Override
687                     public void lockAvailable(Lock lock) {
688                         sem.release();
689                     }
690
691                     @Override
692                     public void lockUnavailable(Lock lock) {
693                         sem.release();
694                     }
695                 };
696
697                 Lock lock = feature.createLock(RESOURCE, getName(), HOLD_SEC, cb, false);
698
699                 // wait for callback, whether available or unavailable
700                 assertTrue(sem.tryAcquire(5, TimeUnit.SECONDS));
701                 if (!lock.isActive()) {
702                     return;
703                 }
704
705                 nsuccesses.incrementAndGet();
706
707                 assertEquals(1, nactive.incrementAndGet());
708
709                 lock.extend(HOLD_SEC2, cb);
710                 assertTrue(sem.tryAcquire(5, TimeUnit.SECONDS));
711                 assertTrue(lock.isActive());
712
713                 // decrement BEFORE free()
714                 nactive.decrementAndGet();
715
716                 assertTrue(lock.free());
717                 assertTrue(lock.isUnavailable());
718
719             } catch (InterruptedException e) {
720                 Thread.currentThread().interrupt();
721                 throw new AssertionError("interrupted", e);
722             }
723         }
724     }
725 }