Committed by
Gerrit Code Review
Adding BoundedThreadPool and BlockingBoolean
Updating EventuallyConsistentMap to use BoundedThreadPool for broadcast threads, and disabling anti-entropy for now. Change-Id: Id1bfcdaf1d0a19745fe7336e4ac9eaf649871d5d
Showing
6 changed files
with
727 additions
and
8 deletions
| ... | @@ -53,6 +53,12 @@ | ... | @@ -53,6 +53,12 @@ |
| 53 | 53 | ||
| 54 | <dependency> | 54 | <dependency> |
| 55 | <groupId>org.onosproject</groupId> | 55 | <groupId>org.onosproject</groupId> |
| 56 | + <artifactId>onlab-misc</artifactId> | ||
| 57 | + <version>${project.version}</version> | ||
| 58 | + </dependency> | ||
| 59 | + | ||
| 60 | + <dependency> | ||
| 61 | + <groupId>org.onosproject</groupId> | ||
| 56 | <artifactId>onos-core-common</artifactId> | 62 | <artifactId>onos-core-common</artifactId> |
| 57 | </dependency> | 63 | </dependency> |
| 58 | 64 | ... | ... |
| ... | @@ -54,8 +54,8 @@ import java.util.stream.Collectors; | ... | @@ -54,8 +54,8 @@ import java.util.stream.Collectors; |
| 54 | import static com.google.common.base.Preconditions.checkNotNull; | 54 | import static com.google.common.base.Preconditions.checkNotNull; |
| 55 | import static com.google.common.base.Preconditions.checkState; | 55 | import static com.google.common.base.Preconditions.checkState; |
| 56 | import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor; | 56 | import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor; |
| 57 | +import static org.onlab.util.BoundedThreadPool.newFixedThreadPool; | ||
| 57 | import static org.onlab.util.Tools.groupedThreads; | 58 | import static org.onlab.util.Tools.groupedThreads; |
| 58 | -import static org.onlab.util.Tools.minPriority; | ||
| 59 | 59 | ||
| 60 | /** | 60 | /** |
| 61 | * Distributed Map implementation which uses optimistic replication and gossip | 61 | * Distributed Map implementation which uses optimistic replication and gossip |
| ... | @@ -149,16 +149,23 @@ public class EventuallyConsistentMapImpl<K, V> | ... | @@ -149,16 +149,23 @@ public class EventuallyConsistentMapImpl<K, V> |
| 149 | items = new ConcurrentHashMap<>(); | 149 | items = new ConcurrentHashMap<>(); |
| 150 | removedItems = new ConcurrentHashMap<>(); | 150 | removedItems = new ConcurrentHashMap<>(); |
| 151 | 151 | ||
| 152 | - executor = Executors //FIXME | 152 | + // should be a normal executor; it's used for receiving messages |
| 153 | - .newFixedThreadPool(4, groupedThreads("onos/ecm", mapName + "-fg-%d")); | 153 | + //TODO make # of threads configurable |
| 154 | + executor = Executors.newFixedThreadPool(8, groupedThreads("onos/ecm", mapName + "-fg-%d")); | ||
| 154 | 155 | ||
| 155 | - broadcastMessageExecutor = Executors.newSingleThreadExecutor(groupedThreads("onos/ecm", mapName + "-notify")); | 156 | + // sending executor; should be capped |
| 157 | + //TODO make # of threads configurable | ||
| 158 | + broadcastMessageExecutor = //newSingleThreadExecutor(groupedThreads("onos/ecm", mapName + "-notify")); | ||
| 159 | + newFixedThreadPool(4, groupedThreads("onos/ecm", mapName + "-notify")); | ||
| 156 | 160 | ||
| 157 | backgroundExecutor = | 161 | backgroundExecutor = |
| 158 | - newSingleThreadScheduledExecutor(minPriority( | 162 | + //FIXME anti-entropy can take >60 seconds and it blocks fg workers |
| 159 | - groupedThreads("onos/ecm", mapName + "-bg-%d"))); | 163 | + // ... dropping minPriority to try to help until this can be parallel |
| 164 | + newSingleThreadScheduledExecutor(//minPriority( | ||
| 165 | + groupedThreads("onos/ecm", mapName + "-bg-%d"))/*)*/; | ||
| 160 | 166 | ||
| 161 | // start anti-entropy thread | 167 | // start anti-entropy thread |
| 168 | + //TODO disable anti-entropy for now in testing (it is unstable) | ||
| 162 | backgroundExecutor.scheduleAtFixedRate(new SendAdvertisementTask(), | 169 | backgroundExecutor.scheduleAtFixedRate(new SendAdvertisementTask(), |
| 163 | initialDelaySec, periodSec, | 170 | initialDelaySec, periodSec, |
| 164 | TimeUnit.SECONDS); | 171 | TimeUnit.SECONDS); |
| ... | @@ -494,8 +501,8 @@ public class EventuallyConsistentMapImpl<K, V> | ... | @@ -494,8 +501,8 @@ public class EventuallyConsistentMapImpl<K, V> |
| 494 | clusterService.getLocalNode().id(), | 501 | clusterService.getLocalNode().id(), |
| 495 | subject, | 502 | subject, |
| 496 | serializer.encode(event)); | 503 | serializer.encode(event)); |
| 497 | - //broadcastMessageExecutor.execute(() -> clusterCommunicator.broadcast(message)); | 504 | + broadcastMessageExecutor.execute(() -> clusterCommunicator.broadcast(message)); |
| 498 | - clusterCommunicator.broadcast(message); | 505 | +// clusterCommunicator.broadcast(message); |
| 499 | } | 506 | } |
| 500 | 507 | ||
| 501 | private void unicastMessage(NodeId peer, | 508 | private void unicastMessage(NodeId peer, | ... | ... |
| 1 | +/* | ||
| 2 | + * Copyright 2015 Open Networking Laboratory | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | +package org.onlab.util; | ||
| 17 | + | ||
| 18 | +import java.util.concurrent.TimeUnit; | ||
| 19 | +import java.util.concurrent.locks.AbstractQueuedSynchronizer; | ||
| 20 | + | ||
| 21 | +/** | ||
| 22 | + * Mutable boolean that allows threads to wait for a specified value. | ||
| 23 | + */ | ||
| 24 | +public class BlockingBoolean extends AbstractQueuedSynchronizer { | ||
| 25 | + | ||
| 26 | + private static final int TRUE = 1; | ||
| 27 | + private static final int FALSE = 0; | ||
| 28 | + | ||
| 29 | + /** | ||
| 30 | + * Creates a new blocking boolean with the specified value. | ||
| 31 | + * | ||
| 32 | + * @param value the starting value | ||
| 33 | + */ | ||
| 34 | + public BlockingBoolean(boolean value) { | ||
| 35 | + setState(value ? TRUE : FALSE); | ||
| 36 | + } | ||
| 37 | + | ||
| 38 | + /** | ||
| 39 | + * Causes the current thread to wait until the boolean equals the specified | ||
| 40 | + * value unless the thread is {@linkplain Thread#interrupt interrupted}. | ||
| 41 | + * | ||
| 42 | + * @param value specified value | ||
| 43 | + * @throws InterruptedException | ||
| 44 | + */ | ||
| 45 | + public void await(boolean value) throws InterruptedException { | ||
| 46 | + acquireSharedInterruptibly(value ? TRUE : FALSE); | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + /** | ||
| 50 | + * Causes the current thread to wait until the boolean equals the specified | ||
| 51 | + * value unless the thread is {@linkplain Thread#interrupt interrupted}, | ||
| 52 | + * or the specified waiting time elapses. | ||
| 53 | + * | ||
| 54 | + * @param value specified value | ||
| 55 | + * @param timeout the maximum time to wait | ||
| 56 | + * @param unit the time unit of the {@code timeout} argument | ||
| 57 | + * @return {@code true} if the count reached zero and {@code false} | ||
| 58 | + * if the waiting time elapsed before the count reached zero | ||
| 59 | + * @throws InterruptedException | ||
| 60 | + */ | ||
| 61 | + public boolean await(boolean value, long timeout, TimeUnit unit) | ||
| 62 | + throws InterruptedException { | ||
| 63 | + return tryAcquireSharedNanos(value ? TRUE : FALSE, unit.toNanos(timeout)); | ||
| 64 | + } | ||
| 65 | + | ||
| 66 | + protected int tryAcquireShared(int acquires) { | ||
| 67 | + return (getState() == acquires) ? 1 : -1; | ||
| 68 | + } | ||
| 69 | + | ||
| 70 | + /** | ||
| 71 | + * Sets the value of the blocking boolean. | ||
| 72 | + * | ||
| 73 | + * @param value new value | ||
| 74 | + */ | ||
| 75 | + public void set(boolean value) { | ||
| 76 | + releaseShared(value ? TRUE : FALSE); | ||
| 77 | + } | ||
| 78 | + | ||
| 79 | + /** | ||
| 80 | + * Gets the value of the blocking boolean. | ||
| 81 | + * | ||
| 82 | + * @return current value | ||
| 83 | + */ | ||
| 84 | + public boolean get() { | ||
| 85 | + return getState() == TRUE; | ||
| 86 | + } | ||
| 87 | + | ||
| 88 | + protected boolean tryReleaseShared(int releases) { | ||
| 89 | + // Signal on state change only | ||
| 90 | + int state = getState(); | ||
| 91 | + if (state == releases) { | ||
| 92 | + return false; | ||
| 93 | + } | ||
| 94 | + return compareAndSetState(state, releases); | ||
| 95 | + } | ||
| 96 | + | ||
| 97 | +} |
| 1 | +/* | ||
| 2 | + * Copyright 2015 Open Networking Laboratory | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | +package org.onlab.util; | ||
| 17 | + | ||
| 18 | +import org.slf4j.LoggerFactory; | ||
| 19 | + | ||
| 20 | +import java.util.concurrent.Callable; | ||
| 21 | +import java.util.concurrent.Future; | ||
| 22 | +import java.util.concurrent.LinkedBlockingQueue; | ||
| 23 | +import java.util.concurrent.RejectedExecutionHandler; | ||
| 24 | +import java.util.concurrent.ThreadFactory; | ||
| 25 | +import java.util.concurrent.ThreadPoolExecutor; | ||
| 26 | +import java.util.concurrent.TimeUnit; | ||
| 27 | +import java.util.concurrent.atomic.AtomicLong; | ||
| 28 | + | ||
| 29 | +/** | ||
| 30 | + * Implementation of ThreadPoolExecutor that bounds the work queue. | ||
| 31 | + * <p> | ||
| 32 | + * When a new job would exceed the queue bound, the job is run on the caller's | ||
| 33 | + * thread rather than on a thread from the pool. | ||
| 34 | + * </p> | ||
| 35 | + */ | ||
| 36 | +public final class BoundedThreadPool extends ThreadPoolExecutor { | ||
| 37 | + | ||
| 38 | + private static final org.slf4j.Logger log = LoggerFactory.getLogger(BoundedThreadPool.class); | ||
| 39 | + | ||
| 40 | + protected static int maxQueueSize = 80_000; //TODO tune this value | ||
| 41 | + //private static final RejectedExecutionHandler DEFAULT_HANDLER = new CallerFeedbackPolicy(); | ||
| 42 | + private static final long STATS_INTERVAL = 5_000; //ms | ||
| 43 | + | ||
| 44 | + private final BlockingBoolean underHighLoad; | ||
| 45 | + | ||
| 46 | + private BoundedThreadPool(int numberOfThreads, | ||
| 47 | + ThreadFactory threadFactory) { | ||
| 48 | + super(numberOfThreads, numberOfThreads, | ||
| 49 | + 0L, TimeUnit.MILLISECONDS, | ||
| 50 | + new LinkedBlockingQueue<>(maxQueueSize), | ||
| 51 | + threadFactory, | ||
| 52 | + new CallerFeedbackPolicy()); | ||
| 53 | + underHighLoad = ((CallerFeedbackPolicy) getRejectedExecutionHandler()).load(); | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + /** | ||
| 57 | + * Returns a single-thread, bounded executor service. | ||
| 58 | + * | ||
| 59 | + * @param threadFactory thread factory for the worker thread. | ||
| 60 | + * @return the bounded thread pool | ||
| 61 | + */ | ||
| 62 | + public static BoundedThreadPool newSingleThreadExecutor(ThreadFactory threadFactory) { | ||
| 63 | + return new BoundedThreadPool(1, threadFactory); | ||
| 64 | + } | ||
| 65 | + | ||
| 66 | + /** | ||
| 67 | + * Returns a fixed-size, bounded executor service. | ||
| 68 | + * | ||
| 69 | + * @param threadFactory thread factory for the worker threads. | ||
| 70 | + * @return the bounded thread pool | ||
| 71 | + */ | ||
| 72 | + public static BoundedThreadPool newFixedThreadPool(int numberOfThreads, ThreadFactory threadFactory) { | ||
| 73 | + return new BoundedThreadPool(numberOfThreads, threadFactory); | ||
| 74 | + } | ||
| 75 | + | ||
| 76 | + //TODO Might want to switch these to use Metrics class Meter and/or Gauge instead. | ||
| 77 | + private final Counter submitted = new Counter(); | ||
| 78 | + private final Counter taken = new Counter(); | ||
| 79 | + | ||
| 80 | + @Override | ||
| 81 | + public Future<?> submit(Runnable task) { | ||
| 82 | + submitted.add(1); | ||
| 83 | + return super.submit(task); | ||
| 84 | + } | ||
| 85 | + | ||
| 86 | + @Override | ||
| 87 | + public <T> Future<T> submit(Runnable task, T result) { | ||
| 88 | + submitted.add(1); | ||
| 89 | + return super.submit(task, result); | ||
| 90 | + } | ||
| 91 | + | ||
| 92 | + @Override | ||
| 93 | + public void execute(Runnable command) { | ||
| 94 | + submitted.add(1); | ||
| 95 | + super.execute(command); | ||
| 96 | + } | ||
| 97 | + | ||
| 98 | + @Override | ||
| 99 | + public <T> Future<T> submit(Callable<T> task) { | ||
| 100 | + submitted.add(1); | ||
| 101 | + return super.submit(task); | ||
| 102 | + } | ||
| 103 | + | ||
| 104 | + | ||
| 105 | + @Override | ||
| 106 | + protected void beforeExecute(Thread t, Runnable r) { | ||
| 107 | + super.beforeExecute(t, r); | ||
| 108 | + taken.add(1); | ||
| 109 | + periodicallyPrintStats(); | ||
| 110 | + updateLoad(); | ||
| 111 | + } | ||
| 112 | + | ||
| 113 | + // TODO schedule this with a fixed delay from a scheduled executor | ||
| 114 | + private final AtomicLong lastPrinted = new AtomicLong(0L); | ||
| 115 | + private void periodicallyPrintStats() { | ||
| 116 | + long now = System.currentTimeMillis(); | ||
| 117 | + long prev = lastPrinted.get(); | ||
| 118 | + if (now - prev > STATS_INTERVAL) { | ||
| 119 | + if (lastPrinted.compareAndSet(prev, now)) { | ||
| 120 | + log.warn("queue size: {} jobs, submitted: {} jobs/s, taken: {} jobs/s", | ||
| 121 | + getQueue().size(), | ||
| 122 | + submitted.throughput(), taken.throughput()); | ||
| 123 | + submitted.reset(); | ||
| 124 | + taken.reset(); | ||
| 125 | + } | ||
| 126 | + } | ||
| 127 | + } | ||
| 128 | + | ||
| 129 | + // TODO consider updating load whenever queue changes | ||
| 130 | + private void updateLoad() { | ||
| 131 | + underHighLoad.set(getQueue().remainingCapacity() / (double) maxQueueSize < 0.2); | ||
| 132 | + } | ||
| 133 | + | ||
| 134 | + /** | ||
| 135 | + * Feedback policy that delays the caller's thread until the executor's work | ||
| 136 | + * queue falls below a threshold, then runs the job on the caller's thread. | ||
| 137 | + */ | ||
| 138 | + private static final class CallerFeedbackPolicy implements RejectedExecutionHandler { | ||
| 139 | + | ||
| 140 | + private final BlockingBoolean underLoad = new BlockingBoolean(false); | ||
| 141 | + | ||
| 142 | + public BlockingBoolean load() { | ||
| 143 | + return underLoad; | ||
| 144 | + } | ||
| 145 | + | ||
| 146 | + /** | ||
| 147 | + * Executes task r in the caller's thread, unless the executor | ||
| 148 | + * has been shut down, in which case the task is discarded. | ||
| 149 | + * | ||
| 150 | + * @param r the runnable task requested to be executed | ||
| 151 | + * @param e the executor attempting to execute this task | ||
| 152 | + */ | ||
| 153 | + public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { | ||
| 154 | + if (!e.isShutdown()) { | ||
| 155 | + // Wait for up to 1 second while the queue drains... | ||
| 156 | + boolean notified = false; | ||
| 157 | + try { | ||
| 158 | + notified = underLoad.await(false, 1, TimeUnit.SECONDS); | ||
| 159 | + } catch (InterruptedException exception) { | ||
| 160 | + log.debug("Got exception waiting for notification:", exception); | ||
| 161 | + } finally { | ||
| 162 | + if (!notified) { | ||
| 163 | + log.info("Waited for 1 second on {}. Proceeding with work...", | ||
| 164 | + Thread.currentThread().getName()); | ||
| 165 | + } else { | ||
| 166 | + log.info("FIXME we got a notice"); | ||
| 167 | + } | ||
| 168 | + } | ||
| 169 | + // Do the work on the submitter's thread | ||
| 170 | + r.run(); | ||
| 171 | + } | ||
| 172 | + } | ||
| 173 | + } | ||
| 174 | +} | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| 1 | +/* | ||
| 2 | + * Copyright 2015 Open Networking Laboratory | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | +package org.onlab.util; | ||
| 17 | + | ||
| 18 | +import org.apache.commons.lang.mutable.MutableBoolean; | ||
| 19 | +import org.junit.Ignore; | ||
| 20 | +import org.junit.Test; | ||
| 21 | + | ||
| 22 | +import java.util.concurrent.CountDownLatch; | ||
| 23 | +import java.util.concurrent.ExecutorService; | ||
| 24 | +import java.util.concurrent.Executors; | ||
| 25 | +import java.util.concurrent.TimeUnit; | ||
| 26 | + | ||
| 27 | +import static org.junit.Assert.*; | ||
| 28 | + | ||
| 29 | +/** | ||
| 30 | + * Tests of the BlockingBoolean utility. | ||
| 31 | + */ | ||
| 32 | +public class BlockingBooleanTest { | ||
| 33 | + | ||
| 34 | + @Test | ||
| 35 | + public void basics() { | ||
| 36 | + BlockingBoolean b = new BlockingBoolean(false); | ||
| 37 | + assertEquals(false, b.get()); | ||
| 38 | + b.set(true); | ||
| 39 | + assertEquals(true, b.get()); | ||
| 40 | + b.set(true); | ||
| 41 | + assertEquals(true, b.get()); | ||
| 42 | + b.set(false); | ||
| 43 | + assertEquals(false, b.get()); | ||
| 44 | + } | ||
| 45 | + | ||
| 46 | + private void waitChange(boolean value, int numThreads) { | ||
| 47 | + BlockingBoolean b = new BlockingBoolean(!value); | ||
| 48 | + | ||
| 49 | + CountDownLatch latch = new CountDownLatch(numThreads); | ||
| 50 | + ExecutorService exec = Executors.newFixedThreadPool(numThreads); | ||
| 51 | + for (int i = 0; i < numThreads; i++) { | ||
| 52 | + exec.submit(() -> { | ||
| 53 | + try { | ||
| 54 | + b.await(value); | ||
| 55 | + latch.countDown(); | ||
| 56 | + } catch (InterruptedException e) { | ||
| 57 | + fail(); | ||
| 58 | + } | ||
| 59 | + }); | ||
| 60 | + } | ||
| 61 | + b.set(value); | ||
| 62 | + try { | ||
| 63 | + assertTrue(latch.await(10, TimeUnit.MILLISECONDS)); | ||
| 64 | + } catch (InterruptedException e) { | ||
| 65 | + fail(); | ||
| 66 | + } | ||
| 67 | + exec.shutdown(); | ||
| 68 | + } | ||
| 69 | + | ||
| 70 | + @Test | ||
| 71 | + public void waitTrueChange() { | ||
| 72 | + waitChange(true, 4); | ||
| 73 | + } | ||
| 74 | + | ||
| 75 | + @Test | ||
| 76 | + public void waitFalseChange() { | ||
| 77 | + waitChange(false, 4); | ||
| 78 | + } | ||
| 79 | + | ||
| 80 | + @Test | ||
| 81 | + public void waitSame() { | ||
| 82 | + BlockingBoolean b = new BlockingBoolean(true); | ||
| 83 | + | ||
| 84 | + CountDownLatch latch = new CountDownLatch(1); | ||
| 85 | + ExecutorService exec = Executors.newSingleThreadExecutor(); | ||
| 86 | + exec.submit(() -> { | ||
| 87 | + try { | ||
| 88 | + b.await(true); | ||
| 89 | + latch.countDown(); | ||
| 90 | + } catch (InterruptedException e) { | ||
| 91 | + fail(); | ||
| 92 | + } | ||
| 93 | + }); | ||
| 94 | + try { | ||
| 95 | + assertTrue(latch.await(10, TimeUnit.MILLISECONDS)); | ||
| 96 | + } catch (InterruptedException e) { | ||
| 97 | + fail(); | ||
| 98 | + } | ||
| 99 | + exec.shutdown(); | ||
| 100 | + } | ||
| 101 | + | ||
| 102 | + @Test | ||
| 103 | + public void someWait() { | ||
| 104 | + BlockingBoolean b = new BlockingBoolean(false); | ||
| 105 | + | ||
| 106 | + int numThreads = 4; | ||
| 107 | + CountDownLatch sameLatch = new CountDownLatch(numThreads / 2); | ||
| 108 | + CountDownLatch waitLatch = new CountDownLatch(numThreads / 2); | ||
| 109 | + | ||
| 110 | + ExecutorService exec = Executors.newFixedThreadPool(numThreads); | ||
| 111 | + for (int i = 0; i < numThreads; i++) { | ||
| 112 | + final boolean value = (i % 2 == 1); | ||
| 113 | + exec.submit(() -> { | ||
| 114 | + try { | ||
| 115 | + b.await(value); | ||
| 116 | + if (value) { | ||
| 117 | + waitLatch.countDown(); | ||
| 118 | + } else { | ||
| 119 | + sameLatch.countDown(); | ||
| 120 | + } | ||
| 121 | + } catch (InterruptedException e) { | ||
| 122 | + fail(); | ||
| 123 | + } | ||
| 124 | + }); | ||
| 125 | + } | ||
| 126 | + try { | ||
| 127 | + assertTrue(sameLatch.await(10, TimeUnit.MILLISECONDS)); | ||
| 128 | + assertEquals(waitLatch.getCount(), numThreads / 2); | ||
| 129 | + } catch (InterruptedException e) { | ||
| 130 | + fail(); | ||
| 131 | + } | ||
| 132 | + b.set(true); | ||
| 133 | + try { | ||
| 134 | + assertTrue(waitLatch.await(10, TimeUnit.MILLISECONDS)); | ||
| 135 | + } catch (InterruptedException e) { | ||
| 136 | + fail(); | ||
| 137 | + } | ||
| 138 | + exec.shutdown(); | ||
| 139 | + } | ||
| 140 | + | ||
| 141 | + @Test | ||
| 142 | + public void waitTimeout() { | ||
| 143 | + BlockingBoolean b = new BlockingBoolean(true); | ||
| 144 | + | ||
| 145 | + CountDownLatch latch = new CountDownLatch(1); | ||
| 146 | + ExecutorService exec = Executors.newSingleThreadExecutor(); | ||
| 147 | + exec.submit(() -> { | ||
| 148 | + try { | ||
| 149 | + if (!b.await(false, 1, TimeUnit.NANOSECONDS)) { | ||
| 150 | + latch.countDown(); | ||
| 151 | + } else { | ||
| 152 | + fail(); | ||
| 153 | + } | ||
| 154 | + } catch (InterruptedException e) { | ||
| 155 | + fail(); | ||
| 156 | + } | ||
| 157 | + }); | ||
| 158 | + try { | ||
| 159 | + assertTrue(latch.await(10, TimeUnit.MILLISECONDS)); | ||
| 160 | + } catch (InterruptedException e) { | ||
| 161 | + fail(); | ||
| 162 | + } | ||
| 163 | + exec.shutdown(); | ||
| 164 | + | ||
| 165 | + } | ||
| 166 | + | ||
| 167 | + @Test | ||
| 168 | + @Ignore | ||
| 169 | + public void samePerf() { | ||
| 170 | + int iters = 10_000; | ||
| 171 | + | ||
| 172 | + BlockingBoolean b1 = new BlockingBoolean(false); | ||
| 173 | + long t1 = System.nanoTime(); | ||
| 174 | + for (int i = 0; i < iters; i++) { | ||
| 175 | + b1.set(false); | ||
| 176 | + } | ||
| 177 | + long t2 = System.nanoTime(); | ||
| 178 | + MutableBoolean b2 = new MutableBoolean(false); | ||
| 179 | + for (int i = 0; i < iters; i++) { | ||
| 180 | + b2.setValue(false); | ||
| 181 | + } | ||
| 182 | + long t3 = System.nanoTime(); | ||
| 183 | + System.out.println((t2 - t1) + " " + (t3 - t2) + " " + ((t2 - t1) <= (t3 - t2))); | ||
| 184 | + } | ||
| 185 | + | ||
| 186 | + @Test | ||
| 187 | + @Ignore | ||
| 188 | + public void changePerf() { | ||
| 189 | + int iters = 10_000; | ||
| 190 | + | ||
| 191 | + BlockingBoolean b1 = new BlockingBoolean(false); | ||
| 192 | + boolean v = true; | ||
| 193 | + long t1 = System.nanoTime(); | ||
| 194 | + for (int i = 0; i < iters; i++) { | ||
| 195 | + b1.set(v); | ||
| 196 | + v = !v; | ||
| 197 | + } | ||
| 198 | + long t2 = System.nanoTime(); | ||
| 199 | + MutableBoolean b2 = new MutableBoolean(false); | ||
| 200 | + for (int i = 0; i < iters; i++) { | ||
| 201 | + b2.setValue(v); | ||
| 202 | + v = !v; | ||
| 203 | + } | ||
| 204 | + long t3 = System.nanoTime(); | ||
| 205 | + System.out.println((t2 - t1) + " " + (t3 - t2) + " " + ((t2 - t1) <= (t3 - t2))); | ||
| 206 | + } | ||
| 207 | + | ||
| 208 | +} |
| 1 | +/* | ||
| 2 | + * Copyright 2015 Open Networking Laboratory | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | +package org.onlab.util; | ||
| 17 | + | ||
| 18 | +import com.google.common.collect.Lists; | ||
| 19 | +import org.junit.Test; | ||
| 20 | + | ||
| 21 | +import java.util.List; | ||
| 22 | +import java.util.concurrent.CountDownLatch; | ||
| 23 | +import java.util.concurrent.ExecutionException; | ||
| 24 | +import java.util.concurrent.ExecutorService; | ||
| 25 | +import java.util.concurrent.Executors; | ||
| 26 | +import java.util.concurrent.Future; | ||
| 27 | +import java.util.concurrent.TimeUnit; | ||
| 28 | +import java.util.concurrent.atomic.AtomicBoolean; | ||
| 29 | + | ||
| 30 | +import static org.junit.Assert.*; | ||
| 31 | +import static org.onlab.util.BoundedThreadPool.*; | ||
| 32 | +import static org.onlab.util.Tools.namedThreads; | ||
| 33 | + | ||
| 34 | +/** | ||
| 35 | + * Test of BoundedThreadPool. | ||
| 36 | + */ | ||
| 37 | +public final class BoundedThreadPoolTest { | ||
| 38 | + | ||
| 39 | + @Test | ||
| 40 | + public void simpleJob() { | ||
| 41 | + final Thread myThread = Thread.currentThread(); | ||
| 42 | + final AtomicBoolean sameThread = new AtomicBoolean(true); | ||
| 43 | + final CountDownLatch latch = new CountDownLatch(1); | ||
| 44 | + | ||
| 45 | + BoundedThreadPool exec = newSingleThreadExecutor(namedThreads("test")); | ||
| 46 | + exec.submit(() -> { | ||
| 47 | + sameThread.set(myThread.equals(Thread.currentThread())); | ||
| 48 | + latch.countDown(); | ||
| 49 | + }); | ||
| 50 | + | ||
| 51 | + try { | ||
| 52 | + assertTrue("Job not run", latch.await(100, TimeUnit.MILLISECONDS)); | ||
| 53 | + assertFalse("Runnable used caller thread", sameThread.get()); | ||
| 54 | + } catch (InterruptedException e) { | ||
| 55 | + fail(); | ||
| 56 | + } finally { | ||
| 57 | + exec.shutdown(); | ||
| 58 | + } | ||
| 59 | + | ||
| 60 | + // TODO perhaps move to tearDown | ||
| 61 | + try { | ||
| 62 | + assertTrue(exec.awaitTermination(1, TimeUnit.SECONDS)); | ||
| 63 | + } catch (InterruptedException e) { | ||
| 64 | + fail(); | ||
| 65 | + } | ||
| 66 | + } | ||
| 67 | + | ||
| 68 | + private List<CountDownLatch> fillExecutor(BoundedThreadPool exec) { | ||
| 69 | + int numThreads = exec.getMaximumPoolSize(); | ||
| 70 | + List<CountDownLatch> latches = Lists.newArrayList(); | ||
| 71 | + final CountDownLatch started = new CountDownLatch(numThreads); | ||
| 72 | + List<CountDownLatch> finished = Lists.newArrayList(); | ||
| 73 | + | ||
| 74 | + // seed the executor's threads | ||
| 75 | + for (int i = 0; i < numThreads; i++) { | ||
| 76 | + final CountDownLatch latch = new CountDownLatch(1); | ||
| 77 | + final CountDownLatch fin = new CountDownLatch(1); | ||
| 78 | + latches.add(latch); | ||
| 79 | + finished.add(fin); | ||
| 80 | + exec.submit(() -> { | ||
| 81 | + try { | ||
| 82 | + started.countDown(); | ||
| 83 | + latch.await(); | ||
| 84 | + fin.countDown(); | ||
| 85 | + } catch (InterruptedException e) { | ||
| 86 | + fail(); | ||
| 87 | + } | ||
| 88 | + }); | ||
| 89 | + } | ||
| 90 | + try { | ||
| 91 | + assertTrue(started.await(100, TimeUnit.MILLISECONDS)); | ||
| 92 | + } catch (InterruptedException e) { | ||
| 93 | + fail(); | ||
| 94 | + } | ||
| 95 | + // fill the queue | ||
| 96 | + CountDownLatch startedBlocked = new CountDownLatch(1); | ||
| 97 | + while (exec.getQueue().remainingCapacity() > 0) { | ||
| 98 | + final CountDownLatch latch = new CountDownLatch(1); | ||
| 99 | + latches.add(latch); | ||
| 100 | + exec.submit(() -> { | ||
| 101 | + try { | ||
| 102 | + startedBlocked.countDown(); | ||
| 103 | + latch.await(); | ||
| 104 | + } catch (InterruptedException e) { | ||
| 105 | + fail(); | ||
| 106 | + } | ||
| 107 | + }); | ||
| 108 | + } | ||
| 109 | + | ||
| 110 | + latches.remove(0).countDown(); // release one of the executors | ||
| 111 | + // ... we need to do this because load is recomputed when jobs are taken | ||
| 112 | + // Note: For this to work, 1 / numThreads must be less than the load threshold (0.2) | ||
| 113 | + | ||
| 114 | + // verify that the old job has terminated | ||
| 115 | + try { | ||
| 116 | + assertTrue("Job didn't finish", | ||
| 117 | + finished.remove(0).await(100, TimeUnit.MILLISECONDS)); | ||
| 118 | + } catch (InterruptedException e) { | ||
| 119 | + fail(); | ||
| 120 | + } | ||
| 121 | + | ||
| 122 | + // verify that a previously blocked thread has started | ||
| 123 | + try { | ||
| 124 | + assertTrue(startedBlocked.await(10, TimeUnit.MILLISECONDS)); | ||
| 125 | + } catch (InterruptedException e) { | ||
| 126 | + fail(); | ||
| 127 | + } | ||
| 128 | + | ||
| 129 | + | ||
| 130 | + // add another job to fill the queue | ||
| 131 | + final CountDownLatch latch = new CountDownLatch(1); | ||
| 132 | + latches.add(latch); | ||
| 133 | + exec.submit(() -> { | ||
| 134 | + try { | ||
| 135 | + latch.await(); | ||
| 136 | + } catch (InterruptedException e) { | ||
| 137 | + fail(); | ||
| 138 | + } | ||
| 139 | + }); | ||
| 140 | + assertEquals(exec.getQueue().size(), maxQueueSize); | ||
| 141 | + | ||
| 142 | + return latches; | ||
| 143 | + } | ||
| 144 | + | ||
| 145 | + @Test | ||
| 146 | + public void releaseOneThread() { | ||
| 147 | + maxQueueSize = 10; | ||
| 148 | + BoundedThreadPool exec = newFixedThreadPool(4, namedThreads("test")); | ||
| 149 | + List<CountDownLatch> latches = fillExecutor(exec); | ||
| 150 | + | ||
| 151 | + CountDownLatch myLatch = new CountDownLatch(1); | ||
| 152 | + ExecutorService myExec = Executors.newSingleThreadExecutor(); | ||
| 153 | + Future<Thread> expected = myExec.submit(Thread::currentThread); | ||
| 154 | + | ||
| 155 | + assertEquals(exec.getQueue().size(), maxQueueSize); | ||
| 156 | + long start = System.nanoTime(); | ||
| 157 | + Future<Thread> actual = myExec.submit(() -> { | ||
| 158 | + return exec.submit(() -> { | ||
| 159 | + myLatch.countDown(); | ||
| 160 | + return Thread.currentThread(); | ||
| 161 | + }).get(); | ||
| 162 | + }); | ||
| 163 | + | ||
| 164 | + try { | ||
| 165 | + assertFalse("Thread should still be blocked", | ||
| 166 | + myLatch.await(10, TimeUnit.MILLISECONDS)); | ||
| 167 | + | ||
| 168 | + latches.remove(0).countDown(); // release the first thread | ||
| 169 | + assertFalse("Thread should still be blocked", | ||
| 170 | + myLatch.await(10, TimeUnit.MILLISECONDS)); | ||
| 171 | + latches.remove(0).countDown(); // release the second thread | ||
| 172 | + | ||
| 173 | + assertTrue("Thread should be unblocked", | ||
| 174 | + myLatch.await(10, TimeUnit.MILLISECONDS)); | ||
| 175 | + long delta = System.nanoTime() - start; | ||
| 176 | + double load = exec.getQueue().size() / (double) maxQueueSize; | ||
| 177 | + assertTrue("Load is greater than threshold", load <= 0.8); | ||
| 178 | + assertTrue("Load is less than threshold", load >= 0.6); | ||
| 179 | + assertEquals("Work done on wrong thread", expected.get(), actual.get()); | ||
| 180 | + assertTrue("Took more than one second", delta < Math.pow(10, 9)); | ||
| 181 | + } catch (InterruptedException | ExecutionException e) { | ||
| 182 | + fail(); | ||
| 183 | + } finally { | ||
| 184 | + latches.forEach(CountDownLatch::countDown); | ||
| 185 | + exec.shutdown(); | ||
| 186 | + } | ||
| 187 | + | ||
| 188 | + // TODO perhaps move to tearDown | ||
| 189 | + try { | ||
| 190 | + assertTrue(exec.awaitTermination(1, TimeUnit.SECONDS)); | ||
| 191 | + } catch (InterruptedException e) { | ||
| 192 | + fail(); | ||
| 193 | + } | ||
| 194 | + | ||
| 195 | + } | ||
| 196 | + | ||
| 197 | + @Test | ||
| 198 | + public void highLoadTimeout() { | ||
| 199 | + maxQueueSize = 10; | ||
| 200 | + BoundedThreadPool exec = newFixedThreadPool(2, namedThreads("test")); | ||
| 201 | + List<CountDownLatch> latches = fillExecutor(exec); | ||
| 202 | + | ||
| 203 | + // true if the job is executed and it is done on the test thread | ||
| 204 | + final AtomicBoolean sameThread = new AtomicBoolean(false); | ||
| 205 | + final Thread myThread = Thread.currentThread(); | ||
| 206 | + long start = System.nanoTime(); | ||
| 207 | + exec.submit(() -> { | ||
| 208 | + sameThread.set(myThread.equals(Thread.currentThread())); | ||
| 209 | + }); | ||
| 210 | + | ||
| 211 | + long delta = System.nanoTime() - start; | ||
| 212 | + assertEquals(maxQueueSize, exec.getQueue().size()); | ||
| 213 | + assertTrue("Work done on wrong thread (or didn't happen)", sameThread.get()); | ||
| 214 | + assertTrue("Took less than one second. Actual: " + delta / 1_000_000.0 + "ms", | ||
| 215 | + delta > Math.pow(10, 9)); | ||
| 216 | + assertTrue("Took more than two seconds", delta < 2 * Math.pow(10, 9)); | ||
| 217 | + latches.forEach(CountDownLatch::countDown); | ||
| 218 | + exec.shutdown(); | ||
| 219 | + | ||
| 220 | + // TODO perhaps move to tearDown | ||
| 221 | + try { | ||
| 222 | + assertTrue(exec.awaitTermination(1, TimeUnit.SECONDS)); | ||
| 223 | + } catch (InterruptedException e) { | ||
| 224 | + fail(); | ||
| 225 | + } | ||
| 226 | + } | ||
| 227 | +} | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
-
Please register or login to post a comment