View Javadoc

1   /*
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.regionserver;
20  
21  import static org.apache.hadoop.hbase.HConstants.NO_NONCE;
22  import static org.junit.Assert.assertEquals;
23  import static org.junit.Assert.assertFalse;
24  import static org.junit.Assert.assertTrue;
25  import static org.junit.Assert.fail;
26  
27  import java.util.concurrent.CountDownLatch;
28  import java.util.concurrent.atomic.AtomicInteger;
29  
30  import org.apache.hadoop.conf.Configuration;
31  import org.apache.hadoop.hbase.HBaseConfiguration;
32  import org.apache.hadoop.hbase.ScheduledChore;
33  import org.apache.hadoop.hbase.Stoppable;
34  import org.apache.hadoop.hbase.testclassification.SmallTests;
35  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
36  import org.apache.hadoop.hbase.util.ManualEnvironmentEdge;
37  import org.apache.hadoop.hbase.util.Threads;
38  import org.junit.Test;
39  import org.junit.experimental.categories.Category;
40  import org.mockito.Mockito;
41  import org.mockito.invocation.InvocationOnMock;
42  import org.mockito.stubbing.Answer;
43  
44  @Category(SmallTests.class)
45  public class TestServerNonceManager {
46  
47    @Test
48    public void testMvcc() throws Exception {
49      ServerNonceManager nm = createManager();
50      final long group = 100;
51      final long nonce = 1;
52      final long initMvcc = 999;
53      assertTrue(nm.startOperation(group, nonce, createStoppable()));
54      nm.addMvccToOperationContext(group, nonce, initMvcc);
55      nm.endOperation(group, nonce, true);
56      assertEquals(initMvcc, nm.getMvccFromOperationContext(group, nonce));
57      long newMvcc = initMvcc + 1;
58      for (long newNonce = nonce + 1; newNonce != (nonce + 5); ++newNonce) {
59        assertTrue(nm.startOperation(group, newNonce, createStoppable()));
60        nm.addMvccToOperationContext(group, newNonce, newMvcc);
61        nm.endOperation(group, newNonce, true);
62        assertEquals(newMvcc, nm.getMvccFromOperationContext(group, newNonce));
63        ++newMvcc;
64      }
65      assertEquals(initMvcc, nm.getMvccFromOperationContext(group, nonce));
66    }
67  
68    @Test
69    public void testNormalStartEnd() throws Exception {
70      final long[] numbers = new long[] { NO_NONCE, 1, 2, Long.MAX_VALUE, Long.MIN_VALUE };
71      ServerNonceManager nm = createManager();
72      for (int i = 0; i < numbers.length; ++i) {
73        for (int j = 0; j < numbers.length; ++j) {
74          assertTrue(nm.startOperation(numbers[i], numbers[j], createStoppable()));
75        }
76      }
77      // Should be able to start operation the second time w/o nonces.
78      for (int i = 0; i < numbers.length; ++i) {
79        assertTrue(nm.startOperation(numbers[i], NO_NONCE, createStoppable()));
80      }
81      // Fail all operations - should be able to restart.
82      for (int i = 0; i < numbers.length; ++i) {
83        for (int j = 0; j < numbers.length; ++j) {
84          nm.endOperation(numbers[i], numbers[j], false);
85          assertTrue(nm.startOperation(numbers[i], numbers[j], createStoppable()));
86        }
87      }
88      // Succeed all operations - should not be able to restart, except for NO_NONCE.
89      for (int i = 0; i < numbers.length; ++i) {
90        for (int j = 0; j < numbers.length; ++j) {
91          nm.endOperation(numbers[i], numbers[j], true);
92          assertEquals(numbers[j] == NO_NONCE,
93              nm.startOperation(numbers[i], numbers[j], createStoppable()));
94        }
95      }
96    }
97  
98    @Test
99    public void testNoEndWithoutStart() {
100     ServerNonceManager nm = createManager();
101     try {
102       nm.endOperation(NO_NONCE, 1, true);
103       throw new Error("Should have thrown");
104     } catch (AssertionError err) {}
105   }
106 
107   @Test
108   public void testCleanup() throws Exception {
109     ManualEnvironmentEdge edge = new ManualEnvironmentEdge();
110     EnvironmentEdgeManager.injectEdge(edge);
111     try {
112       ServerNonceManager nm = createManager(6);
113       ScheduledChore cleanup = nm.createCleanupScheduledChore(Mockito.mock(Stoppable.class));
114       edge.setValue(1);
115       assertTrue(nm.startOperation(NO_NONCE, 1, createStoppable()));
116       assertTrue(nm.startOperation(NO_NONCE, 2, createStoppable()));
117       assertTrue(nm.startOperation(NO_NONCE, 3, createStoppable()));
118       edge.setValue(2);
119       nm.endOperation(NO_NONCE, 1, true);
120       edge.setValue(4);
121       nm.endOperation(NO_NONCE, 2, true);
122       edge.setValue(9);
123       cleanup.choreForTesting();
124       // Nonce 1 has been cleaned up.
125       assertTrue(nm.startOperation(NO_NONCE, 1, createStoppable()));
126       // Nonce 2 has not been cleaned up.
127       assertFalse(nm.startOperation(NO_NONCE, 2, createStoppable()));
128       // Nonce 3 was active and active ops should never be cleaned up; try to end and start.
129       nm.endOperation(NO_NONCE, 3, false);
130       assertTrue(nm.startOperation(NO_NONCE, 3, createStoppable()));
131       edge.setValue(11);
132       cleanup.choreForTesting();
133       // Now, nonce 2 has been cleaned up.
134       assertTrue(nm.startOperation(NO_NONCE, 2, createStoppable()));
135     } finally {
136       EnvironmentEdgeManager.reset();
137     }
138   }
139 
140   @Test
141   public void testWalNonces() throws Exception {
142     ManualEnvironmentEdge edge = new ManualEnvironmentEdge();
143     EnvironmentEdgeManager.injectEdge(edge);
144     try {
145       ServerNonceManager nm = createManager(6);
146       ScheduledChore cleanup = nm.createCleanupScheduledChore(Mockito.mock(Stoppable.class));
147       // Add nonces from WAL, including dups.
148       edge.setValue(12);
149       nm.reportOperationFromWal(NO_NONCE, 1, 8);
150       nm.reportOperationFromWal(NO_NONCE, 2, 2);
151       nm.reportOperationFromWal(NO_NONCE, 3, 5);
152       nm.reportOperationFromWal(NO_NONCE, 3, 6);
153       // WAL nonces should prevent cross-server conflicts.
154       assertFalse(nm.startOperation(NO_NONCE, 1, createStoppable()));
155       // Make sure we ignore very old nonces, but not borderline old nonces.
156       assertTrue(nm.startOperation(NO_NONCE, 2, createStoppable()));
157       assertFalse(nm.startOperation(NO_NONCE, 3, createStoppable()));
158       // Make sure grace period is counted from recovery time.
159       edge.setValue(17);
160       cleanup.choreForTesting();
161       assertFalse(nm.startOperation(NO_NONCE, 1, createStoppable()));
162       assertFalse(nm.startOperation(NO_NONCE, 3, createStoppable()));
163       edge.setValue(19);
164       cleanup.choreForTesting();
165       assertTrue(nm.startOperation(NO_NONCE, 1, createStoppable()));
166       assertTrue(nm.startOperation(NO_NONCE, 3, createStoppable()));
167     } finally {
168       EnvironmentEdgeManager.reset();
169     }
170   }
171 
172   @Test
173   public void testConcurrentAttempts() throws Exception {
174     final ServerNonceManager nm = createManager();
175 
176     nm.startOperation(NO_NONCE, 1, createStoppable());
177     TestRunnable tr = new TestRunnable(nm, 1, false, createStoppable());
178     Thread t = tr.start();
179     waitForThreadToBlockOrExit(t);
180     nm.endOperation(NO_NONCE, 1, true); // operation succeeded
181     t.join(); // thread must now unblock and not proceed (result checked inside).
182     tr.propagateError();
183 
184     nm.startOperation(NO_NONCE, 2, createStoppable());
185     tr = new TestRunnable(nm, 2, true, createStoppable());
186     t = tr.start();
187     waitForThreadToBlockOrExit(t);
188     nm.endOperation(NO_NONCE, 2, false);
189     t.join(); // thread must now unblock and allow us to proceed (result checked inside).
190     tr.propagateError();
191     nm.endOperation(NO_NONCE, 2, true); // that is to say we should be able to end operation
192 
193     nm.startOperation(NO_NONCE, 3, createStoppable());
194     tr = new TestRunnable(nm, 4, true, createStoppable());
195     tr.start().join();  // nonce 3 must have no bearing on nonce 4
196     tr.propagateError();
197   }
198 
199   @Test
200   public void testStopWaiting() throws Exception {
201     final ServerNonceManager nm = createManager();
202     nm.setConflictWaitIterationMs(1);
203     Stoppable stoppingStoppable = createStoppable();
204     Mockito.when(stoppingStoppable.isStopped()).thenAnswer(new Answer<Boolean>() {
205       AtomicInteger answer = new AtomicInteger(3);
206       @Override
207       public Boolean answer(InvocationOnMock invocation) throws Throwable {
208         return 0 < answer.decrementAndGet();
209       }
210     });
211 
212     nm.startOperation(NO_NONCE, 1, createStoppable());
213     TestRunnable tr = new TestRunnable(nm, 1, null, stoppingStoppable);
214     Thread t = tr.start();
215     waitForThreadToBlockOrExit(t);
216     // thread must eventually throw
217     t.join();
218     tr.propagateError();
219   }
220 
221   private void waitForThreadToBlockOrExit(Thread t) throws InterruptedException {
222     for (int i = 9; i >= 0; --i) {
223       if (t.getState() == Thread.State.TIMED_WAITING || t.getState() == Thread.State.WAITING
224           || t.getState() == Thread.State.BLOCKED || t.getState() == Thread.State.TERMINATED) {
225         return;
226       }
227       if (i > 0) Thread.sleep(300);
228     }
229     // Thread didn't block in 3 seconds. What is it doing? Continue the test, we'd rather
230     // have a very strange false positive then false negative due to timing.
231   }
232 
233   private static class TestRunnable implements Runnable {
234     public final CountDownLatch startedLatch = new CountDownLatch(1); // It's the final countdown!
235 
236     private final ServerNonceManager nm;
237     private final long nonce;
238     private final Boolean expected;
239     private final Stoppable stoppable;
240 
241     private Throwable throwable = null;
242 
243     public TestRunnable(ServerNonceManager nm, long nonce, Boolean expected, Stoppable stoppable) {
244       this.nm = nm;
245       this.nonce = nonce;
246       this.expected = expected;
247       this.stoppable = stoppable;
248     }
249 
250     public void propagateError() throws Exception {
251       if (throwable == null) return;
252       throw new Exception(throwable);
253     }
254 
255     public Thread start() {
256       Thread t = new Thread(this);
257       t = Threads.setDaemonThreadRunning(t);
258       try {
259         startedLatch.await();
260       } catch (InterruptedException e) {
261         fail("Unexpected");
262       }
263       return t;
264     }
265 
266     @Override
267     public void run() {
268       startedLatch.countDown();
269       boolean shouldThrow = expected == null;
270       boolean hasThrown = true;
271       try {
272         boolean result = nm.startOperation(NO_NONCE, nonce, stoppable);
273         hasThrown = false;
274         if (!shouldThrow) {
275           assertEquals(expected.booleanValue(), result);
276         }
277       } catch (Throwable t) {
278         if (!shouldThrow) {
279           throwable = t;
280         }
281       }
282       if (shouldThrow && !hasThrown) {
283         throwable = new AssertionError("Should have thrown");
284       }
285     }
286   }
287 
288   private Stoppable createStoppable() {
289     Stoppable s = Mockito.mock(Stoppable.class);
290     Mockito.when(s.isStopped()).thenReturn(false);
291     return s;
292   }
293 
294   private ServerNonceManager createManager() {
295     return createManager(null);
296   }
297 
298   private ServerNonceManager createManager(Integer gracePeriod) {
299     Configuration conf = HBaseConfiguration.create();
300     if (gracePeriod != null) {
301       conf.setInt(ServerNonceManager.HASH_NONCE_GRACE_PERIOD_KEY, gracePeriod.intValue());
302     }
303     return new ServerNonceManager(conf);
304   }
305 }