/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.zookeeper.server.quorum;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.apache.zookeeper.ZKTestCase;
import org.junit.Assert;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LearnerSnapshotThrottlerTest extends ZKTestCase {
private static final Logger LOG =
LoggerFactory.getLogger(LearnerSnapshotThrottlerTest.class);
@Test(expected = SnapshotThrottleException.class)
public void testTooManySnapshotsNonessential() throws Exception {
LearnerSnapshotThrottler throttler = new LearnerSnapshotThrottler(5);
for (int i = 0; i < 6; i++) {
throttler.beginSnapshot(false);
}
}
@Test(expected = SnapshotThrottleException.class)
public void testTooManySnapshotsEssential() throws Exception {
LearnerSnapshotThrottler throttler = new LearnerSnapshotThrottler(5);
try {
for (int i = 0; i < 6; i++) {
throttler.beginSnapshot(true);
}
}
catch (SnapshotThrottleException ex) {
Assert.fail("essential snapshots should not be throttled");
}
throttler.endSnapshot();
throttler.beginSnapshot(false);
}
@Test
public void testNoThrottle() throws Exception {
LearnerSnapshotThrottler throttler = new LearnerSnapshotThrottler(5);
try {
for (int i = 0; i < 6; i++) {
throttler.beginSnapshot(true);
}
}
catch (SnapshotThrottleException ex) {
Assert.fail("essential snapshots should not be throttled");
}
throttler.endSnapshot();
for (int i = 0; i < 5; i++) {
throttler.endSnapshot();
throttler.beginSnapshot(false);
}
}
@Test
public void testTryWithResourceNoThrottle() throws Exception {
LearnerSnapshotThrottler throttler = new LearnerSnapshotThrottler(1);
for (int i = 0; i < 3; i++) {
LearnerSnapshot snapshot = throttler.beginSnapshot(false);
try {
Assert.assertFalse(snapshot.isEssential());
Assert.assertEquals(1, snapshot.getConcurrentSnapshotNumber());
} finally {
snapshot.close();
}
}
}
@Test(expected = SnapshotThrottleException.class)
public void testTryWithResourceThrottle() throws Exception {
LearnerSnapshotThrottler throttler = new LearnerSnapshotThrottler(1);
LearnerSnapshot outer = throttler.beginSnapshot(true);
try {
LearnerSnapshot inner = throttler.beginSnapshot(false);
try {
Assert.fail("shouldn't be able to have both snapshots open");
} finally {
inner.close();
}
} finally {
outer.close();
}
}
@Test
public void testParallelNoThrottle() throws Exception {
final int numThreads = 50;
final LearnerSnapshotThrottler throttler = new LearnerSnapshotThrottler(numThreads);
ExecutorService threadPool = Executors.newFixedThreadPool(numThreads);
final CountDownLatch threadStartLatch = new CountDownLatch(numThreads);
final CountDownLatch snapshotProgressLatch = new CountDownLatch(numThreads);
List<Future<Boolean>> results = new ArrayList<Future<Boolean>>(numThreads);
for (int i = 0; i < numThreads; i++) {
results.add(threadPool.submit(new Callable<Boolean>() {
@Override
public Boolean call() {
threadStartLatch.countDown();
try {
threadStartLatch.await();
throttler.beginSnapshot(false);
snapshotProgressLatch.countDown();
snapshotProgressLatch.await();
throttler.endSnapshot();
}
catch (Exception e) {
return false;
}
return true;
}
}));
}
for (Future<Boolean> result : results) {
Assert.assertTrue(result.get());
}
}
@Test
public void testPositiveTimeout() throws Exception {
final LearnerSnapshotThrottler throttler = new LearnerSnapshotThrottler(1, 200);
ExecutorService threadPool = Executors.newFixedThreadPool(1);
LearnerSnapshot first = throttler.beginSnapshot(false);
final CountDownLatch snapshotProgressLatch = new CountDownLatch(1);
Future<Boolean> result = threadPool.submit(new Callable<Boolean>() {
@Override
public Boolean call() {
try {
snapshotProgressLatch.countDown();
LearnerSnapshot second = throttler.beginSnapshot(false);
second.close();
}
catch (Exception e) {
return false;
}
return true;
}
});
snapshotProgressLatch.await();
first.close();
Assert.assertTrue(result.get());
}
@Test
public void testHighContentionWithTimeout() throws Exception {
int numThreads = 20;
final LearnerSnapshotThrottler throttler = new LearnerSnapshotThrottler(2, 5000);
ExecutorService threadPool = Executors.newFixedThreadPool(numThreads);
final CountDownLatch threadStartLatch = new CountDownLatch(numThreads);
List<Future<Boolean>> results = new ArrayList<Future<Boolean>>(numThreads);
for (int i = 0; i < numThreads; i++) {
results.add(threadPool.submit(new Callable<Boolean>() {
@Override
public Boolean call() {
threadStartLatch.countDown();
try {
threadStartLatch.await();
LearnerSnapshot snap = throttler.beginSnapshot(false);
int snapshotNumber = snap.getConcurrentSnapshotNumber();
throttler.endSnapshot();
return snapshotNumber <= 2;
}
catch (Exception e) {
LOG.error("Exception trying to begin snapshot", e);
return false;
}
}
}));
}
for (Future<Boolean> result : results) {
Assert.assertTrue(result.get());
}
}
}