) () -> readPropertyRaw(name, defVal));
151 | } else {
152 | return readPropertyRaw(name, defVal);
153 | }
154 | }
155 |
156 | static String readPropertyRaw(final String name, final String defVal) {
157 | return System.getProperty(name, defVal);
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/src/main/java/org/jboss/threads/Waiter.java:
--------------------------------------------------------------------------------
1 | package org.jboss.threads;
2 |
3 | /**
4 | */
5 | final class Waiter {
6 | private volatile Thread thread;
7 | private Waiter next;
8 |
9 | Waiter(final Waiter next) {
10 | this.next = next;
11 | }
12 |
13 | Thread getThread() {
14 | return thread;
15 | }
16 |
17 | Waiter setThread(final Thread thread) {
18 | this.thread = thread;
19 | return this;
20 | }
21 |
22 | Waiter getNext() {
23 | return next;
24 | }
25 |
26 | Waiter setNext(final Waiter next) {
27 | this.next = next;
28 | return this;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/org/jboss/threads/management/ManageableThreadPoolExecutorService.java:
--------------------------------------------------------------------------------
1 | package org.jboss.threads.management;
2 |
3 | import java.util.concurrent.ExecutorService;
4 |
5 | import io.smallrye.common.constraint.NotNull;
6 |
7 | /**
8 | * A thread pool for which an MBean can be obtained.
9 | */
10 | public interface ManageableThreadPoolExecutorService extends ExecutorService {
11 | /**
12 | * Create or acquire an MXBean instance for this thread pool. Note that the thread pool itself will not
13 | * do anything in particular to register (or unregister) the MXBean with a JMX server; that is the caller's
14 | * responsibility.
15 | *
16 | * @return the MXBean instance (must not be {@code null})
17 | */
18 | @NotNull
19 | StandardThreadPoolMXBean getThreadPoolMXBean();
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/org/jboss/threads/management/StandardThreadPoolMXBean.java:
--------------------------------------------------------------------------------
1 | package org.jboss.threads.management;
2 |
3 | /**
4 | * An MXBean which contains the attributes and operations found on all standard thread pools.
5 | */
6 | public interface StandardThreadPoolMXBean {
7 | /**
8 | * Get the pool size growth resistance factor. If the thread pool does not support growth resistance,
9 | * then {@code 0.0} (no resistance) is returned.
10 | *
11 | * @return the growth resistance factor ({@code 0.0 ≤ n ≤ 1.0})
12 | */
13 | float getGrowthResistance();
14 |
15 | /**
16 | * Set the pool size growth resistance factor, if supported.
17 | *
18 | * @param value the growth resistance factor ({@code 0.0 ≤ n ≤ 1.0})
19 | */
20 | void setGrowthResistance(float value);
21 |
22 | /**
23 | * Determine whether the thread pool supports a growth resistance factor.
24 | *
25 | * @return {@code true} if the growth resistance factor is supported, {@code false} otherwise
26 | */
27 | boolean isGrowthResistanceSupported();
28 |
29 | /**
30 | * Get the core pool size. This is the size below which new threads will always be created if no idle threads
31 | * are available. If the thread pool does not support a separate core pool size, this size will match the
32 | * maximum size.
33 | *
34 | * @return the core pool size
35 | */
36 | int getCorePoolSize();
37 |
38 | /**
39 | * Set the core pool size. If the configured maximum pool size is less than the configured core size, the
40 | * core size will be reduced to match the maximum size when the thread pool is constructed. If the thread pool
41 | * does not support a separate core pool size, this setting will be ignored.
42 | *
43 | * @param corePoolSize the core pool size (must be greater than or equal to 0)
44 | */
45 | void setCorePoolSize(int corePoolSize);
46 |
47 | /**
48 | * Determine whether this implementation supports a separate core pool size.
49 | *
50 | * @return {@code true} if a separate core size is supported; {@code false} otherwise
51 | */
52 | boolean isCorePoolSizeSupported();
53 |
54 | /**
55 | * Attempt to start a core thread without submitting work to it.
56 | *
57 | * @return {@code true} if the thread was started, or {@code false} if the pool is filled to the core size, the
58 | * thread could not be created, or prestart of core threads is not supported.
59 | */
60 | boolean prestartCoreThread();
61 |
62 | /**
63 | * Attempt to start all core threads. This is usually equivalent to calling {@link #prestartCoreThread()} in a loop
64 | * until it returns {@code false} and counting the {@code true} results.
65 | *
66 | * @return the number of started core threads (may be 0)
67 | */
68 | int prestartAllCoreThreads();
69 |
70 | /**
71 | * Determine whether this thread pool allows manual pre-start of core threads.
72 | *
73 | * @return {@code true} if pre-starting core threads is supported, {@code false} otherwise
74 | */
75 | boolean isCoreThreadPrestartSupported();
76 |
77 | /**
78 | * Get the maximum pool size. This is the absolute upper limit to the size of the thread pool.
79 | *
80 | * @return the maximum pool size
81 | */
82 | int getMaximumPoolSize();
83 |
84 | /**
85 | * Set the maximum pool size. If the configured maximum pool size is less than the configured core size, the
86 | * core size will be reduced to match the maximum size when the thread pool is constructed.
87 | *
88 | * @param maxPoolSize the maximum pool size (must be greater than or equal to 0)
89 | */
90 | void setMaximumPoolSize(final int maxPoolSize);
91 |
92 | /**
93 | * Get an estimate of the current number of active threads in the pool.
94 | *
95 | * @return an estimate of the current number of active threads in the pool
96 | */
97 | int getPoolSize();
98 |
99 | /**
100 | * Get an estimate of the peak number of threads that the pool has ever held.
101 | *
102 | * @return an estimate of the peak number of threads that the pool has ever held
103 | */
104 | int getLargestPoolSize();
105 |
106 | /**
107 | * Get an estimate of the current number of active (busy) threads.
108 | *
109 | * @return an estimate of the active count
110 | */
111 | int getActiveCount();
112 |
113 | /**
114 | * Determine whether core threads are allowed to time out. A "core thread" is defined as any thread in the pool
115 | * when the pool size is below the pool's {@linkplain #getCorePoolSize() core pool size}. If the thread pool
116 | * does not support a separate core pool size, this method should return {@code false}.
117 | *
118 | * This method is named differently from the typical {@code allowsCoreThreadTimeOut()} in order to accommodate
119 | * the requirements of MXBean attribute methods.
120 | *
121 | * @return {@code true} if core threads are allowed to time out, {@code false} otherwise
122 | */
123 | boolean isAllowCoreThreadTimeOut();
124 |
125 | /**
126 | * Establish whether core threads are allowed to time out. A "core thread" is defined as any thread in the pool
127 | * when the pool size is below the pool's {@linkplain #getCorePoolSize() core pool size}. If the thread pool
128 | * does not support a separate core pool size, the value is ignored.
129 | *
130 | * This method is named differently from the typical {@code allowCoreThreadTimeOut(boolean)} in order to accommodate
131 | * the requirements of MXBean attribute methods.
132 | *
133 | * @param value {@code true} if core threads are allowed to time out, {@code false} otherwise
134 | */
135 | void setAllowCoreThreadTimeOut(boolean value);
136 |
137 | /**
138 | * Get the thread keep-alive time, in seconds.
139 | *
140 | * This method differs from the typical {@code getKeepAliveTime(TimeUnit)} due to the inability to send in a
141 | * time units parameter on an MXBean attribute. As such, the unit is hard-coded to seconds.
142 | *
143 | * @return the thread keep-alive time, in seconds
144 | */
145 | long getKeepAliveTimeSeconds();
146 |
147 | /**
148 | * Set the thread keep-alive time, in seconds.
149 | *
150 | * This method differs from the typical {@code getKeepAliveTime(TimeUnit)} due to the inability to send in a
151 | * time units parameter on an MXBean attribute. As such, the unit is hard-coded to seconds.
152 | *
153 | * @param seconds the thread keep-alive time, in seconds (must be greater than or equal to 0)
154 | */
155 | void setKeepAliveTimeSeconds(long seconds);
156 |
157 | /**
158 | * Get the maximum queue size for this thread pool. If there is no queue or it is not bounded, {@link Integer#MAX_VALUE} is
159 | * returned.
160 | *
161 | * @return the maximum queue size
162 | */
163 | int getMaximumQueueSize();
164 |
165 | /**
166 | * Set the maximum queue size for this thread pool. If the new maximum queue size is smaller than the current queue
167 | * size, there is no effect other than preventing tasks from being enqueued until the size decreases below the
168 | * maximum again. If changing the maximum queue size is not supported, or there is no bounded backing queue,
169 | * then the value is ignored.
170 | *
171 | * @param size the maximum queue size for this thread pool
172 | */
173 | void setMaximumQueueSize(int size);
174 |
175 | /**
176 | * Get an estimate of the current queue size, if any. If no backing queue exists, or its size cannot be determined,
177 | * this method will return 0.
178 | *
179 | * @return an estimate of the current queue size
180 | */
181 | int getQueueSize();
182 |
183 | /**
184 | * Get an estimate of the peak size of the queue, if any. If no backing queue exists, or its size cannot be determined,
185 | * this method will return 0.
186 | *
187 | * @return an estimate of the peak size of the queue
188 | */
189 | int getLargestQueueSize();
190 |
191 | /**
192 | * Determine whether there is a bounded queue backing this thread pool.
193 | *
194 | * @return {@code true} if there is a bounded backing queue, {@code false} otherwise
195 | */
196 | boolean isQueueBounded();
197 |
198 | /**
199 | * Determine whether the maximum queue size is modifiable.
200 | *
201 | * @return {@code true} if the queue size is modifiable, false otherwise
202 | */
203 | boolean isQueueSizeModifiable();
204 |
205 | /**
206 | * Determine whether shutdown was requested.
207 | *
208 | * @return {@code true} if shutdown was requested, {@code false} otherwise
209 | */
210 | boolean isShutdown();
211 |
212 | /**
213 | * Determine whether shutdown is in progress.
214 | *
215 | * @return {@code true} if shutdown is in progress, {@code false} otherwise
216 | */
217 | boolean isTerminating();
218 |
219 | /**
220 | * Determine whether shutdown is complete.
221 | *
222 | * @return {@code true} if shutdown is complete, {@code false} otherwise
223 | */
224 | boolean isTerminated();
225 |
226 | /**
227 | * Get an estimate of the total number of tasks ever submitted to this thread pool. This number may be zero
228 | * if the underlying thread pool does not support this metric.
229 | *
230 | * @return an estimate of the total number of tasks ever submitted to this thread pool
231 | */
232 | long getSubmittedTaskCount();
233 |
234 | /**
235 | * Get an estimate of the total number of tasks ever rejected by this thread pool for any reason. This number may be zero
236 | * if the underlying thread pool does not support this metric.
237 | *
238 | * @return an estimate of the total number of tasks ever rejected by this thread pool
239 | */
240 | long getRejectedTaskCount();
241 |
242 | /**
243 | * Get an estimate of the number of tasks completed by this thread pool. This number may be zero
244 | * if the underlying thread pool does not support this metric.
245 | *
246 | * @return an estimate of the number of tasks completed by this thread pool
247 | */
248 | long getCompletedTaskCount();
249 |
250 | /**
251 | * Get the number of spin misses that have occurred. Spin misses indicate that contention is not being properly
252 | * handled by the thread pool.
253 | *
254 | * @return an estimate of the number of spin misses
255 | */
256 | default long getSpinMissCount() {
257 | return 0;
258 | }
259 | }
260 |
--------------------------------------------------------------------------------
/src/main/java24/org/jboss/threads/JDKSpecific.java:
--------------------------------------------------------------------------------
1 | package org.jboss.threads;
2 |
3 | import java.lang.invoke.MethodHandle;
4 | import java.lang.invoke.MethodHandles;
5 | import java.lang.invoke.MethodType;
6 | import java.lang.invoke.VarHandle;
7 | import java.lang.reflect.UndeclaredThrowableException;
8 |
9 | final class JDKSpecific {
10 | private JDKSpecific() {}
11 |
12 | static void setThreadContextClassLoader(Thread thread, ClassLoader classLoader) {
13 | thread.setContextClassLoader(classLoader);
14 | }
15 |
16 | static ClassLoader getThreadContextClassLoader(Thread thread) {
17 | return thread.getContextClassLoader();
18 | }
19 |
20 | static final class ThreadAccess {
21 | private static final MethodHandle setThreadLocalsHandle;
22 | private static final MethodHandle setInheritableThreadLocalsHandle;
23 |
24 | static {
25 | try {
26 | MethodHandles.Lookup threadLookup = MethodHandles.privateLookupIn(Thread.class, MethodHandles.lookup());
27 | setThreadLocalsHandle = threadLookup.unreflectVarHandle(Thread.class.getDeclaredField("threadLocals")).toMethodHandle(VarHandle.AccessMode.SET).asType(MethodType.methodType(void.class, Thread.class, Object.class));
28 | setInheritableThreadLocalsHandle = threadLookup.unreflectVarHandle(Thread.class.getDeclaredField("inheritableThreadLocals")).toMethodHandle(VarHandle.AccessMode.SET).asType(MethodType.methodType(void.class, Thread.class, Object.class));
29 | } catch (IllegalAccessException e) {
30 | Module myModule = ThreadAccess.class.getModule();
31 | String myName = myModule.isNamed() ? myModule.getName() : "ALL-UNNAMED";
32 | throw new IllegalAccessError(e.getMessage() +
33 | "; to use the thread-local-reset capability on Java 24 or later, use this JVM option: --add-opens java.base/java.lang=" + myName);
34 | } catch (NoSuchFieldException e) {
35 | throw new NoSuchFieldError(e.getMessage());
36 | }
37 | }
38 |
39 | static void clearThreadLocals() {
40 | final Thread thread = Thread.currentThread();
41 | try {
42 | setThreadLocalsHandle.invokeExact(thread, (Object) null);
43 | setInheritableThreadLocalsHandle.invokeExact(thread, (Object) null);
44 | } catch (RuntimeException | Error e) {
45 | throw e;
46 | } catch (Throwable t) {
47 | throw new UndeclaredThrowableException(t);
48 | }
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/native-image/org.jboss.threads/jboss-threads/native-image.properties:
--------------------------------------------------------------------------------
1 | Args = --initialize-at-run-time=org.jboss.threads.EnhancedQueueExecutor$RuntimeFields
2 |
--------------------------------------------------------------------------------
/src/test/java/org/jboss/threads/ArrayQueueTests.java:
--------------------------------------------------------------------------------
1 | package org.jboss.threads;
2 |
3 | import static org.junit.jupiter.api.Assertions.assertEquals;
4 | import static org.junit.jupiter.api.Assertions.assertFalse;
5 | import static org.junit.jupiter.api.Assertions.assertNull;
6 | import static org.junit.jupiter.api.Assertions.assertSame;
7 | import static org.junit.jupiter.api.Assertions.assertTrue;
8 |
9 | import java.util.concurrent.TimeUnit;
10 |
11 | import org.junit.jupiter.api.AfterAll;
12 | import org.junit.jupiter.api.BeforeAll;
13 | import org.junit.jupiter.api.Test;
14 |
15 | public class ArrayQueueTests {
16 |
17 | static EnhancedQueueExecutor eqe;
18 |
19 | static EnhancedQueueExecutor.AbstractScheduledFuture>[] ITEMS;
20 |
21 | @BeforeAll
22 | public static void beforeAll() {
23 | eqe = new EnhancedQueueExecutor.Builder().build();
24 | ITEMS = new EnhancedQueueExecutor.AbstractScheduledFuture[32];
25 | for (int i = 0; i < 32; i ++) {
26 | final String toString = "[" + i + "]";
27 | ITEMS[i] = eqe.new RunnableScheduledFuture(new Runnable() {
28 | public void run() {
29 | // nothing
30 | }
31 |
32 | public String toString() {
33 | return toString;
34 | }
35 | }, 0, TimeUnit.DAYS);
36 | }
37 | }
38 |
39 | @Test
40 | public void testMoveForward() {
41 | EnhancedQueueExecutor.ArrayQueue aq = new EnhancedQueueExecutor.ArrayQueue(16);
42 |
43 | int head = 5;
44 | aq.testPoint_setHead(head);
45 | aq.testPoint_setArrayItem(head + 0, ITEMS[0]);
46 | aq.testPoint_setArrayItem(head + 1, ITEMS[1]);
47 | aq.testPoint_setArrayItem(head + 2, ITEMS[2]);
48 | aq.testPoint_setArrayItem(head + 3, ITEMS[3]);
49 | aq.testPoint_setSize(4);
50 |
51 | aq.moveForward(2, ITEMS[4]);
52 |
53 | assertEquals(head, aq.testPoint_head());
54 |
55 | assertSame(ITEMS[0], aq.testPoint_getArrayItem(head + 0));
56 | assertSame(ITEMS[1], aq.testPoint_getArrayItem(head + 1));
57 | assertSame(ITEMS[4], aq.testPoint_getArrayItem(head + 2));
58 | assertSame(ITEMS[2], aq.testPoint_getArrayItem(head + 3));
59 | assertSame(ITEMS[3], aq.testPoint_getArrayItem(head + 4));
60 | }
61 |
62 | @Test
63 | public void testMoveForwardWrap() {
64 | EnhancedQueueExecutor.ArrayQueue aq = new EnhancedQueueExecutor.ArrayQueue(16);
65 |
66 | int head = 14;
67 | aq.testPoint_setHead(head);
68 | aq.testPoint_setArrayItem(head + 0, ITEMS[0]);
69 | aq.testPoint_setArrayItem(head + 1, ITEMS[1]);
70 | aq.testPoint_setArrayItem(head + 2, ITEMS[2]);
71 | aq.testPoint_setArrayItem(head + 3, ITEMS[3]);
72 | aq.testPoint_setSize(4);
73 |
74 | aq.moveForward(2, ITEMS[4]);
75 |
76 | assertEquals(head, aq.testPoint_head());
77 |
78 | assertSame(ITEMS[0], aq.testPoint_getArrayItem(head + 0));
79 | assertSame(ITEMS[1], aq.testPoint_getArrayItem(head + 1));
80 | assertSame(ITEMS[4], aq.testPoint_getArrayItem(head + 2));
81 | assertSame(ITEMS[2], aq.testPoint_getArrayItem(head + 3));
82 | assertSame(ITEMS[3], aq.testPoint_getArrayItem(head + 4));
83 | }
84 |
85 | @Test
86 | public void testMoveBackward() {
87 | EnhancedQueueExecutor.ArrayQueue aq = new EnhancedQueueExecutor.ArrayQueue(16);
88 |
89 | int head = 5;
90 | aq.testPoint_setHead(head);
91 | aq.testPoint_setArrayItem(head + 0, ITEMS[0]);
92 | aq.testPoint_setArrayItem(head + 1, ITEMS[1]);
93 | aq.testPoint_setArrayItem(head + 2, ITEMS[2]);
94 | aq.testPoint_setArrayItem(head + 3, ITEMS[3]);
95 | aq.testPoint_setSize(4);
96 |
97 | aq.moveBackward(2, ITEMS[4]);
98 |
99 | assertEquals(head - 1, aq.testPoint_head());
100 | head--;
101 |
102 | assertSame(ITEMS[0], aq.testPoint_getArrayItem(head + 0));
103 | assertSame(ITEMS[1], aq.testPoint_getArrayItem(head + 1));
104 | assertSame(ITEMS[4], aq.testPoint_getArrayItem(head + 2));
105 | assertSame(ITEMS[2], aq.testPoint_getArrayItem(head + 3));
106 | assertSame(ITEMS[3], aq.testPoint_getArrayItem(head + 4));
107 | }
108 |
109 | @Test
110 | public void testMoveBackwardWrap() {
111 | EnhancedQueueExecutor.ArrayQueue aq = new EnhancedQueueExecutor.ArrayQueue(16);
112 |
113 | int head = 14;
114 | aq.testPoint_setHead(head);
115 | aq.testPoint_setArrayItem(head + 0, ITEMS[0]);
116 | aq.testPoint_setArrayItem(head + 1, ITEMS[1]);
117 | aq.testPoint_setArrayItem(head + 2, ITEMS[2]);
118 | aq.testPoint_setArrayItem(head + 3, ITEMS[3]);
119 | aq.testPoint_setSize(4);
120 |
121 | aq.moveBackward(2, ITEMS[4]);
122 |
123 | assertEquals(head - 1, aq.testPoint_head());
124 | head--;
125 |
126 | assertSame(ITEMS[0], aq.testPoint_getArrayItem(head + 0));
127 | assertSame(ITEMS[1], aq.testPoint_getArrayItem(head + 1));
128 | assertSame(ITEMS[4], aq.testPoint_getArrayItem(head + 2));
129 | assertSame(ITEMS[2], aq.testPoint_getArrayItem(head + 3));
130 | assertSame(ITEMS[3], aq.testPoint_getArrayItem(head + 4));
131 | }
132 |
133 | @Test
134 | public void testQueueBehavior() {
135 | EnhancedQueueExecutor.ArrayQueue aq = new EnhancedQueueExecutor.ArrayQueue(16);
136 |
137 | // tc 0 (n/a)
138 | assertTrue(aq.isEmpty());
139 | assertEquals(0, aq.size());
140 | assertEquals(0, aq.testPoint_head());
141 | assertEquals(16, aq.testPoint_arrayLength());
142 |
143 | // tc 1
144 | aq.insertAt(0, ITEMS[0]);
145 | assertFalse(aq.isEmpty());
146 | assertEquals(1, aq.size());
147 | assertEquals(0, aq.testPoint_head());
148 | assertSame(ITEMS[0], aq.testPoint_getArrayItem(0));
149 | assertNull(aq.testPoint_getArrayItem(1));
150 | assertNull(aq.testPoint_getArrayItem(15));
151 |
152 | // removal
153 | assertSame(ITEMS[0], aq.pollFirst());
154 | assertTrue(aq.isEmpty());
155 | assertEquals(0, aq.size());
156 | assertEquals(1, aq.testPoint_head());
157 | assertNull(aq.testPoint_getArrayItem(0));
158 | assertNull(aq.testPoint_getArrayItem(1));
159 |
160 | // tc 1 (but this time with head == 1)
161 | aq.insertAt(0, ITEMS[1]);
162 | assertFalse(aq.isEmpty());
163 | assertEquals(1, aq.size());
164 | assertEquals(1, aq.testPoint_head());
165 | assertNull(aq.testPoint_getArrayItem(0));
166 | assertSame(ITEMS[1], aq.testPoint_getArrayItem(1));
167 | assertNull(aq.testPoint_getArrayItem(2));
168 |
169 | // tc 1 (but with head == 1 and size == 1)
170 | aq.insertAt(1, ITEMS[2]);
171 | assertFalse(aq.isEmpty());
172 | assertEquals(2, aq.size());
173 | assertEquals(1, aq.testPoint_head());
174 | assertNull(aq.testPoint_getArrayItem(0));
175 | assertSame(ITEMS[1], aq.testPoint_getArrayItem(1));
176 | assertSame(ITEMS[2], aq.testPoint_getArrayItem(2));
177 | assertNull(aq.testPoint_getArrayItem(3));
178 |
179 | // tc 2 (but with head == 1 and size == 2)
180 | aq.insertAt(0, ITEMS[3]);
181 | assertFalse(aq.isEmpty());
182 | assertEquals(3, aq.size()); // halfSize == 2
183 | // head moves back to 0
184 | assertEquals(0, aq.testPoint_head());
185 | assertNull(aq.testPoint_getArrayItem(15));
186 | assertSame(ITEMS[3], aq.testPoint_getArrayItem(0));
187 | assertSame(ITEMS[1], aq.testPoint_getArrayItem(1));
188 | assertSame(ITEMS[2], aq.testPoint_getArrayItem(2));
189 | assertNull(aq.testPoint_getArrayItem(3));
190 |
191 | // tc 2 (but with head == 0 and size == 3)
192 | aq.insertAt(0, ITEMS[4]);
193 | assertFalse(aq.isEmpty());
194 | assertEquals(4, aq.size());
195 | // head wraps around to 15
196 | assertEquals(15, aq.testPoint_head());
197 | assertNull(aq.testPoint_getArrayItem(14));
198 | assertSame(ITEMS[4], aq.testPoint_getArrayItem(15));
199 | assertSame(ITEMS[3], aq.testPoint_getArrayItem(0));
200 | assertSame(ITEMS[1], aq.testPoint_getArrayItem(1));
201 | assertSame(ITEMS[2], aq.testPoint_getArrayItem(2));
202 | assertNull(aq.testPoint_getArrayItem(3));
203 |
204 | // tc 2 (but with head == 15 and size == 4)
205 | aq.insertAt(0, ITEMS[5]);
206 | assertFalse(aq.isEmpty());
207 | assertEquals(5, aq.size());
208 | assertEquals(14, aq.testPoint_head());
209 | assertNull(aq.testPoint_getArrayItem(13));
210 | assertSame(ITEMS[5], aq.testPoint_getArrayItem(14));
211 | assertSame(ITEMS[4], aq.testPoint_getArrayItem(15));
212 | assertSame(ITEMS[3], aq.testPoint_getArrayItem(0));
213 | assertSame(ITEMS[1], aq.testPoint_getArrayItem(1));
214 | assertSame(ITEMS[2], aq.testPoint_getArrayItem(2));
215 | assertNull(aq.testPoint_getArrayItem(3));
216 |
217 | // tc
218 | aq.insertAt(1, ITEMS[6]);
219 |
220 | assertNull(aq.testPoint_getArrayItem(12));
221 | assertSame(ITEMS[5], aq.testPoint_getArrayItem(13));
222 | assertSame(ITEMS[6], aq.testPoint_getArrayItem(14));
223 | assertSame(ITEMS[4], aq.testPoint_getArrayItem(15));
224 | assertSame(ITEMS[3], aq.testPoint_getArrayItem(0));
225 | assertSame(ITEMS[1], aq.testPoint_getArrayItem(1));
226 | assertSame(ITEMS[2], aq.testPoint_getArrayItem(2));
227 | assertNull(aq.testPoint_getArrayItem(3));
228 |
229 | aq.insertAt(0, ITEMS[7]);
230 |
231 | assertNull(aq.testPoint_getArrayItem(11));
232 | assertSame(ITEMS[7], aq.testPoint_getArrayItem(12));
233 | assertSame(ITEMS[5], aq.testPoint_getArrayItem(13));
234 | assertSame(ITEMS[6], aq.testPoint_getArrayItem(14));
235 | assertSame(ITEMS[4], aq.testPoint_getArrayItem(15));
236 | assertSame(ITEMS[3], aq.testPoint_getArrayItem(0));
237 | assertSame(ITEMS[1], aq.testPoint_getArrayItem(1));
238 | assertSame(ITEMS[2], aq.testPoint_getArrayItem(2));
239 | assertNull(aq.testPoint_getArrayItem(3));
240 | }
241 |
242 | @AfterAll
243 | public static void afterAll() throws InterruptedException {
244 | try {
245 | eqe.shutdown();
246 | eqe.awaitTermination(30, TimeUnit.SECONDS);
247 | } finally {
248 | eqe = null;
249 | }
250 | }
251 | }
252 |
--------------------------------------------------------------------------------
/src/test/java/org/jboss/threads/DeferredInterruptTestCase.java:
--------------------------------------------------------------------------------
1 | package org.jboss.threads;
2 |
3 | import java.util.concurrent.CountDownLatch;
4 | import java.util.concurrent.atomic.AtomicBoolean;
5 | import java.util.concurrent.locks.LockSupport;
6 |
7 | import org.junit.jupiter.api.Test;
8 |
9 | import static org.junit.jupiter.api.Assertions.assertTrue;
10 |
11 | /**
12 | * @author David M. Lloyd
13 | */
14 | public class DeferredInterruptTestCase {
15 |
16 | @Test
17 | public void testDeferral() throws Exception {
18 | final AtomicBoolean delivered0 = new AtomicBoolean();
19 | final AtomicBoolean deferred = new AtomicBoolean();
20 | final AtomicBoolean delivered = new AtomicBoolean();
21 | final CountDownLatch latch1 = new CountDownLatch(1);
22 | final CountDownLatch latch2 = new CountDownLatch(1);
23 | final JBossThread thread = new JBossThread(new Runnable() {
24 | public void run() {
25 | Thread.interrupted();
26 | latch1.countDown();
27 | // now wait
28 | LockSupport.parkNanos(3000000000L);
29 | if (! Thread.currentThread().isInterrupted()) {
30 | // spurious unpark, perhaps
31 | LockSupport.parkNanos(3000000000L);
32 | }
33 | delivered0.set(Thread.interrupted());
34 | JBossThread.executeWithInterruptDeferred(new Runnable() {
35 | public void run() {
36 | latch2.countDown();
37 | LockSupport.parkNanos(500000000L);
38 | deferred.set(! Thread.interrupted());
39 | }
40 | });
41 | delivered.set(Thread.interrupted());
42 | }
43 | });
44 | thread.start();
45 | latch1.await();
46 | thread.interrupt();
47 | latch2.await();
48 | thread.interrupt();
49 | thread.join();
50 | assertTrue(delivered0.get());
51 | assertTrue(deferred.get());
52 | assertTrue(delivered.get());
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/test/java/org/jboss/threads/EnhancedQueueExecutorTest.java:
--------------------------------------------------------------------------------
1 | package org.jboss.threads;
2 |
3 | import java.time.Duration;
4 | import java.util.concurrent.CountDownLatch;
5 | import java.util.concurrent.ExecutorService;
6 | import java.util.concurrent.RejectedExecutionException;
7 | import java.util.concurrent.TimeUnit;
8 | import java.util.concurrent.TimeoutException;
9 | import java.util.concurrent.atomic.AtomicInteger;
10 |
11 | import org.junit.jupiter.api.Disabled;
12 | import org.junit.jupiter.api.Test;
13 |
14 | import static org.junit.jupiter.api.Assertions.assertEquals;
15 | import static org.junit.jupiter.api.Assertions.assertFalse;
16 | import static org.junit.jupiter.api.Assertions.assertTrue;
17 | import static org.junit.jupiter.api.Assertions.fail;
18 |
19 | public class EnhancedQueueExecutorTest {
20 | private int coreSize = 3;
21 | private int maxSize = coreSize * 2;
22 | private long keepaliveTimeMillis = 1000;
23 |
24 | class TestTask implements Runnable {
25 | private long sleepTime = 0;
26 |
27 | public TestTask withSleepTime(long sleepTime) {
28 | if (sleepTime > 0) {
29 | this.sleepTime = sleepTime;
30 | }
31 | return this;
32 | }
33 |
34 | @Override
35 | public void run() {
36 | try {
37 | if (sleepTime > 0) {
38 | Thread.sleep(sleepTime);
39 | }
40 | } catch (InterruptedException e) {
41 | e.printStackTrace();
42 | }
43 | }
44 | }
45 |
46 | @Test
47 | public void testMaximumQueueSize() throws InterruptedException {
48 | var builder = (new EnhancedQueueExecutor.Builder())
49 | .setMaximumQueueSize(1)
50 | .setCorePoolSize(1)
51 | .setMaximumPoolSize(1);
52 | assertTrue(builder.getQueueLimited());
53 | var executor = builder.build();
54 | CountDownLatch executeEnqueuedTask = new CountDownLatch(1);
55 | AtomicInteger count = new AtomicInteger();
56 | CountDownLatch enqueuedTask = new CountDownLatch(1);
57 | CountDownLatch executedEnqueuedTask = new CountDownLatch(1);
58 | executor.execute(() -> {
59 | count.incrementAndGet();
60 | try {
61 | enqueuedTask.countDown();
62 | executeEnqueuedTask.await();
63 | } catch (InterruptedException ignored) {
64 | }
65 | });
66 | enqueuedTask.await();
67 | assertEquals(1, count.get());
68 | assertEquals(0, executor.getQueueSize());
69 | // this is going to cause the queue size to be == 1
70 | executor.execute(() -> {
71 | count.incrementAndGet();
72 | executedEnqueuedTask.countDown();
73 | });
74 | assertEquals(1, count.get());
75 | assertEquals(1, executor.getQueueSize());
76 | try {
77 | executor.execute(count::incrementAndGet);
78 | fail("Expected RejectedExecutionException");
79 | } catch (RejectedExecutionException e) {
80 | // expected
81 | }
82 | assertEquals(1, count.get());
83 | assertEquals(1, executor.getQueueSize());
84 | executeEnqueuedTask.countDown();
85 | executedEnqueuedTask.await();
86 | assertEquals(2, count.get());
87 | assertEquals(0, executor.getQueueSize());
88 | executor.shutdown();
89 | }
90 |
91 | @Test
92 | public void testNoQueueLimit() throws InterruptedException {
93 | var builder = (new EnhancedQueueExecutor.Builder())
94 | .setQueueLimited(false)
95 | .setMaximumQueueSize(1)
96 | .setCorePoolSize(1)
97 | .setMaximumPoolSize(1);
98 | assertFalse(builder.getQueueLimited());
99 | var executor = builder.build();
100 | assertEquals(Integer.MAX_VALUE, executor.getMaximumQueueSize());
101 | CountDownLatch executeEnqueuedTasks = new CountDownLatch(1);
102 | AtomicInteger count = new AtomicInteger();
103 | CountDownLatch enqueuedTask = new CountDownLatch(1);
104 | CountDownLatch executedEnqueuedTasks = new CountDownLatch(2);
105 | executor.execute(() -> {
106 | count.incrementAndGet();
107 | try {
108 | enqueuedTask.countDown();
109 | executeEnqueuedTasks.await();
110 | } catch (InterruptedException ignored) {
111 | }
112 | });
113 | enqueuedTask.await();
114 | executor.execute(() -> {
115 | count.incrementAndGet();
116 | executedEnqueuedTasks.countDown();
117 | });
118 | assertEquals(1, count.get());
119 | assertEquals(-1, executor.getQueueSize());
120 | executor.execute(() -> {
121 | count.incrementAndGet();
122 | executedEnqueuedTasks.countDown();
123 | });
124 | assertEquals(1, count.get());
125 | assertEquals(-1, executor.getQueueSize());
126 | executeEnqueuedTasks.countDown();
127 | executedEnqueuedTasks.await();
128 | assertEquals(3, count.get());
129 | assertEquals(-1, executor.getQueueSize());
130 | executor.shutdown();
131 | }
132 |
133 | /**
134 | * Test that unused threads are being reused. Scenario:
135 | *
136 | * - max threads = 2x, core threads = x
137 | * - schedule x tasks, wait for tasks to finish
138 | * - schedule x tasks, expect pool size = x immediately after
139 | *
140 | */
141 | @Test
142 | @Disabled("https://issues.jboss.org/browse/JBTHR-67")
143 | public void testThreadReuse() throws TimeoutException, InterruptedException {
144 | EnhancedQueueExecutor executor = (new EnhancedQueueExecutor.Builder())
145 | .setKeepAliveTime(Duration.ofMillis(keepaliveTimeMillis))
146 | .setCorePoolSize(coreSize)
147 | .setMaximumPoolSize(maxSize)
148 | .build();
149 |
150 | for (int i = 0; i < coreSize; i++) {
151 | executor.execute(new TestTask().withSleepTime(100));
152 | }
153 | assertEquals(executor.getPoolSize(), coreSize, "expected: == " + coreSize + ", actual: " + executor.getPoolSize());
154 | waitForActiveCount(executor, 0, 1000);
155 | assertEquals(executor.getPoolSize(), coreSize, "expected: == " + coreSize + ", actual: " + executor.getPoolSize());
156 | for (int i = 0; i < coreSize; i++) {
157 | executor.execute(new TestTask().withSleepTime(1000));
158 | }
159 | assertEquals(executor.getPoolSize(), coreSize, "expected: == " + coreSize + ", actual: " + executor.getPoolSize());
160 | executor.shutdown();
161 | }
162 |
163 | /**
164 | * Test that keepalive time is honored and threads above the core count are being removed when no tasks are
165 | * available.
166 | *
167 | * @throws InterruptedException
168 | * @throws TimeoutException
169 | */
170 | @Test
171 | @Disabled("https://issues.jboss.org/browse/JBTHR-67")
172 | public void testKeepaliveTime() throws TimeoutException, InterruptedException {
173 | EnhancedQueueExecutor executor = (new EnhancedQueueExecutor.Builder())
174 | .setKeepAliveTime(Duration.ofMillis(keepaliveTimeMillis))
175 | .setCorePoolSize(coreSize)
176 | .setMaximumPoolSize(maxSize)
177 | .build();
178 |
179 | assertTrue(executor.getPoolSize() <= coreSize, "expected: <=" + coreSize + ", actual: " + executor.getPoolSize());
180 | for (int i = 0; i < maxSize; i++) {
181 | executor.execute(new TestTask().withSleepTime(1000));
182 | }
183 | assertEquals(executor.getPoolSize(), maxSize, "expected: ==" + maxSize + ", actual: " + executor.getPoolSize());
184 | waitForActiveCount(executor, 0, 5000);
185 | waitForPoolSize(executor, coreSize, keepaliveTimeMillis * 2);
186 | executor.shutdown();
187 | }
188 |
189 | /**
190 | * Test that max size setting is honored. Test that keepalive time is ignored when core threads are the same as max
191 | * threads and core thread time out is disabled.
192 | */
193 | @Test
194 | @Disabled("https://issues.jboss.org/browse/JBTHR-67")
195 | public void testKeepaliveTime2() throws TimeoutException, InterruptedException {
196 | EnhancedQueueExecutor executor = (new EnhancedQueueExecutor.Builder())
197 | .setKeepAliveTime(Duration.ofMillis(keepaliveTimeMillis))
198 | .setCorePoolSize(coreSize)
199 | .setMaximumPoolSize(coreSize)
200 | .build();
201 |
202 | for (int i = 0; i < 2*coreSize; i++) {
203 | executor.execute(new TestTask().withSleepTime(100));
204 | }
205 | int currentThreads = executor.getPoolSize();
206 | assertEquals(currentThreads, coreSize, "expected: == " + coreSize + ", actual: " + currentThreads);
207 | waitForActiveCount(executor, 0, 5000);
208 | assertEquals(executor.getPoolSize(), currentThreads, "expected: == " + currentThreads + ", actual: " + executor.getPoolSize());
209 | executor.shutdown();
210 | }
211 |
212 | /**
213 | * Test the keepalive setting with core thread time out enabled.
214 | */
215 | @Test
216 | @Disabled("https://issues.jboss.org/browse/JBTHR-67")
217 | public void testKeepaliveTime3() throws TimeoutException, InterruptedException {
218 | EnhancedQueueExecutor executor = (new EnhancedQueueExecutor.Builder())
219 | .setKeepAliveTime(Duration.ofMillis(keepaliveTimeMillis))
220 | .allowCoreThreadTimeOut(true)
221 | .setCorePoolSize(coreSize)
222 | .setMaximumPoolSize(maxSize)
223 | .build();
224 |
225 | for (int i = 0; i < maxSize; i++) {
226 | executor.execute(new TestTask().withSleepTime(0));
227 | }
228 | waitForActiveCount(executor, 0, 5000);
229 | waitForPoolSize(executor, 0, keepaliveTimeMillis * 2);
230 | executor.shutdown();
231 | }
232 |
233 | /**
234 | * Tests that prestarting core threads starts exactly the core threads amount specified.
235 | */
236 | @Test
237 | public void testPrestartCoreThreads() {
238 | EnhancedQueueExecutor executor = (new EnhancedQueueExecutor.Builder())
239 | .setKeepAliveTime(Duration.ofMillis(keepaliveTimeMillis))
240 | .setCorePoolSize(coreSize)
241 | .setMaximumPoolSize(maxSize)
242 | .build();
243 | int prestarted = executor.prestartAllCoreThreads();
244 | assertEquals(prestarted, coreSize, "expected: == " + coreSize + ", actual: " + prestarted);
245 | assertEquals(executor.getPoolSize(), coreSize, "expected: == " + coreSize + ", actual: " + executor.getPoolSize());
246 | executor.shutdown();
247 | }
248 |
249 | public void assertStackDepth(ExecutorService executor, int expectedStackFrames) throws InterruptedException {
250 | CountDownLatch initialTaskCompletionBlockingLatch = new CountDownLatch(1);
251 | AtomicInteger initialTaskStackFrames = new AtomicInteger();
252 | Runnable initialTask = new Runnable() {
253 | @Override
254 | public void run() {
255 | initialTaskStackFrames.set(new RuntimeException().getStackTrace().length);
256 | try {
257 | initialTaskCompletionBlockingLatch.await();
258 | } catch (InterruptedException e) {
259 | throw new AssertionError(e);
260 | }
261 | }
262 | };
263 | CountDownLatch queuedTaskCompletionLatch = new CountDownLatch(1);
264 | AtomicInteger queuedTaskStackFrames = new AtomicInteger();
265 | Runnable queuedTask = new Runnable() {
266 | @Override
267 | public void run() {
268 | queuedTaskStackFrames.set(new RuntimeException().getStackTrace().length);
269 | queuedTaskCompletionLatch.countDown();
270 | }
271 | };
272 | try {
273 | executor.submit(initialTask);
274 | executor.submit(queuedTask);
275 | initialTaskCompletionBlockingLatch.countDown();
276 | queuedTaskCompletionLatch.await();
277 | assertEquals(expectedStackFrames, initialTaskStackFrames.get());
278 | assertEquals(expectedStackFrames, queuedTaskStackFrames.get());
279 | } finally {
280 | executor.shutdown();
281 | assertTrue(executor.awaitTermination(5, TimeUnit.SECONDS), "Executor failed to terminate");
282 | }
283 | }
284 |
285 | private void waitForPoolSize(EnhancedQueueExecutor executor, int expectedPoolSize, long waitMillis) throws TimeoutException, InterruptedException {
286 | long deadline = System.currentTimeMillis() + waitMillis;
287 | long delayMillis = 100;
288 |
289 | do {
290 | if (executor.getPoolSize() == expectedPoolSize) {
291 | break;
292 | }
293 | Thread.sleep(delayMillis);
294 | } while (System.currentTimeMillis() + delayMillis < deadline);
295 | if (executor.getPoolSize() != expectedPoolSize) {
296 | throw new TimeoutException("Timed out waiting for pool size to become " + expectedPoolSize
297 | + ", current pool size is " + executor.getPoolSize());
298 | }
299 | }
300 |
301 | private void waitForActiveCount(EnhancedQueueExecutor executor, int expectedActiveCount, long waitMillis) throws TimeoutException, InterruptedException {
302 | long deadline = System.currentTimeMillis() + waitMillis;
303 | long delayMillis = 100;
304 |
305 | do {
306 | if (executor.getActiveCount() == expectedActiveCount) {
307 | break;
308 | }
309 | Thread.sleep(delayMillis);
310 | } while (System.currentTimeMillis() + delayMillis < deadline);
311 | if (executor.getActiveCount() != expectedActiveCount) {
312 | throw new TimeoutException("Timed out waiting for active count to become " + expectedActiveCount
313 | + ", current active count is " + executor.getActiveCount());
314 | }
315 | }
316 | }
317 |
--------------------------------------------------------------------------------
/src/test/java/org/jboss/threads/EnhancedThreadQueueExecutorTestCase.java:
--------------------------------------------------------------------------------
1 | package org.jboss.threads;
2 |
3 | import org.assertj.core.api.Assertions;
4 | import org.junit.jupiter.api.Disabled;
5 | import org.junit.jupiter.api.Test;
6 |
7 | import java.time.Duration;
8 | import java.util.concurrent.CountDownLatch;
9 | import java.util.concurrent.TimeUnit;
10 | import java.util.concurrent.TimeoutException;
11 |
12 | import static org.junit.jupiter.api.Assertions.assertEquals;
13 | import static org.junit.jupiter.api.Assertions.assertTrue;
14 |
15 | /**
16 | * Tests for checking EnhancedTreadPoolExecutor
17 | *
18 | */
19 | public class EnhancedThreadQueueExecutorTestCase {
20 |
21 | private int coreSize = 4;
22 | private int maxSize = 7;
23 | private long keepaliveTimeMillis = 1000;
24 | private long defaultWaitTimeout = 5000;
25 |
26 | class TestTask implements Runnable {
27 | CountDownLatch exitLatch;
28 | CountDownLatch allThreadsRunningLatch;
29 |
30 | private TestTask(CountDownLatch exitLatch, CountDownLatch allThreadsRunningLatch) {
31 | this.exitLatch = exitLatch;
32 | this.allThreadsRunningLatch = allThreadsRunningLatch;
33 | }
34 |
35 | @Override
36 | public void run() {
37 | try {
38 | allThreadsRunningLatch.countDown();
39 | exitLatch.await();
40 | } catch (InterruptedException e) {
41 | e.printStackTrace();
42 | }
43 | }
44 | }
45 |
46 | /**
47 | * Test invalid values:
48 | * * Negative keepAlive, coreSize, maxSize
49 | * * maxSize > coreSize
50 | */
51 | @Test
52 | public void testInvalidValuesKeepAliveZero() {
53 | Assertions.assertThatExceptionOfType(IllegalArgumentException.class)
54 | .isThrownBy(() -> new EnhancedQueueExecutor.Builder()
55 | .setKeepAliveTime(Duration.ZERO)
56 | .setCorePoolSize(coreSize)
57 | .setMaximumPoolSize(maxSize)
58 | .build());
59 | }
60 |
61 | @Test
62 | public void testInvalidValuesKeepAliveNegative() {
63 | Assertions.assertThatExceptionOfType(IllegalArgumentException.class)
64 | .isThrownBy(() -> new EnhancedQueueExecutor.Builder()
65 | .setKeepAliveTime(Duration.ofMillis(-3456))
66 | .setCorePoolSize(coreSize)
67 | .setMaximumPoolSize(maxSize)
68 | .build());
69 | }
70 |
71 | @Test
72 | public void testInvalidValuesCoreSizeNegative() {
73 | Assertions.assertThatExceptionOfType(IllegalArgumentException.class)
74 | .isThrownBy(() -> new EnhancedQueueExecutor.Builder()
75 | .setKeepAliveTime(Duration.ofMillis(keepaliveTimeMillis))
76 | .setCorePoolSize(-5)
77 | .setMaximumPoolSize(maxSize)
78 | .build());
79 | }
80 |
81 | @Test
82 | public void testInvalidValuesMaxSizeNegative() {
83 | Assertions.assertThatExceptionOfType(IllegalArgumentException.class)
84 | .isThrownBy(() -> new EnhancedQueueExecutor.Builder()
85 | .setKeepAliveTime(Duration.ofMillis(keepaliveTimeMillis))
86 | .setCorePoolSize(coreSize)
87 | .setMaximumPoolSize(-3)
88 | .build());
89 | }
90 |
91 | @Test
92 | public void testCoreSizeBiggerThanMaxSize() {
93 | int expectedCorePoolSize = 5;
94 | EnhancedQueueExecutor executor = (new EnhancedQueueExecutor.Builder())
95 | .setKeepAliveTime(Duration.ofMillis(keepaliveTimeMillis))
96 | .setCorePoolSize(2 * expectedCorePoolSize)
97 | .setMaximumPoolSize(expectedCorePoolSize)
98 | .build();
99 | assertEquals(expectedCorePoolSize, executor.getCorePoolSize(), "Core size should be automatically adjusted to be equal to max size in case it's bigger.");
100 | }
101 |
102 | /**
103 | * Test that unused threads are being reused. Scenario:
104 | *
105 | * - max threads = 2x, core threads = x
106 | * - schedule x tasks, wait for tasks to finish
107 | * - schedule x tasks, expect pool size = x immediately after
108 | *
109 | */
110 | @Test
111 | public void testThreadReuse() throws TimeoutException, InterruptedException {
112 | EnhancedQueueExecutor executor = (new EnhancedQueueExecutor.Builder())
113 | .setKeepAliveTime(Duration.ofMillis(keepaliveTimeMillis))
114 | .setCorePoolSize(coreSize)
115 | .setMaximumPoolSize(maxSize)
116 | .build();
117 |
118 | CountDownLatch exitLatch = new CountDownLatch(1);
119 | CountDownLatch allThreadsRunningLatch = new CountDownLatch(coreSize);
120 |
121 | for (int i = 0; i < coreSize; i++) {
122 | executor.execute(new TestTask(exitLatch, allThreadsRunningLatch));
123 | }
124 | assertTrue(allThreadsRunningLatch.await(defaultWaitTimeout, TimeUnit.MILLISECONDS),
125 | "Not all threads were running. They were most likely not scheduled for execution.");
126 | waitForPoolSize(executor, coreSize, defaultWaitTimeout);
127 | exitLatch.countDown();
128 | waitForActiveCount(executor, 0, defaultWaitTimeout);
129 | waitForPoolSize(executor, coreSize, defaultWaitTimeout);
130 | exitLatch = new CountDownLatch(1);
131 | allThreadsRunningLatch = new CountDownLatch(coreSize);
132 | for (int i = 0; i < coreSize; i++) {
133 | executor.execute(new TestTask(exitLatch, allThreadsRunningLatch));
134 | }
135 | assertTrue(allThreadsRunningLatch.await(defaultWaitTimeout, TimeUnit.MILLISECONDS),
136 | "Not all threads were running. They were most likely not scheduled for execution.");
137 | exitLatch.countDown();
138 | waitForPoolSize(executor, coreSize, defaultWaitTimeout);
139 | executor.shutdown();
140 | }
141 |
142 | /**
143 | * Test thread reuse above core size
144 | * Scenario:
145 | *
146 | * - setKeepAlive=60 sec
147 | * - max threads = 2x, core threads = x
148 | * - schedule x tasks and wait to occupy all core threads
149 | * - schedule one more task and let it finish
150 | * - schedule one task and check that pool size is still x+1
151 | *
152 | */
153 | @Test
154 | @Disabled("This test consistently fails, see JBTHR-67")
155 | public void testThreadReuseAboveCoreSize() throws TimeoutException, InterruptedException {
156 | EnhancedQueueExecutor executor = (new EnhancedQueueExecutor.Builder())
157 | .setKeepAliveTime(Duration.ofSeconds(60))
158 | .setCorePoolSize(coreSize)
159 | .setMaximumPoolSize(maxSize)
160 | .build();
161 |
162 | // submit 3 tasks to fill core size
163 | CountDownLatch exitLatch = new CountDownLatch(1);
164 | CountDownLatch allThreadsRunningLatch = new CountDownLatch(coreSize);
165 | for (int i = 0; i < coreSize; i++) {
166 | executor.execute(new TestTask(exitLatch, allThreadsRunningLatch));
167 | }
168 | assertTrue(allThreadsRunningLatch.await(defaultWaitTimeout, TimeUnit.MILLISECONDS),
169 | "Not all threads were running. They were most likely not scheduled for execution.");
170 | waitForPoolSize(executor, coreSize, defaultWaitTimeout);
171 |
172 | // submit one more task and allow it to finish
173 | CountDownLatch singleExitLatch = new CountDownLatch(1);
174 | CountDownLatch threadRunningLatch = new CountDownLatch(1);
175 | executor.execute(new TestTask(singleExitLatch, threadRunningLatch));
176 | threadRunningLatch.await(defaultWaitTimeout, TimeUnit.MILLISECONDS);
177 | waitForPoolSize(executor, coreSize + 1, defaultWaitTimeout);
178 | singleExitLatch.countDown();
179 | waitForActiveCount(executor, coreSize, defaultWaitTimeout);
180 |
181 | // now there are just core threads and one free thread, submit another task and check it's reused
182 | singleExitLatch = new CountDownLatch(1);
183 | threadRunningLatch = new CountDownLatch(1);
184 | executor.execute(new TestTask(singleExitLatch, threadRunningLatch));
185 | threadRunningLatch.await(defaultWaitTimeout, TimeUnit.MILLISECONDS);
186 | waitForPoolSize(executor, coreSize + 1, defaultWaitTimeout);
187 | singleExitLatch.countDown();
188 |
189 | // finish all
190 | exitLatch.countDown();
191 | executor.shutdown();
192 | }
193 |
194 | /**
195 | * Test that keepalive time is honored and threads above the core count are being removed when no tasks are
196 | * available.
197 | *
198 | * @throws InterruptedException
199 | * @throws TimeoutException
200 | */
201 | @Test
202 | @Disabled("This test consistently fails, see JBTHR-67")
203 | public void testKeepaliveTime() throws TimeoutException, InterruptedException {
204 | EnhancedQueueExecutor executor = (new EnhancedQueueExecutor.Builder())
205 | .setKeepAliveTime(Duration.ofMillis(keepaliveTimeMillis))
206 | .setCorePoolSize(coreSize)
207 | .setMaximumPoolSize(maxSize)
208 | .allowCoreThreadTimeOut(false)
209 | .build();
210 |
211 | CountDownLatch exitLatch = new CountDownLatch(1);
212 | CountDownLatch allThreadsRunningLatch = new CountDownLatch(coreSize);
213 | for (int i = 0; i < coreSize; i++) {
214 | executor.execute(new TestTask(exitLatch, allThreadsRunningLatch));
215 | }
216 | assertTrue(allThreadsRunningLatch.await(defaultWaitTimeout, TimeUnit.MILLISECONDS),
217 | "Not all threads were running. They were most likely not scheduled for execution.");
218 | CountDownLatch exitLatch2 = new CountDownLatch(1);
219 | CountDownLatch allThreadsRunningLatch2 = new CountDownLatch(maxSize - coreSize);
220 | for (int i = 0; i < (maxSize - coreSize); i++) {
221 | executor.execute(new TestTask(exitLatch2, allThreadsRunningLatch2));
222 | }
223 | assertTrue(allThreadsRunningLatch.await(defaultWaitTimeout, TimeUnit.MILLISECONDS),
224 | "Not all threads were running. They were most likely not scheduled for execution.");
225 | waitForPoolSize(executor, maxSize, defaultWaitTimeout);
226 |
227 | // finish core tasks and let timeout "core" threads
228 | exitLatch.countDown();
229 | waitForActiveCount(executor, maxSize - coreSize, defaultWaitTimeout);
230 | waitForPoolSize(executor, Math.max(coreSize, (maxSize - coreSize)), defaultWaitTimeout);
231 | exitLatch2.countDown();
232 | waitForActiveCount(executor, 0, defaultWaitTimeout);
233 | waitForPoolSize(executor, coreSize, defaultWaitTimeout);
234 | executor.shutdown();
235 | }
236 |
237 | /**
238 | * Test that keepalive time is ignored when core threads are the same as max
239 | * threads and core thread time out is disabled.
240 | */
241 | @Test
242 | public void testKeepaliveTime2() throws TimeoutException, InterruptedException {
243 | EnhancedQueueExecutor executor = (new EnhancedQueueExecutor.Builder())
244 | .setKeepAliveTime(Duration.ofMillis(keepaliveTimeMillis))
245 | .setCorePoolSize(coreSize)
246 | .setMaximumPoolSize(coreSize)
247 | .build();
248 |
249 | CountDownLatch exitLatch = new CountDownLatch(1);
250 | CountDownLatch allThreadsRunningLatch = new CountDownLatch(coreSize);
251 |
252 | for (int i = 0; i < coreSize; i++) {
253 | executor.execute(new TestTask(exitLatch, allThreadsRunningLatch));
254 | }
255 | assertTrue(allThreadsRunningLatch.await(defaultWaitTimeout, TimeUnit.MILLISECONDS),
256 | "Not all threads were running. They were most likely not scheduled for execution.");
257 | waitForPoolSize(executor, coreSize, defaultWaitTimeout);
258 | exitLatch.countDown();
259 | waitForActiveCount(executor, 0, defaultWaitTimeout);
260 | waitForPoolSize(executor, coreSize, defaultWaitTimeout);
261 | executor.shutdown();
262 | }
263 |
264 | /**
265 | * Test the keepalive setting with core thread time out enabled.
266 | */
267 | @Test
268 | public void testKeepaliveTimeWithCoreThreadTimeoutEnabled() throws TimeoutException, InterruptedException {
269 | EnhancedQueueExecutor executor = (new EnhancedQueueExecutor.Builder())
270 | .setKeepAliveTime(Duration.ofMillis(keepaliveTimeMillis))
271 | .allowCoreThreadTimeOut(true)
272 | .setCorePoolSize(coreSize)
273 | .setMaximumPoolSize(maxSize)
274 | .build();
275 |
276 | CountDownLatch exitLatch = new CountDownLatch(1);
277 | CountDownLatch allThreadsRunningLatch = new CountDownLatch(maxSize);
278 |
279 | for (int i = 0; i < maxSize; i++) {
280 | executor.execute(new TestTask(exitLatch, allThreadsRunningLatch));
281 | }
282 | // this will make sure that all thread are running at the same time
283 | assertTrue(allThreadsRunningLatch.await(defaultWaitTimeout, TimeUnit.MILLISECONDS),
284 | "Not all threads were running. They were most likely not scheduled for execution.");
285 | exitLatch.countDown();
286 | waitForActiveCount(executor, 0, defaultWaitTimeout);
287 | waitForPoolSize(executor, 0, defaultWaitTimeout);
288 | executor.shutdown();
289 | }
290 |
291 | /**
292 | * Tests that prestarting core threads starts exactly the core threads amount specified.
293 | */
294 | @Test
295 | public void testPrestartCoreThreads() {
296 | EnhancedQueueExecutor executor = (new EnhancedQueueExecutor.Builder())
297 | .setKeepAliveTime(Duration.ofMillis(keepaliveTimeMillis))
298 | .setCorePoolSize(coreSize)
299 | .setMaximumPoolSize(maxSize)
300 | .build();
301 | int prestarted = executor.prestartAllCoreThreads();
302 | assertEquals(coreSize, prestarted, "expected: == " + coreSize + ", actual: " + prestarted);
303 | assertEquals(coreSize, executor.getPoolSize(), "expected: == " + coreSize + ", actual: " + executor.getPoolSize());
304 | executor.shutdown();
305 | }
306 |
307 | private void waitForPoolSize(EnhancedQueueExecutor executor, int expectedPoolSize, long waitMillis) throws TimeoutException, InterruptedException {
308 | long deadline = System.currentTimeMillis() + waitMillis;
309 | long delayMillis = 100;
310 |
311 | do {
312 | if (executor.getPoolSize() == expectedPoolSize) {
313 | break;
314 | }
315 | Thread.sleep(delayMillis);
316 | } while (System.currentTimeMillis() + delayMillis < deadline);
317 | if (executor.getPoolSize() != expectedPoolSize) {
318 | throw new TimeoutException("Timed out waiting for pool size to become " + expectedPoolSize
319 | + ", current pool size is " + executor.getPoolSize());
320 | }
321 | }
322 |
323 | private void waitForActiveCount(EnhancedQueueExecutor executor, int expectedActiveCount, long waitMillis) throws TimeoutException, InterruptedException {
324 | long deadline = System.currentTimeMillis() + waitMillis;
325 | long delayMillis = 100;
326 |
327 | do {
328 | if (executor.getActiveCount() == expectedActiveCount) {
329 | break;
330 | }
331 | Thread.sleep(delayMillis);
332 | } while (System.currentTimeMillis() + delayMillis < deadline);
333 | if (executor.getActiveCount() != expectedActiveCount) {
334 | throw new TimeoutException("Timed out waiting for active count to become " + expectedActiveCount
335 | + ", current active count is " + executor.getActiveCount());
336 | }
337 | }
338 |
339 | @Test
340 | public void testEnhancedExecutorShutdownNoTasks() throws Exception {
341 | final CountDownLatch terminateLatch = new CountDownLatch(1);
342 | EnhancedQueueExecutor executor = new EnhancedQueueExecutor.Builder()
343 | .setCorePoolSize(10)
344 | .setKeepAliveTime(Duration.ofNanos(1))
345 | .setTerminationTask(new Runnable() {
346 | @Override
347 | public void run() {
348 | terminateLatch.countDown();
349 | }
350 | })
351 | .build();
352 |
353 | executor.shutdown();
354 | assertTrue(terminateLatch.await(10, TimeUnit.SECONDS));
355 | }
356 |
357 | @Test //JBTHR-50
358 | public void testEnhancedExecutorShutdown() throws Exception {
359 | final CountDownLatch terminateLatch = new CountDownLatch(1);
360 | EnhancedQueueExecutor executor = new EnhancedQueueExecutor.Builder()
361 | .setCorePoolSize(10)
362 | .setKeepAliveTime(Duration.ofNanos(1))
363 | .setTerminationTask(new Runnable() {
364 | @Override
365 | public void run() {
366 | terminateLatch.countDown();
367 | }
368 | })
369 | .build();
370 |
371 | for (int i = 0; i < 10000; ++i) {
372 | executor.submit(new Runnable() {
373 | @Override
374 | public void run() {
375 | try {
376 | Thread.sleep(1);
377 | } catch (InterruptedException e) {
378 | //ignore
379 | }
380 | }
381 | });
382 | }
383 | executor.shutdown();
384 | assertTrue(terminateLatch.await(10, TimeUnit.SECONDS));
385 | }
386 | }
387 |
--------------------------------------------------------------------------------
/src/test/java/org/jboss/threads/QueuelessViewExecutorTest.java:
--------------------------------------------------------------------------------
1 | package org.jboss.threads;
2 |
3 | import org.awaitility.Awaitility;
4 | import org.junit.jupiter.api.Timeout;
5 | import org.junit.jupiter.params.ParameterizedTest;
6 | import org.junit.jupiter.params.provider.EnumSource;
7 |
8 | import java.util.concurrent.CountDownLatch;
9 | import java.util.concurrent.Executor;
10 | import java.util.concurrent.ExecutorService;
11 | import java.util.concurrent.Executors;
12 | import java.util.concurrent.RejectedExecutionException;
13 | import java.util.concurrent.TimeUnit;
14 | import java.util.concurrent.atomic.AtomicBoolean;
15 | import java.util.concurrent.atomic.AtomicInteger;
16 |
17 | import static org.assertj.core.api.Assertions.assertThat;
18 | import static org.assertj.core.api.Assertions.assertThatThrownBy;
19 |
20 |
21 | public class QueuelessViewExecutorTest {
22 |
23 | private static final String THREAD_BASE_NAME = "CachedExecutorViewTest-";
24 |
25 | /*
26 | * Both implementations have enough permits that queues shouldn't
27 | * be used so the implementations should be equivalent.
28 | */
29 | public enum ExecutorType {
30 | QUEUELESS_VIEW() {
31 | @Override
32 | ExecutorService wrap(Executor delegate) {
33 | return ViewExecutor.builder(delegate)
34 | .setQueueLimit(0)
35 | .setMaxSize(Short.MAX_VALUE)
36 | .build();
37 | }
38 | },
39 | QUEUED() {
40 | @Override
41 | ExecutorService wrap(Executor delegate) {
42 | return ViewExecutor.builder(delegate)
43 | .setQueueLimit(Integer.MAX_VALUE)
44 | .setMaxSize(Short.MAX_VALUE)
45 | .build();
46 | }
47 | };
48 |
49 | abstract ExecutorService wrap(Executor delegate);
50 | }
51 |
52 | @ParameterizedTest
53 | @EnumSource(QueuelessViewExecutorTest.ExecutorType.class)
54 | public void testShutdownNow(ExecutorType executorType) throws InterruptedException {
55 | AtomicBoolean interrupted = new AtomicBoolean();
56 | ExecutorService cached = cachedExecutor();
57 | ExecutorService view = executorType.wrap(cached);
58 | assertThat(view.isShutdown()).isEqualTo(cached.isShutdown()).isFalse();
59 | assertThat(view.isTerminated()).isEqualTo(cached.isTerminated()).isFalse();
60 | CountDownLatch executionStartedLatch = new CountDownLatch(1);
61 | CountDownLatch interruptedLatch = new CountDownLatch(1);
62 | view.execute(() -> {
63 | try {
64 | executionStartedLatch.countDown();
65 | Thread.sleep(10_000);
66 | } catch (InterruptedException e) {
67 | interrupted.set(true);
68 | // Wait so we can validate the time between shutdown and terminated
69 | try {
70 | interruptedLatch.await();
71 | } catch (InterruptedException ee) {
72 | throw new AssertionError(ee);
73 | }
74 | }
75 | });
76 | executionStartedLatch.await();
77 | assertThat(view.shutdownNow()).as("Cached executors have no queue").isEmpty();
78 | assertThat(view.isShutdown()).isTrue();
79 | assertThatThrownBy(() -> view.execute(NullRunnable.getInstance()))
80 | .as("Submitting work after invoking shutdown or shutdownNow should fail")
81 | .isInstanceOf(RejectedExecutionException.class);
82 | Awaitility.waitAtMost(3, TimeUnit.SECONDS).untilAsserted(() -> {
83 | assertThat(interrupted).isTrue();
84 | assertThat(view.isTerminated()).isFalse();
85 | });
86 | interruptedLatch.countDown();
87 | Awaitility.waitAtMost(3, TimeUnit.SECONDS)
88 | .untilAsserted(() -> assertThat(view.isTerminated()).as("%s", view).isTrue());
89 |
90 | assertCleanShutdown(cached);
91 | }
92 |
93 | @ParameterizedTest
94 | @EnumSource(QueuelessViewExecutorTest.ExecutorType.class)
95 | public void testShutdownNow_immediatelyAfterTaskIsSubmitted(ExecutorType executorType) throws InterruptedException {
96 | AtomicBoolean interrupted = new AtomicBoolean();
97 | ExecutorService cached = cachedExecutor();
98 | ExecutorService view = executorType.wrap(runnable -> {
99 | cached.execute(() -> {
100 | // Emphasize the jitter between when a task is submitted, and when it begins to execute
101 | try {
102 | Thread.sleep(100);
103 | } catch (InterruptedException e) {
104 | throw new AssertionError(e);
105 | }
106 | runnable.run();
107 | });
108 | });
109 | assertThat(view.isShutdown()).isEqualTo(cached.isShutdown()).isFalse();
110 | assertThat(view.isTerminated()).isEqualTo(cached.isTerminated()).isFalse();
111 | view.execute(() -> {
112 | try {
113 | Thread.sleep(10_000);
114 | } catch (InterruptedException e) {
115 | interrupted.set(true);
116 | }
117 | });
118 | assertThat(view.shutdownNow()).as("Cached executors have no queue").isEmpty();
119 | assertThat(view.awaitTermination(3, TimeUnit.SECONDS))
120 | .as("View failed to terminate within 3 seconds: %s", view)
121 | .isTrue();
122 | assertThat(interrupted).as("Task should have been interrupted").isTrue();
123 |
124 | assertCleanShutdown(cached);
125 | }
126 |
127 | @Timeout(5_000) // Failing awaitTermination should return quickly
128 | @ParameterizedTest
129 | @EnumSource(QueuelessViewExecutorTest.ExecutorType.class)
130 | public void testAwaitTermination(ExecutorType executorType) throws InterruptedException {
131 | AtomicBoolean interrupted = new AtomicBoolean();
132 | ExecutorService cached = cachedExecutor();
133 | ExecutorService view = executorType.wrap(cached);
134 | assertThat(view.isShutdown()).isEqualTo(cached.isShutdown()).isFalse();
135 | assertThat(view.isTerminated()).isEqualTo(cached.isTerminated()).isFalse();
136 | view.execute(() -> {
137 | try {
138 | Thread.sleep(30_000);
139 | } catch (InterruptedException e) {
140 | interrupted.set(true);
141 | }
142 | });
143 |
144 | view.shutdown();
145 | assertThat(view.awaitTermination(10, TimeUnit.MILLISECONDS))
146 | .as("Task should not have been interrupted, and is still sleeping")
147 | .isFalse();
148 |
149 | assertThat(interrupted).as("Task should not be interrupted by 'shutdown'").isFalse();
150 |
151 | assertThat(view.shutdownNow()).as("Cached executors have no queue").isEmpty();
152 | assertThat(view.awaitTermination(3, TimeUnit.SECONDS))
153 | .as("Task should have been interrupted: %s", view)
154 | .isTrue();
155 |
156 | assertThat(interrupted).as("Task should be interrupted by 'shutdownNow'").isTrue();
157 |
158 | assertCleanShutdown(cached);
159 | }
160 |
161 | @ParameterizedTest
162 | @EnumSource(QueuelessViewExecutorTest.ExecutorType.class)
163 | public void testShutdown(ExecutorType executorType) throws InterruptedException {
164 | AtomicBoolean interrupted = new AtomicBoolean();
165 | ExecutorService cached = cachedExecutor();
166 | ExecutorService view = executorType.wrap(cached);
167 | assertThat(view.isShutdown()).isEqualTo(cached.isShutdown()).isFalse();
168 | assertThat(view.isTerminated()).isEqualTo(cached.isTerminated()).isFalse();
169 | CountDownLatch executionStartedLatch = new CountDownLatch(1);
170 | view.execute(() -> {
171 | try {
172 | executionStartedLatch.countDown();
173 | Thread.sleep(500);
174 | } catch (InterruptedException e) {
175 | interrupted.set(true);
176 | }
177 | });
178 | executionStartedLatch.await();
179 | view.shutdown();
180 | assertThat(view.isShutdown()).isTrue();
181 | assertThatThrownBy(() -> view.execute(NullRunnable.getInstance()))
182 | .as("Submitting work after invoking shutdown or shutdown should fail")
183 | .isInstanceOf(RejectedExecutionException.class);
184 | assertThat(view.isTerminated()).isFalse();
185 | Awaitility.waitAtMost(3, TimeUnit.SECONDS)
186 | .untilAsserted(() -> assertThat(view.isTerminated()).as("%s", view).isTrue());
187 | assertThat(interrupted).isFalse();
188 |
189 | assertCleanShutdown(cached);
190 | }
191 |
192 | private static ExecutorService cachedExecutor() {
193 | AtomicInteger index = new AtomicInteger();
194 | return Executors.newCachedThreadPool(
195 | task -> {
196 | Thread thread = new Thread(task);
197 | thread.setDaemon(true);
198 | thread.setName(THREAD_BASE_NAME + index.getAndIncrement());
199 | return thread;
200 | });
201 | }
202 |
203 | private static void assertCleanShutdown(ExecutorService executor) {
204 | assertThat(executor.isShutdown()).isFalse();
205 | assertThat(executor.isTerminated()).isFalse();
206 | executor.shutdown();
207 | try {
208 | assertThat(executor.awaitTermination(1, TimeUnit.SECONDS))
209 | .as("Failed to clean up the executor")
210 | .isTrue();
211 | } catch (InterruptedException e) {
212 | throw new AssertionError(e);
213 | }
214 | }
215 | }
216 |
--------------------------------------------------------------------------------
/src/test/java/org/jboss/threads/ScheduledEnhancedQueueExecutorTest.java:
--------------------------------------------------------------------------------
1 | package org.jboss.threads;
2 |
3 | import static org.junit.jupiter.api.Assertions.*;
4 |
5 | import java.time.LocalDateTime;
6 | import java.util.ArrayList;
7 | import java.util.concurrent.Callable;
8 | import java.util.concurrent.CancellationException;
9 | import java.util.concurrent.CountDownLatch;
10 | import java.util.concurrent.ExecutionException;
11 | import java.util.concurrent.ScheduledFuture;
12 | import java.util.concurrent.TimeUnit;
13 | import java.util.concurrent.atomic.AtomicInteger;
14 |
15 | import org.junit.jupiter.api.Test;
16 |
17 | public class ScheduledEnhancedQueueExecutorTest {
18 |
19 | @Test
20 | public void testCancel() throws Exception {
21 | EnhancedQueueExecutor eqe = new EnhancedQueueExecutor.Builder().build();
22 | try {
23 | ScheduledFuture> future = eqe.schedule(() -> fail("Should never run"), 1000, TimeUnit.DAYS);
24 | Thread.sleep(400); // a few ms to let things percolate
25 | assertFalse(future.isCancelled());
26 | // this should succeed since the task isn't submitted yet
27 | assertTrue(future.cancel(false));
28 | assertTrue(future.isCancelled());
29 | eqe.shutdown();
30 | assertTrue(eqe.awaitTermination(5, TimeUnit.SECONDS), "Timely shutdown");
31 | } finally {
32 | eqe.shutdownNow();
33 | }
34 | }
35 |
36 | @Test
37 | public void testCancelWhileRunning() throws Exception {
38 | EnhancedQueueExecutor eqe = new EnhancedQueueExecutor.Builder().build();
39 | try {
40 | CountDownLatch latch = new CountDownLatch(1);
41 | ScheduledFuture future = eqe.schedule(() -> { latch.countDown(); Thread.sleep(1_000_000_000L); return Boolean.TRUE; }, 1, TimeUnit.NANOSECONDS);
42 | assertTrue(latch.await(5, TimeUnit.SECONDS), "Timely task execution");
43 | assertFalse(future.isCancelled());
44 | // task is running
45 | assertTrue(future.cancel(false));
46 | assertFalse(future.isCancelled());
47 | assertFalse(future.isDone());
48 | // now try to interrupt it
49 | assertTrue(future.cancel(true));
50 | assertFalse(future.isCancelled());
51 | // now get it
52 | Throwable cause = assertThrows(ExecutionException.class, () -> future.get(100L, TimeUnit.MILLISECONDS)).getCause();
53 | assertInstanceOf(InterruptedException.class, cause);
54 | assertTrue(future.isDone());
55 | eqe.shutdown();
56 | assertTrue(eqe.awaitTermination(5, TimeUnit.SECONDS), "Timely shutdown");
57 | } finally {
58 | eqe.shutdownNow();
59 | }
60 | }
61 |
62 | @Test
63 | public void testReasonableExecutionDelay() throws Exception {
64 | EnhancedQueueExecutor eqe = new EnhancedQueueExecutor.Builder().build();
65 | try {
66 | Callable task = () -> Boolean.TRUE;
67 | long start = System.nanoTime();
68 | ScheduledFuture future = eqe.schedule(task, 1, TimeUnit.MILLISECONDS);
69 | Boolean result = future.get();
70 | long execTime = System.nanoTime() - start;
71 | long expected = 1_000_000L;
72 | assertTrue(execTime >= expected, "Execution too short (expected at least " + expected + ", got " + execTime + ")");
73 | assertNotNull(result);
74 | assertTrue(result.booleanValue());
75 | start = System.nanoTime();
76 | future = eqe.schedule(task, 500, TimeUnit.MILLISECONDS);
77 | result = future.get();
78 | execTime = System.nanoTime() - start;
79 | expected = 500_000_000L;
80 | assertTrue(execTime >= expected, "Execution too short (expected at least " + expected + ", got " + execTime + ")");
81 | assertNotNull(result);
82 | assertTrue(result.booleanValue());
83 | eqe.shutdown();
84 | assertTrue(eqe.awaitTermination(5, TimeUnit.SECONDS), "Timely shutdown");
85 | } finally {
86 | eqe.shutdownNow();
87 | }
88 | }
89 |
90 | @Test
91 | public void testFixedRateExecution() throws Exception {
92 | EnhancedQueueExecutor eqe = new EnhancedQueueExecutor.Builder().build();
93 | try {
94 | AtomicInteger ai = new AtomicInteger();
95 | CountDownLatch completeLatch = new CountDownLatch(1);
96 | ScheduledFuture> future = eqe.scheduleAtFixedRate(() -> {
97 | if (ai.incrementAndGet() == 5) {
98 | completeLatch.countDown();
99 | }
100 | }, 20, 50, TimeUnit.MILLISECONDS);
101 | assertTrue(completeLatch.await(5, TimeUnit.SECONDS), "Completion of enough iterations");
102 | assertFalse(future.isDone()); // they're never done
103 | // don't assert, because there's a small chance it would happen to be running
104 | future.cancel(false);
105 | try {
106 | future.get(5, TimeUnit.SECONDS);
107 | fail("Expected cancellation exception");
108 | } catch (CancellationException e) {
109 | // expected
110 | }
111 | eqe.shutdown();
112 | assertTrue(eqe.awaitTermination(5, TimeUnit.SECONDS), "Timely shutdown");
113 | } finally {
114 | eqe.shutdownNow();
115 | }
116 | }
117 |
118 | @Test
119 | public void testFixedDelayExecution() throws Exception {
120 | EnhancedQueueExecutor eqe = new EnhancedQueueExecutor.Builder().build();
121 | try {
122 | AtomicInteger ai = new AtomicInteger();
123 | CountDownLatch completeLatch = new CountDownLatch(1);
124 | ScheduledFuture> future = eqe.scheduleWithFixedDelay(() -> {
125 | if (ai.incrementAndGet() == 5) {
126 | completeLatch.countDown();
127 | }
128 | }, 20, 50, TimeUnit.MILLISECONDS);
129 | assertTrue(completeLatch.await(5, TimeUnit.SECONDS), "Completion of enough iterations");
130 | assertFalse(future.isDone()); // they're never done
131 | // don't assert, because there's a small chance it would happen to be running
132 | future.cancel(false);
133 | try {
134 | future.get(5, TimeUnit.SECONDS);
135 | fail("Expected cancellation exception");
136 | } catch (CancellationException e) {
137 | // expected
138 | }
139 | eqe.shutdown();
140 | assertTrue(eqe.awaitTermination(5, TimeUnit.SECONDS), "Timely shutdown");
141 | } finally {
142 | eqe.shutdownNow();
143 | }
144 | }
145 |
146 | @Test
147 | public void testThatFixedDelayTerminatesTask() {
148 | EnhancedQueueExecutor eqe = new EnhancedQueueExecutor.Builder().build();
149 | final ArrayList times = new ArrayList<>();
150 | var r = new Runnable() {
151 | final CountDownLatch latch = new CountDownLatch(1);
152 | volatile ScheduledFuture> future;
153 |
154 | public void run() {
155 | times.add(LocalDateTime.now());
156 | if (times.size() >= 5) {
157 | try {
158 | latch.await();
159 | } catch (InterruptedException e) {
160 | throw new RuntimeException(e);
161 | }
162 | future.cancel(false);
163 | }
164 | }
165 |
166 | public void schedule() {
167 | future = eqe.scheduleWithFixedDelay(this, 0, 100, TimeUnit.MILLISECONDS);
168 | }
169 | };
170 | r.schedule();
171 | r.latch.countDown();
172 | assertThrows(CancellationException.class, () -> r.future.get(5, TimeUnit.SECONDS));
173 | }
174 |
175 | @Test
176 | public void testCancelOnShutdown() throws Exception {
177 | EnhancedQueueExecutor eqe = new EnhancedQueueExecutor.Builder().build();
178 | try {
179 | ScheduledFuture> future = eqe.schedule(() -> fail("Should never run"), 1, TimeUnit.DAYS);
180 | eqe.shutdown();
181 | assertTrue(eqe.awaitTermination(5, TimeUnit.SECONDS), "Timely shutdown");
182 | try {
183 | future.get(5, TimeUnit.SECONDS);
184 | fail("Expected cancellation exception");
185 | } catch (CancellationException e) {
186 | // expected
187 | }
188 | assertTrue(future.isCancelled(), "Was cancelled on shutdown");
189 | } finally {
190 | eqe.shutdownNow();
191 | }
192 | }
193 | }
194 |
--------------------------------------------------------------------------------
/src/test/java/org/jboss/threads/ThreadFactoryTestCase.java:
--------------------------------------------------------------------------------
1 | package org.jboss.threads;
2 |
3 | import org.junit.jupiter.api.Disabled;
4 | import org.junit.jupiter.api.Test;
5 |
6 | import java.util.concurrent.atomic.AtomicBoolean;
7 | import java.util.concurrent.CountDownLatch;
8 |
9 | import static org.junit.jupiter.api.Assertions.assertEquals;
10 | import static org.junit.jupiter.api.Assertions.assertFalse;
11 | import static org.junit.jupiter.api.Assertions.assertTrue;
12 |
13 | /**
14 | *
15 | */
16 | public final class ThreadFactoryTestCase {
17 | private static final NullRunnable NULL_RUNNABLE = new NullRunnable();
18 |
19 | private static class NullRunnable implements Runnable {
20 | public void run() {
21 | }
22 | }
23 |
24 | private static void doTestNamePattern(JBossThreadFactory threadFactory, int expectedPerFactoryId, int expectedGlobalId, int expectedFactoryId) {
25 | final String name = threadFactory.newThread(NULL_RUNNABLE).getName();
26 | assertTrue(name.matches("-([a-z]+:)*one:two:three-%-" + expectedPerFactoryId + "-" + expectedGlobalId + "-" + expectedFactoryId + "-"), "Wrong thread name (" + name + ") ");
27 | }
28 |
29 | /**
30 | * This MUST be the first test, otherwise the sequence numbers will be wrong.
31 | */
32 | @Test
33 | @Disabled("skip test for now since it depends on order")
34 | public void testNamePattern() {
35 | // TODO - skip test for now since it depends on order.
36 | if (true) return;
37 | final JBossThreadFactory threadFactory1 = new JBossThreadFactory(new ThreadGroup(new ThreadGroup(new ThreadGroup("one"), "two"), "three"), null,
38 | null, "-%p-%%-%t-%g-%f-", null, null);
39 | doTestNamePattern(threadFactory1, 1, 1, 1);
40 | doTestNamePattern(threadFactory1, 2, 2, 1);
41 | doTestNamePattern(threadFactory1, 3, 3, 1);
42 | final JBossThreadFactory threadFactory2 = new JBossThreadFactory(new ThreadGroup(new ThreadGroup(new ThreadGroup("one"), "two"), "three"), null,
43 | null, "-%p-%%-%t-%g-%f-", null, null);
44 | doTestNamePattern(threadFactory2, 1, 4, 2);
45 | doTestNamePattern(threadFactory2, 2, 5, 2);
46 | doTestNamePattern(threadFactory2, 3, 6, 2);
47 | doTestNamePattern(threadFactory2, 4, 7, 2);
48 | // back to the first factory...
49 | doTestNamePattern(threadFactory1, 4, 8, 1);
50 | }
51 |
52 | @Test
53 | public void testDaemon() {
54 | final JBossThreadFactory threadFactory1 = new JBossThreadFactory(null, Boolean.TRUE, null, "%t", null, null);
55 | assertTrue(threadFactory1.newThread(NULL_RUNNABLE).isDaemon(), "Thread is not a daemon thread");
56 | final JBossThreadFactory threadFactory2 = new JBossThreadFactory(null, Boolean.FALSE, null, "%t", null, null);
57 | assertFalse(threadFactory2.newThread(NULL_RUNNABLE).isDaemon(),"Thread should not be a daemon thread");
58 | }
59 |
60 | @Test
61 | public void testInterruptHandler() throws InterruptedException {
62 | final AtomicBoolean wasInterrupted = new AtomicBoolean();
63 | final AtomicBoolean called = new AtomicBoolean();
64 | final CountDownLatch latch = new CountDownLatch(1);
65 | final JBossThreadFactory threadFactory = new JBossThreadFactory(null, null, null, null, null, null);
66 | final Thread t = threadFactory.newThread(new Runnable() {
67 | public void run() {
68 | synchronized (this) {
69 | final InterruptHandler old = JBossThread.getAndSetInterruptHandler(new InterruptHandler() {
70 | public void handleInterrupt(final Thread thread) {
71 | called.set(true);
72 | }
73 | });
74 | Thread.interrupted();
75 | latch.countDown();
76 | try {
77 | for (;;) wait();
78 | } catch (InterruptedException e) {
79 | wasInterrupted.set(true);
80 | }
81 | }
82 | }
83 | });
84 | t.start();
85 | latch.await();
86 | t.interrupt();
87 | t.join();
88 | assertTrue(wasInterrupted.get(), "Was not interrupted");
89 | assertTrue(called.get(), "Handler was not called");
90 | }
91 |
92 | @Test
93 | public void testUncaughtHandler() throws InterruptedException {
94 | final AtomicBoolean called = new AtomicBoolean();
95 | final JBossThreadFactory factory = new JBossThreadFactory(null, null, null, null, new Thread.UncaughtExceptionHandler() {
96 | public void uncaughtException(final Thread t, final Throwable e) {
97 | called.set(true);
98 | }
99 | }, null);
100 | final Thread t = factory.newThread(new Runnable() {
101 | public void run() {
102 | throw new RuntimeException("...");
103 | }
104 | });
105 | t.start();
106 | t.join();
107 | assertTrue(called.get(), "Handler was not called");
108 | }
109 |
110 | @Test
111 | public void testInitialPriority() {
112 | assertEquals(1, new JBossThreadFactory(null, null, Integer.valueOf(1), null, null, null).newThread(NULL_RUNNABLE).getPriority(), "Wrong initial thread priority");
113 | assertEquals(2, new JBossThreadFactory(null, null, Integer.valueOf(2), null, null, null).newThread(NULL_RUNNABLE).getPriority(), "Wrong initial thread priority");
114 | final ThreadGroup grp = new ThreadGroup("blah");
115 | grp.setMaxPriority(5);
116 | assertEquals(5, new JBossThreadFactory(grp, null, Integer.valueOf(10), null, null, null).newThread(NULL_RUNNABLE).getPriority(), "Wrong initial thread priority");
117 | assertEquals(1, new JBossThreadFactory(grp, null, Integer.valueOf(1), null, null, null).newThread(NULL_RUNNABLE).getPriority(), "Wrong initial thread priority");
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/test/java/org/jboss/threads/ThreadLocalResetterTests.java:
--------------------------------------------------------------------------------
1 | package org.jboss.threads;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | public class ThreadLocalResetterTests {
6 | @Test
7 | public void testResetter() {
8 | JDKSpecific.ThreadAccess.clearThreadLocals();
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/test/java/org/jboss/threads/ViewExecutorTest.java:
--------------------------------------------------------------------------------
1 | package org.jboss.threads;
2 |
3 | import static org.junit.jupiter.api.Assertions.*;
4 |
5 | import java.time.Duration;
6 | import java.util.ArrayDeque;
7 | import java.util.List;
8 | import java.util.concurrent.CopyOnWriteArrayList;
9 | import java.util.concurrent.CountDownLatch;
10 | import java.util.concurrent.Executor;
11 | import java.util.concurrent.ExecutorService;
12 | import java.util.concurrent.Executors;
13 | import java.util.concurrent.RejectedExecutionException;
14 | import java.util.concurrent.SynchronousQueue;
15 | import java.util.concurrent.ThreadPoolExecutor;
16 | import java.util.concurrent.TimeUnit;
17 | import java.util.concurrent.atomic.AtomicBoolean;
18 | import java.util.concurrent.atomic.AtomicInteger;
19 |
20 | import org.awaitility.Awaitility;
21 | import org.junit.jupiter.api.Test;
22 | import org.junit.jupiter.api.Timeout;
23 |
24 | public final class ViewExecutorTest {
25 |
26 | @Test
27 | public void testExecution() throws InterruptedException {
28 | final ViewExecutor ve = ViewExecutor.builder(JBossExecutors.directExecutor()).build();
29 | assertFalse(ve.isShutdown());
30 | assertFalse(ve.isTerminated());
31 | AtomicBoolean ran = new AtomicBoolean();
32 | ve.execute(new Runnable() {
33 | public void run() {
34 | ran.set(true);
35 | }
36 | });
37 | assertTrue(ran.get());
38 | ve.shutdown();
39 | assertTrue(ve.isShutdown());
40 | assertTrue(ve.awaitTermination(10L, TimeUnit.SECONDS));
41 | assertTrue(ve.isTerminated());
42 | ve.shutdown();
43 | assertTrue(ve.isTerminated());
44 | }
45 |
46 | @Test
47 | public void testQueuedExecution() {
48 | final ArrayDeque executedTasks = new ArrayDeque<>();
49 | Executor testExecutor = new QueuedExecutor(executedTasks);
50 | final ViewExecutor ve = ViewExecutor.builder(testExecutor).setMaxSize(1).build();
51 | AtomicBoolean ran1 = new AtomicBoolean();
52 | ve.execute(new Runnable() {
53 | public void run() {
54 | ran1.set(true);
55 | }
56 | });
57 | assertEquals(1, executedTasks.size());
58 | AtomicBoolean ran2 = new AtomicBoolean();
59 | ve.execute(new Runnable() {
60 | public void run() {
61 | ran2.set(true);
62 | }
63 | });
64 | assertEquals(1, executedTasks.size());
65 | executedTasks.poll().run();
66 | assertEquals(1, executedTasks.size());
67 | assertTrue(ran1.get());
68 | executedTasks.poll().run();
69 | assertTrue(ran2.get());
70 | assertEquals(0, executedTasks.size());
71 | }
72 |
73 | @Test
74 | public void testInterruptedShutdown() throws InterruptedException {
75 | ExecutorService testExecutor = Executors.newSingleThreadExecutor();
76 | final ViewExecutor ve = ViewExecutor.builder(testExecutor).build();
77 | AtomicBoolean intr = new AtomicBoolean();
78 | CountDownLatch runGate = new CountDownLatch(1);
79 | CountDownLatch finishGate = new CountDownLatch(1);
80 | ve.execute(new Runnable() {
81 | public void run() {
82 | runGate.countDown();
83 | try {
84 | Thread.sleep(60_000L);
85 | } catch (InterruptedException e) {
86 | intr.set(true);
87 | } finally {
88 | finishGate.countDown();
89 | }
90 | }
91 | });
92 | runGate.await();
93 | assertFalse(intr.get());
94 | ve.shutdown(true);
95 | finishGate.await();
96 | assertTrue(intr.get());
97 | testExecutor.shutdown();
98 | assertTrue(testExecutor.awaitTermination(5L, TimeUnit.SECONDS));
99 | }
100 |
101 | // TaskWrapper instances are relatively small and take a long time to knock over a JVM,
102 | // however when they aren't collected properly they will cause a GC spiral for much longer
103 | // than ten seconds. Unfortunately this test is impacted by hardware changes and may flake
104 | // (or erroneously pass on fast enough hardware).
105 | @Test
106 | @Timeout(10_000)
107 | public void testViewExecutorMemoryOverhead() {
108 | Executor directExecutor = new Executor() {
109 | @Override
110 | public void execute(Runnable command) {
111 | try {
112 | command.run();
113 | } catch (Throwable t) {
114 | Thread currentThread = Thread.currentThread();
115 | currentThread.getUncaughtExceptionHandler().uncaughtException(currentThread, t);
116 | }
117 | }
118 | };
119 | ExecutorService executorService = ViewExecutor.builder(directExecutor).build();
120 | for (long i = 0; i < 20_000_000L; i++) {
121 | executorService.execute(JBossExecutors.nullRunnable());
122 | }
123 | executorService.shutdown();
124 | assertTrue(executorService.isTerminated());
125 | }
126 |
127 | @Test
128 | public void testSameThreadDelegateDoesNotDeadlock() throws InterruptedException {
129 | Executor direct = Runnable::run;
130 | AtomicInteger completed = new AtomicInteger();
131 | ExecutorService view = ViewExecutor.builder(direct).setQueueLimit(1).setMaxSize(1).build();
132 | ExecutorService submittingExecutor = Executors.newCachedThreadPool();
133 | try {
134 | submittingExecutor.execute(() -> view.execute(() -> view.execute(completed::incrementAndGet)));
135 | Awaitility.waitAtMost(Duration.ofSeconds(1)).untilAsserted(() -> assertEquals(1, completed.get()));
136 | view.shutdown();
137 | assertTrue(view.awaitTermination(100, TimeUnit.SECONDS));
138 | } finally {
139 | submittingExecutor.shutdown();
140 | assertTrue(submittingExecutor.awaitTermination(1, TimeUnit.SECONDS));
141 | }
142 | }
143 |
144 | @Test
145 | public void testDelegateQueueProcessingRejection() throws InterruptedException {
146 | // When the active task pulls from the queue, submitting the task to the delegate will fail because it's
147 | // already consuming the only available thread. The task should be handled on the same thread.
148 | CountDownLatch taskLatch = new CountDownLatch(1);
149 | // One permit, throws RejectedExecutionException if a second task is provided
150 | ExecutorService delegate = new ThreadPoolExecutor(0, 1,
151 | 5, TimeUnit.SECONDS, new SynchronousQueue<>());
152 | try {
153 | ExecutorService view = ViewExecutor.builder(delegate).setQueueLimit(1).setMaxSize(1).build();
154 | List throwables = new CopyOnWriteArrayList<>();
155 | for (int i = 0; i < 2; i++) {
156 | view.execute(() -> {
157 | try {
158 | // latch used to ensure the second task is queued, otherwise the first task may complete
159 | // before the second is submitted.
160 | taskLatch.await();
161 | } catch (InterruptedException e) {
162 | throw new RuntimeException(e);
163 | }
164 | throwables.add(new RuntimeException("task trace"));
165 | });
166 | }
167 | taskLatch.countDown();
168 | Awaitility.waitAtMost(Duration.ofSeconds(1))
169 | .untilAsserted(() -> {
170 | assertEquals(2, throwables.size());
171 | // The stack size mustn't grow with each queued task, otherwise processing will eventually
172 | // fail running out of stack space.
173 | assertEquals(throwables.get(0).getStackTrace().length, throwables.get(1).getStackTrace().length);
174 | });
175 | } finally {
176 | delegate.shutdown();
177 | assertTrue(delegate.awaitTermination(1, TimeUnit.SECONDS));
178 | }
179 | }
180 |
181 | @Test
182 | @Timeout(5_000)
183 | public void testDelegateQueueProcessingRejectionTaskIsInterrupted() throws InterruptedException {
184 | // Subsequent queued tasks run by the same wrapper should support interruption
185 | CountDownLatch firstTaskLatch = new CountDownLatch(1);
186 | // One permit, throws RejectedExecutionException if a second task is provided
187 | ExecutorService delegate = new ThreadPoolExecutor(0, 1,
188 | 5, TimeUnit.SECONDS, new SynchronousQueue<>());
189 | try {
190 | ExecutorService view = ViewExecutor.builder(delegate).setQueueLimit(1).setMaxSize(1).build();
191 | view.submit(() -> {
192 | // latch used to ensure the second task is queued, otherwise the first task may complete
193 | // before the second is submitted.
194 | firstTaskLatch.await();
195 | return null;
196 | });
197 | AtomicBoolean interrupted = new AtomicBoolean();
198 | CountDownLatch secondTaskStartedLatch = new CountDownLatch(1);
199 | view.execute(() -> {
200 | secondTaskStartedLatch.countDown();
201 | try {
202 | Thread.sleep(10_000);
203 | } catch (InterruptedException e) {
204 | interrupted.set(true);
205 | }
206 | });
207 | firstTaskLatch.countDown();
208 | secondTaskStartedLatch.await();
209 | view.shutdownNow();
210 | assertTrue(view.awaitTermination(200, TimeUnit.MILLISECONDS));
211 | assertTrue(interrupted.get());
212 | } finally {
213 | delegate.shutdown();
214 | assertTrue(delegate.awaitTermination(1, TimeUnit.SECONDS));
215 | }
216 | }
217 |
218 | @Test
219 | public void testTestSlowExecuteInParallelWithEnqueue() throws InterruptedException {
220 | // When a thread (threadA) submits a task to a delegate, a parallel task should not be able to
221 | // successfully enqueue work. If an enqueue succeeded but delegate.execute did not, the queue
222 | // would become detached from the executor, and never flush.
223 | CountDownLatch taskLatch = new CountDownLatch(1);
224 | // One permit, throws RejectedExecutionException if a second task is provided
225 | Executor delegate = new Executor() {
226 | private final AtomicBoolean firstCall = new AtomicBoolean(true);
227 | @Override
228 | public void execute(Runnable command) {
229 | if (firstCall.getAndSet(false)) {
230 | try {
231 | taskLatch.await();
232 | } catch (InterruptedException e) {
233 | throw new RuntimeException(e);
234 | }
235 | }
236 | throw new RejectedExecutionException();
237 | }
238 | };
239 |
240 | ExecutorService testRunner = Executors.newCachedThreadPool();
241 | ExecutorService view = ViewExecutor.builder(delegate).setQueueLimit(1).setMaxSize(1).build();
242 | List throwables = new CopyOnWriteArrayList<>();
243 | for (int i = 0; i < 2; i++) {
244 | testRunner.execute(() -> {
245 | try {
246 | view.execute(NullRunnable.getInstance());
247 | throw new AssertionError("should not be reached");
248 | } catch (Throwable t) {
249 | throwables.add(t);
250 | }
251 | });
252 | }
253 | assertEquals(0, throwables.size());
254 | taskLatch.countDown();
255 | testRunner.shutdown();
256 | assertTrue(testRunner.awaitTermination(1, TimeUnit.SECONDS));
257 | assertEquals(2, throwables.size());
258 | for (Throwable throwable : throwables) {
259 | assertTrue(throwable instanceof RejectedExecutionException);
260 | }
261 | }
262 |
263 | private static class QueuedExecutor implements Executor {
264 | private final ArrayDeque executedTasks;
265 |
266 | public QueuedExecutor(final ArrayDeque executedTasks) {
267 | this.executedTasks = executedTasks;
268 | }
269 |
270 | public void execute(final Runnable command) {
271 | executedTasks.add(command);
272 | }
273 | }
274 | }
275 |
--------------------------------------------------------------------------------
/src/test/resources/logging.properties:
--------------------------------------------------------------------------------
1 | #
2 | # JBoss, Home of Professional Open Source.
3 | # Copyright 2017 Red Hat, Inc., and individual contributors
4 | # as indicated by the @author tags.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # 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, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | # Additional logger names to configure (root logger is always configured)
20 | loggers=org.jboss.threads
21 |
22 | # Root logger configuration
23 | logger.level=INFO
24 | logger.handlers=CONSOLE, FILE
25 |
26 | logger.org.jboss.threads.level=${test.level:INFO}
27 |
28 | # Console handler configuration
29 | handler.CONSOLE=org.jboss.logmanager.handlers.ConsoleHandler
30 | handler.CONSOLE.properties=autoFlush
31 | handler.CONSOLE.level=ALL
32 | handler.CONSOLE.autoFlush=true
33 | handler.CONSOLE.formatter=PATTERN
34 |
35 | # File handler configuration
36 | handler.FILE=org.jboss.logmanager.handlers.FileHandler
37 | handler.FILE.level=ALL
38 | handler.FILE.properties=autoFlush,fileName
39 | handler.FILE.autoFlush=true
40 | handler.FILE.fileName=./target/test.log
41 | handler.FILE.formatter=PATTERN
42 |
43 | # Formatter pattern configuration
44 | formatter.PATTERN=org.jboss.logmanager.formatters.PatternFormatter
45 | formatter.PATTERN.properties=pattern
46 | formatter.PATTERN.pattern=%d{HH:mm:ss,SSS} %-5p [%c] (%t) %s%e%n
47 |
48 |
--------------------------------------------------------------------------------