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