View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.eclipse.aether.named;
20  
21  import java.util.concurrent.CountDownLatch;
22  import java.util.concurrent.TimeUnit;
23  
24  import org.eclipse.aether.named.support.LockUpgradeNotSupportedException;
25  import org.junit.jupiter.api.Test;
26  import org.junit.jupiter.api.TestInfo;
27  import org.junit.jupiter.api.Timeout;
28  
29  import static org.junit.jupiter.api.Assertions.*;
30  
31  /**
32   * UT support for {@link NamedLockFactory}.
33   */
34  public abstract class NamedLockFactoryTestSupport {
35  
36      protected static NamedLockFactory namedLockFactory;
37  
38      protected String lockName(TestInfo testInfo) {
39          return testInfo.getDisplayName();
40      }
41  
42      @Test
43      void testFailure(TestInfo testInfo) throws InterruptedException {
44          // note: set system property "aether.named.diagnostic.enabled" to "true" to have log output
45          // this test does NOT assert its presence, only the proper flow
46          assertThrows(IllegalStateException.class, () -> {
47              Thread t1 = new Thread(() -> {
48                  try {
49                      namedLockFactory.getLock(lockName(testInfo)).lockShared(1L, TimeUnit.MINUTES);
50                      namedLockFactory.getLock(lockName(testInfo)).lockShared(1L, TimeUnit.MINUTES);
51                  } catch (InterruptedException e) {
52                      throw new RuntimeException(e);
53                  }
54              });
55              Thread t2 = new Thread(() -> {
56                  try {
57                      namedLockFactory.getLock(lockName(testInfo)).lockShared(1L, TimeUnit.MINUTES);
58                      namedLockFactory.getLock(lockName(testInfo)).lockShared(1L, TimeUnit.MINUTES);
59                      namedLockFactory.getLock(lockName(testInfo)).lockShared(1L, TimeUnit.MINUTES);
60                  } catch (InterruptedException e) {
61                      throw new RuntimeException(e);
62                  }
63              });
64              t1.start();
65              t2.start();
66              t1.join();
67              t2.join();
68              throw namedLockFactory.onFailure(new IllegalStateException("failure"));
69          });
70      }
71  
72      @Test
73      void refCounting(TestInfo testInfo) {
74          final String name = lockName(testInfo);
75          try (NamedLock one = namedLockFactory.getLock(name);
76                  NamedLock two = namedLockFactory.getLock(name)) {
77              assertSame(one, two);
78              one.close();
79              two.close();
80  
81              try (NamedLock three = namedLockFactory.getLock(name)) {
82                  assertNotSame(three, two);
83              }
84          }
85      }
86  
87      @Test
88      void unlockWoLock(TestInfo testInfo) {
89          assertThrows(IllegalStateException.class, () -> {
90              final String name = lockName(testInfo);
91              try (NamedLock one = namedLockFactory.getLock(name)) {
92                  one.unlock();
93              }
94          });
95      }
96  
97      @Test
98      void wwBoxing(TestInfo testInfo) throws InterruptedException {
99          final String name = lockName(testInfo);
100         try (NamedLock one = namedLockFactory.getLock(name)) {
101             assertTrue(one.lockExclusively(1L, TimeUnit.MILLISECONDS));
102             assertTrue(one.lockExclusively(1L, TimeUnit.MILLISECONDS));
103             one.unlock();
104             one.unlock();
105         }
106     }
107 
108     @Test
109     void rrBoxing(TestInfo testInfo) throws InterruptedException {
110         final String name = lockName(testInfo);
111         try (NamedLock one = namedLockFactory.getLock(name)) {
112             assertTrue(one.lockShared(1L, TimeUnit.MILLISECONDS));
113             assertTrue(one.lockShared(1L, TimeUnit.MILLISECONDS));
114             one.unlock();
115             one.unlock();
116         }
117     }
118 
119     @Test
120     void wrBoxing(TestInfo testInfo) throws InterruptedException {
121         final String name = lockName(testInfo);
122         try (NamedLock one = namedLockFactory.getLock(name)) {
123             assertTrue(one.lockExclusively(1L, TimeUnit.MILLISECONDS));
124             assertTrue(one.lockShared(1L, TimeUnit.MILLISECONDS));
125             one.unlock();
126             one.unlock();
127         }
128     }
129 
130     @Test
131     void rwBoxing(TestInfo testInfo) throws InterruptedException {
132         final String name = lockName(testInfo);
133         try (NamedLock one = namedLockFactory.getLock(name)) {
134             assertTrue(one.lockShared(1L, TimeUnit.MILLISECONDS));
135             try {
136                 one.lockExclusively(1L, TimeUnit.MILLISECONDS);
137                 fail();
138             } catch (LockUpgradeNotSupportedException e) {
139                 // good
140             }
141             one.unlock();
142         }
143     }
144 
145     @Test
146     @Timeout(5)
147     public void sharedAccess(TestInfo testInfo) throws InterruptedException {
148         final String name = lockName(testInfo);
149         CountDownLatch winners = new CountDownLatch(2); // we expect 2 winners
150         CountDownLatch losers = new CountDownLatch(0); // we expect 0 losers
151         Thread t1 = new Thread(new Access(namedLockFactory, name, true, winners, losers));
152         Thread t2 = new Thread(new Access(namedLockFactory, name, true, winners, losers));
153         t1.start();
154         t2.start();
155         t1.join();
156         t2.join();
157         winners.await();
158         losers.await();
159     }
160 
161     @Test
162     @Timeout(5)
163     public void exclusiveAccess(TestInfo testInfo) throws InterruptedException {
164         final String name = lockName(testInfo);
165         CountDownLatch winners = new CountDownLatch(1); // we expect 1 winner
166         CountDownLatch losers = new CountDownLatch(1); // we expect 1 loser
167         Thread t1 = new Thread(new Access(namedLockFactory, name, false, winners, losers));
168         Thread t2 = new Thread(new Access(namedLockFactory, name, false, winners, losers));
169         t1.start();
170         t2.start();
171         t1.join();
172         t2.join();
173         winners.await();
174         losers.await();
175     }
176 
177     @Test
178     @Timeout(5)
179     public void mixedAccess(TestInfo testInfo) throws InterruptedException {
180         final String name = lockName(testInfo);
181         CountDownLatch winners = new CountDownLatch(1); // we expect 1 winner
182         CountDownLatch losers = new CountDownLatch(1); // we expect 1 loser
183         Thread t1 = new Thread(new Access(namedLockFactory, name, true, winners, losers));
184         Thread t2 = new Thread(new Access(namedLockFactory, name, false, winners, losers));
185         t1.start();
186         t2.start();
187         t1.join();
188         t2.join();
189         winners.await();
190         losers.await();
191     }
192 
193     @Test
194     @Timeout(5)
195     public void fullyConsumeLockTime(TestInfo testInfo) throws InterruptedException {
196         long start = System.nanoTime();
197         final String name = lockName(testInfo);
198         CountDownLatch winners = new CountDownLatch(1); // we expect 1 winner
199         CountDownLatch losers = new CountDownLatch(1); // we expect 1 loser
200         Thread t1 = new Thread(new Access(namedLockFactory, name, true, winners, losers));
201         Thread t2 = new Thread(new Access(namedLockFactory, name, false, winners, losers));
202         t1.start();
203         t2.start();
204         t1.join();
205         t2.join();
206         winners.await();
207         losers.await();
208         long end = System.nanoTime();
209         long duration = end - start;
210         long expectedDuration = TimeUnit.MILLISECONDS.toNanos(ACCESS_WAIT_MILLIS);
211         assertTrue(duration >= expectedDuration, duration + " >= " + expectedDuration); // equal in ideal case
212     }
213 
214     @Test
215     @Timeout(5)
216     public void releasedExclusiveAllowAccess(TestInfo testInfo) throws InterruptedException {
217         final String name = lockName(testInfo);
218         CountDownLatch winners = new CountDownLatch(1); // we expect 1 winner
219         CountDownLatch losers = new CountDownLatch(0); // we expect 0 loser
220         Thread t1 = new Thread(new Access(namedLockFactory, name, true, winners, losers));
221         try (NamedLock namedLock = namedLockFactory.getLock(name)) {
222             assertTrue(namedLock.lockExclusively(50L, TimeUnit.MILLISECONDS));
223             try {
224                 t1.start();
225                 Thread.sleep(50L);
226             } finally {
227                 namedLock.unlock();
228             }
229         }
230         t1.join();
231         winners.await();
232         losers.await();
233     }
234 
235     private static final long ACCESS_WAIT_MILLIS = 1000L;
236 
237     private static class Access implements Runnable {
238         final NamedLockFactory namedLockFactory;
239         final String name;
240         final boolean shared;
241         final CountDownLatch winner;
242         final CountDownLatch loser;
243 
244         public Access(
245                 NamedLockFactory namedLockFactory,
246                 String name,
247                 boolean shared,
248                 CountDownLatch winner,
249                 CountDownLatch loser) {
250             this.namedLockFactory = namedLockFactory;
251             this.name = name;
252             this.shared = shared;
253             this.winner = winner;
254             this.loser = loser;
255         }
256 
257         @Override
258         public void run() {
259             try (NamedLock lock = namedLockFactory.getLock(name)) {
260                 if (shared
261                         ? lock.lockShared(ACCESS_WAIT_MILLIS, TimeUnit.MILLISECONDS)
262                         : lock.lockExclusively(ACCESS_WAIT_MILLIS, TimeUnit.MILLISECONDS)) {
263                     try {
264                         winner.countDown();
265                         loser.await();
266                     } finally {
267                         lock.unlock();
268                     }
269                 } else {
270                     loser.countDown();
271                     winner.await();
272                 }
273             } catch (InterruptedException e) {
274                 fail(e.getMessage());
275             }
276         }
277     }
278 }