Brian O'Connor
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
...@@ -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