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.Collection;
22  import java.util.Collections;
23  import java.util.concurrent.CountDownLatch;
24  import java.util.concurrent.TimeUnit;
25  
26  import org.eclipse.aether.named.support.LockUpgradeNotSupportedException;
27  import org.junit.jupiter.api.Test;
28  import org.junit.jupiter.api.TestInfo;
29  import org.junit.jupiter.api.Timeout;
30  
31  import static org.junit.jupiter.api.Assertions.*;
32  
33  /**
34   * UT support for {@link NamedLockFactory}.
35   */
36  public abstract class NamedLockFactoryTestSupport {
37  
38      protected static NamedLockFactory namedLockFactory;
39  
40      protected Collection<NamedLockKey> lockName(TestInfo testInfo) {
41          return Collections.singleton(NamedLockKey.of(testInfo.getDisplayName()));
42      }
43  
44      @Test
45      void testFailure(TestInfo testInfo) throws InterruptedException {
46          // note: set system property "aether.named.diagnostic.enabled" to "true" to have log output
47          // this test does NOT assert its presence, only the proper flow
48          assertThrows(IllegalStateException.class, () -> {
49              Thread t1 = new Thread(() -> {
50                  try {
51                      namedLockFactory.getLock(lockName(testInfo)).lockShared(1L, TimeUnit.MINUTES);
52                      namedLockFactory.getLock(lockName(testInfo)).lockShared(1L, TimeUnit.MINUTES);
53                  } catch (InterruptedException e) {
54                      throw new RuntimeException(e);
55                  }
56              });
57              Thread t2 = new Thread(() -> {
58                  try {
59                      namedLockFactory.getLock(lockName(testInfo)).lockShared(1L, TimeUnit.MINUTES);
60                      namedLockFactory.getLock(lockName(testInfo)).lockShared(1L, TimeUnit.MINUTES);
61                      namedLockFactory.getLock(lockName(testInfo)).lockShared(1L, TimeUnit.MINUTES);
62                  } catch (InterruptedException e) {
63                      throw new RuntimeException(e);
64                  }
65              });
66              t1.start();
67              t2.start();
68              t1.join();
69              t2.join();
70              throw namedLockFactory.onFailure(new IllegalStateException("failure"));
71          });
72      }
73  
74      @Test
75      void refCounting(TestInfo testInfo) {
76          final Collection<NamedLockKey> keys = lockName(testInfo);
77          try (NamedLock one = namedLockFactory.getLock(keys);
78                  NamedLock two = namedLockFactory.getLock(keys)) {
79              assertSame(one, two);
80              one.close();
81              two.close();
82  
83              try (NamedLock three = namedLockFactory.getLock(keys)) {
84                  assertNotSame(three, two);
85              }
86          }
87      }
88  
89      @Test
90      void unlockWoLock(TestInfo testInfo) {
91          assertThrows(IllegalStateException.class, () -> {
92              final Collection<NamedLockKey> keys = lockName(testInfo);
93              try (NamedLock one = namedLockFactory.getLock(keys)) {
94                  one.unlock();
95              }
96          });
97      }
98  
99      @Test
100     void wwBoxing(TestInfo testInfo) throws InterruptedException {
101         final Collection<NamedLockKey> keys = lockName(testInfo);
102         try (NamedLock one = namedLockFactory.getLock(keys)) {
103             assertTrue(one.lockExclusively(1L, TimeUnit.MILLISECONDS));
104             assertTrue(one.lockExclusively(1L, TimeUnit.MILLISECONDS));
105             one.unlock();
106             one.unlock();
107         }
108     }
109 
110     @Test
111     void rrBoxing(TestInfo testInfo) throws InterruptedException {
112         final Collection<NamedLockKey> keys = lockName(testInfo);
113         try (NamedLock one = namedLockFactory.getLock(keys)) {
114             assertTrue(one.lockShared(1L, TimeUnit.MILLISECONDS));
115             assertTrue(one.lockShared(1L, TimeUnit.MILLISECONDS));
116             one.unlock();
117             one.unlock();
118         }
119     }
120 
121     @Test
122     void wrBoxing(TestInfo testInfo) throws InterruptedException {
123         final Collection<NamedLockKey> keys = lockName(testInfo);
124         try (NamedLock one = namedLockFactory.getLock(keys)) {
125             assertTrue(one.lockExclusively(1L, TimeUnit.MILLISECONDS));
126             assertTrue(one.lockShared(1L, TimeUnit.MILLISECONDS));
127             one.unlock();
128             one.unlock();
129         }
130     }
131 
132     @Test
133     void rwBoxing(TestInfo testInfo) throws InterruptedException {
134         final Collection<NamedLockKey> keys = lockName(testInfo);
135         try (NamedLock one = namedLockFactory.getLock(keys)) {
136             assertTrue(one.lockShared(1L, TimeUnit.MILLISECONDS));
137             try {
138                 one.lockExclusively(1L, TimeUnit.MILLISECONDS);
139                 fail();
140             } catch (LockUpgradeNotSupportedException e) {
141                 // good
142             }
143             one.unlock();
144         }
145     }
146 
147     @Test
148     @Timeout(5)
149     public void sharedAccess(TestInfo testInfo) throws InterruptedException {
150         final Collection<NamedLockKey> keys = lockName(testInfo);
151         CountDownLatch winners = new CountDownLatch(2); // we expect 2 winners
152         CountDownLatch losers = new CountDownLatch(0); // we expect 0 losers
153         Thread t1 = new Thread(new Access(namedLockFactory, keys, true, winners, losers));
154         Thread t2 = new Thread(new Access(namedLockFactory, keys, true, winners, losers));
155         t1.start();
156         t2.start();
157         t1.join();
158         t2.join();
159         winners.await();
160         losers.await();
161     }
162 
163     @Test
164     @Timeout(5)
165     public void exclusiveAccess(TestInfo testInfo) throws InterruptedException {
166         final Collection<NamedLockKey> keys = lockName(testInfo);
167         CountDownLatch winners = new CountDownLatch(1); // we expect 1 winner
168         CountDownLatch losers = new CountDownLatch(1); // we expect 1 loser
169         Thread t1 = new Thread(new Access(namedLockFactory, keys, false, winners, losers));
170         Thread t2 = new Thread(new Access(namedLockFactory, keys, false, winners, losers));
171         t1.start();
172         t2.start();
173         t1.join();
174         t2.join();
175         winners.await();
176         losers.await();
177     }
178 
179     @Test
180     @Timeout(5)
181     public void mixedAccess(TestInfo testInfo) throws InterruptedException {
182         final Collection<NamedLockKey> keys = lockName(testInfo);
183         CountDownLatch winners = new CountDownLatch(1); // we expect 1 winner
184         CountDownLatch losers = new CountDownLatch(1); // we expect 1 loser
185         Thread t1 = new Thread(new Access(namedLockFactory, keys, true, winners, losers));
186         Thread t2 = new Thread(new Access(namedLockFactory, keys, false, winners, losers));
187         t1.start();
188         t2.start();
189         t1.join();
190         t2.join();
191         winners.await();
192         losers.await();
193     }
194 
195     @Test
196     @Timeout(5)
197     public void fullyConsumeLockTime(TestInfo testInfo) throws InterruptedException {
198         long start = System.nanoTime();
199         final Collection<NamedLockKey> keys = lockName(testInfo);
200         CountDownLatch winners = new CountDownLatch(1); // we expect 1 winner
201         CountDownLatch losers = new CountDownLatch(1); // we expect 1 loser
202         Thread t1 = new Thread(new Access(namedLockFactory, keys, true, winners, losers));
203         Thread t2 = new Thread(new Access(namedLockFactory, keys, false, winners, losers));
204         t1.start();
205         t2.start();
206         t1.join();
207         t2.join();
208         winners.await();
209         losers.await();
210         long end = System.nanoTime();
211         long duration = end - start;
212         long expectedDuration = TimeUnit.MILLISECONDS.toNanos(ACCESS_WAIT_MILLIS);
213         assertTrue(duration >= expectedDuration, duration + " >= " + expectedDuration); // equal in ideal case
214     }
215 
216     @Test
217     @Timeout(5)
218     public void releasedExclusiveAllowAccess(TestInfo testInfo) throws InterruptedException {
219         final Collection<NamedLockKey> keys = lockName(testInfo);
220         CountDownLatch winners = new CountDownLatch(1); // we expect 1 winner
221         CountDownLatch losers = new CountDownLatch(0); // we expect 0 loser
222         Thread t1 = new Thread(new Access(namedLockFactory, keys, true, winners, losers));
223         try (NamedLock namedLock = namedLockFactory.getLock(keys)) {
224             assertTrue(namedLock.lockExclusively(50L, TimeUnit.MILLISECONDS));
225             try {
226                 t1.start();
227                 Thread.sleep(50L);
228             } finally {
229                 namedLock.unlock();
230             }
231         }
232         t1.join();
233         winners.await();
234         losers.await();
235     }
236 
237     private static final long ACCESS_WAIT_MILLIS = 1000L;
238 
239     private static class Access implements Runnable {
240         final NamedLockFactory namedLockFactory;
241         final Collection<NamedLockKey> keys;
242         final boolean shared;
243         final CountDownLatch winner;
244         final CountDownLatch loser;
245 
246         public Access(
247                 NamedLockFactory namedLockFactory,
248                 Collection<NamedLockKey> keys,
249                 boolean shared,
250                 CountDownLatch winner,
251                 CountDownLatch loser) {
252             this.namedLockFactory = namedLockFactory;
253             this.keys = keys;
254             this.shared = shared;
255             this.winner = winner;
256             this.loser = loser;
257         }
258 
259         @Override
260         public void run() {
261             try (NamedLock lock = namedLockFactory.getLock(keys)) {
262                 if (shared
263                         ? lock.lockShared(ACCESS_WAIT_MILLIS, TimeUnit.MILLISECONDS)
264                         : lock.lockExclusively(ACCESS_WAIT_MILLIS, TimeUnit.MILLISECONDS)) {
265                     try {
266                         winner.countDown();
267                         loser.await();
268                     } finally {
269                         lock.unlock();
270                     }
271                 } else {
272                     loser.countDown();
273                     winner.await();
274                 }
275             } catch (InterruptedException e) {
276                 fail(e.getMessage());
277             }
278         }
279     }
280 }