View Javadoc
1   /*
2    * Licensed under the GPL License. You may not use this file except in compliance with the License.
3    * You may obtain a copy of the License at
4    *
5    *   https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
6    *
7    * THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
8    * WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
9    * PURPOSE.
10   */
11  package psiprobe.tools;
12  
13  import static org.junit.jupiter.api.Assertions.assertFalse;
14  import static org.junit.jupiter.api.Assertions.assertTrue;
15  
16  import java.util.concurrent.CountDownLatch;
17  import java.util.concurrent.atomic.AtomicBoolean;
18  
19  import org.junit.jupiter.api.Test;
20  
21  /**
22   * Tests for {@link UpdateCommitLock}.
23   */
24  class UpdateCommitLockTest {
25  
26    @Test
27    void testLockAndReleaseUpdate() throws InterruptedException {
28      UpdateCommitLock lock = new UpdateCommitLock();
29      lock.lockForUpdate();
30      lock.releaseUpdateLock();
31      // No exception means basic lock/release works
32    }
33  
34    @Test
35    void testLockAndReleaseCommit() throws InterruptedException {
36      UpdateCommitLock lock = new UpdateCommitLock();
37      lock.lockForCommit();
38      lock.releaseCommitLock();
39    }
40  
41    @Test
42    void testMultipleUpdatesCanProceedConcurrently() throws InterruptedException {
43      UpdateCommitLock lock = new UpdateCommitLock();
44      CountDownLatch startLatch = new CountDownLatch(1);
45      CountDownLatch bothLocked = new CountDownLatch(2);
46      AtomicBoolean bothAcquired = new AtomicBoolean(false);
47  
48      Runnable task = () -> {
49        try {
50          startLatch.await();
51          lock.lockForUpdate();
52          bothLocked.countDown();
53          bothLocked.await();
54          bothAcquired.set(true);
55          lock.releaseUpdateLock();
56        } catch (InterruptedException e) {
57          Thread.currentThread().interrupt();
58        }
59      };
60  
61      Thread t1 = new Thread(task);
62      Thread t2 = new Thread(task);
63      t1.start();
64      t2.start();
65  
66      startLatch.countDown();
67      t1.join(3000);
68      t2.join(3000);
69  
70      assertTrue(bothAcquired.get(), "Both threads should acquire update lock simultaneously");
71    }
72  
73    @Test
74    void testCommitWaitsForUpdate() throws InterruptedException {
75      UpdateCommitLock lock = new UpdateCommitLock();
76      lock.lockForUpdate(); // hold an update lock
77  
78      AtomicBoolean commitAcquired = new AtomicBoolean(false);
79      CountDownLatch commitStarted = new CountDownLatch(1);
80  
81      Thread commitThread = new Thread(() -> {
82        try {
83          commitStarted.countDown();
84          lock.lockForCommit();
85          commitAcquired.set(true);
86          lock.releaseCommitLock();
87        } catch (InterruptedException e) {
88          Thread.currentThread().interrupt();
89        }
90      });
91      commitThread.start();
92  
93      commitStarted.await();
94      // Give the commit thread time to block
95      Thread.sleep(100);
96      assertFalse(commitAcquired.get(), "Commit should be waiting for update lock");
97  
98      lock.releaseUpdateLock();
99      commitThread.join(3000);
100     assertTrue(commitAcquired.get(), "Commit should have acquired lock after update released");
101   }
102 
103   @Test
104   void testUpdateWaitsForCommit() throws InterruptedException {
105     UpdateCommitLock lock = new UpdateCommitLock();
106     lock.lockForCommit(); // hold commit lock
107 
108     AtomicBoolean updateAcquired = new AtomicBoolean(false);
109     CountDownLatch updateStarted = new CountDownLatch(1);
110 
111     Thread updateThread = new Thread(() -> {
112       try {
113         updateStarted.countDown();
114         lock.lockForUpdate();
115         updateAcquired.set(true);
116         lock.releaseUpdateLock();
117       } catch (InterruptedException e) {
118         Thread.currentThread().interrupt();
119       }
120     });
121     updateThread.start();
122 
123     updateStarted.await();
124     Thread.sleep(100);
125     assertFalse(updateAcquired.get(), "Update should wait for commit to finish");
126 
127     lock.releaseCommitLock();
128     updateThread.join(3000);
129     assertTrue(updateAcquired.get(), "Update should have acquired lock after commit released");
130   }
131 
132   @Test
133   void testUpdateWaitsForCommitRequest() throws InterruptedException {
134     UpdateCommitLock lock = new UpdateCommitLock();
135     // Start a commit request that will block
136     lock.lockForUpdate();
137 
138     CountDownLatch commitRequesting = new CountDownLatch(1);
139     AtomicBoolean updateAcquiredAfterCommitRequest = new AtomicBoolean(false);
140 
141     Thread commitRequestThread = new Thread(() -> {
142       try {
143         commitRequesting.countDown();
144         lock.lockForCommit();
145         Thread.sleep(200);
146         lock.releaseCommitLock();
147       } catch (InterruptedException e) {
148         Thread.currentThread().interrupt();
149       }
150     });
151     commitRequestThread.start();
152 
153     commitRequesting.await();
154     Thread.sleep(50);
155     lock.releaseUpdateLock(); // let commit through
156 
157     Thread updateAfterCommitReq = new Thread(() -> {
158       try {
159         lock.lockForUpdate();
160         updateAcquiredAfterCommitRequest.set(true);
161         lock.releaseUpdateLock();
162       } catch (InterruptedException e) {
163         Thread.currentThread().interrupt();
164       }
165     });
166 
167     // Wait for commit to finish then update should work
168     commitRequestThread.join(3000);
169     updateAfterCommitReq.start();
170     updateAfterCommitReq.join(3000);
171 
172     assertTrue(updateAcquiredAfterCommitRequest.get());
173   }
174 }