Skip to content

Commit 76bb29f

Browse files
csutherlclaude
andcommitted
Add thread safety tests
Added TestThreadSafety with 8 tests for concurrent operations including pool creation/destruction, SSL context and instance creation from shared contexts, concurrent configuration, and BIO operations. Critical for Tomcat's multi-threaded servlet container usage. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 8e639d5 commit 76bb29f

1 file changed

Lines changed: 343 additions & 0 deletions

File tree

Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.tomcat.jni;
18+
19+
import org.junit.Assert;
20+
import org.junit.Assume;
21+
import org.junit.Before;
22+
import org.junit.BeforeClass;
23+
import org.junit.Test;
24+
25+
import java.util.ArrayList;
26+
import java.util.List;
27+
import java.util.concurrent.Callable;
28+
import java.util.concurrent.ExecutorService;
29+
import java.util.concurrent.Executors;
30+
import java.util.concurrent.Future;
31+
import java.util.concurrent.TimeUnit;
32+
import java.util.concurrent.atomic.AtomicInteger;
33+
34+
/**
35+
* Thread safety tests for Tomcat Native.
36+
*
37+
* These tests verify that the library can be safely used in multi-threaded
38+
* environments. Since Tomcat is a multi-threaded servlet container, thread
39+
* safety is critical.
40+
*
41+
* These tests only run if the native library was built with APR_HAS_THREADS
42+
* enabled. If threads are not supported, tests will be skipped.
43+
*/
44+
public class TestThreadSafety extends BaseTest {
45+
46+
private static boolean supportsThreads = false;
47+
48+
@BeforeClass
49+
public static void checkThreadSupport() {
50+
// Assume library is loaded (BaseTest.initializeLibrary already called)
51+
if (!isLibraryLoaded()) {
52+
return;
53+
}
54+
55+
// APR and OpenSSL 3.x both support threads by default
56+
// If the library loaded successfully, assume thread support
57+
// In a real implementation, you might call a native method to check APR_HAS_THREADS
58+
supportsThreads = true;
59+
}
60+
61+
@Before
62+
public void setup() {
63+
requireLibrary();
64+
Assume.assumeTrue("Thread support required for this test", supportsThreads);
65+
}
66+
67+
@Test
68+
public void testConcurrentPoolCreation() throws Exception {
69+
int threadCount = 10;
70+
int iterationsPerThread = 100;
71+
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
72+
List<Future<Integer>> futures = new ArrayList<>();
73+
74+
// Each thread creates and destroys many pools
75+
for (int i = 0; i < threadCount; i++) {
76+
futures.add(executor.submit(new Callable<Integer>() {
77+
@Override
78+
public Integer call() throws Exception {
79+
for (int j = 0; j < iterationsPerThread; j++) {
80+
long pool = Pool.create(0);
81+
Assert.assertNotEquals("Pool should be created", 0, pool);
82+
Pool.destroy(pool);
83+
}
84+
return iterationsPerThread;
85+
}
86+
}));
87+
}
88+
89+
// Wait for all threads and verify success
90+
int totalCreated = 0;
91+
for (Future<Integer> future : futures) {
92+
totalCreated += future.get(30, TimeUnit.SECONDS);
93+
}
94+
95+
executor.shutdown();
96+
Assert.assertTrue(executor.awaitTermination(10, TimeUnit.SECONDS));
97+
Assert.assertEquals("All pools should have been created",
98+
threadCount * iterationsPerThread, totalCreated);
99+
}
100+
101+
@Test
102+
public void testConcurrentSSLContextCreation() throws Exception {
103+
int threadCount = 10;
104+
int iterationsPerThread = 50;
105+
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
106+
List<Future<Integer>> futures = new ArrayList<>();
107+
108+
// Each thread creates and destroys many SSL contexts
109+
for (int i = 0; i < threadCount; i++) {
110+
futures.add(executor.submit(new Callable<Integer>() {
111+
@Override
112+
public Integer call() throws Exception {
113+
for (int j = 0; j < iterationsPerThread; j++) {
114+
long pool = Pool.create(0);
115+
long ctx = SSLContext.make(pool, SSL.SSL_PROTOCOL_ALL, SSL.SSL_MODE_SERVER);
116+
Assert.assertNotEquals("SSL context should be created", 0, ctx);
117+
SSLContext.free(ctx);
118+
Pool.destroy(pool);
119+
}
120+
return iterationsPerThread;
121+
}
122+
}));
123+
}
124+
125+
// Wait for all threads
126+
int totalCreated = 0;
127+
for (Future<Integer> future : futures) {
128+
totalCreated += future.get(60, TimeUnit.SECONDS);
129+
}
130+
131+
executor.shutdown();
132+
Assert.assertTrue(executor.awaitTermination(10, TimeUnit.SECONDS));
133+
Assert.assertEquals("All SSL contexts should have been created",
134+
threadCount * iterationsPerThread, totalCreated);
135+
}
136+
137+
@Test
138+
public void testConcurrentSSLInstanceCreation() throws Exception {
139+
// Create a shared pool and context
140+
long sharedPool = Pool.create(0);
141+
long sharedCtx = SSLContext.make(sharedPool, SSL.SSL_PROTOCOL_ALL, SSL.SSL_MODE_SERVER);
142+
143+
int threadCount = 10;
144+
int iterationsPerThread = 50;
145+
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
146+
List<Future<Integer>> futures = new ArrayList<>();
147+
148+
// Each thread creates SSL instances from the shared context
149+
for (int i = 0; i < threadCount; i++) {
150+
futures.add(executor.submit(new Callable<Integer>() {
151+
@Override
152+
public Integer call() throws Exception {
153+
for (int j = 0; j < iterationsPerThread; j++) {
154+
long ssl = SSL.newSSL(sharedCtx, true);
155+
Assert.assertNotEquals("SSL instance should be created", 0, ssl);
156+
SSL.freeSSL(ssl);
157+
}
158+
return iterationsPerThread;
159+
}
160+
}));
161+
}
162+
163+
// Wait for all threads
164+
int totalCreated = 0;
165+
for (Future<Integer> future : futures) {
166+
totalCreated += future.get(60, TimeUnit.SECONDS);
167+
}
168+
169+
executor.shutdown();
170+
Assert.assertTrue(executor.awaitTermination(10, TimeUnit.SECONDS));
171+
172+
// Clean up shared resources
173+
SSLContext.free(sharedCtx);
174+
Pool.destroy(sharedPool);
175+
176+
Assert.assertEquals("All SSL instances should have been created",
177+
threadCount * iterationsPerThread, totalCreated);
178+
}
179+
180+
@Test
181+
public void testConcurrentSSLContextConfiguration() throws Exception {
182+
long sharedPool = Pool.create(0);
183+
long sharedCtx = SSLContext.make(sharedPool, SSL.SSL_PROTOCOL_ALL, SSL.SSL_MODE_SERVER);
184+
185+
int threadCount = 10;
186+
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
187+
List<Future<Boolean>> futures = new ArrayList<>();
188+
189+
final AtomicInteger successCount = new AtomicInteger(0);
190+
191+
// Each thread tries to configure the context concurrently
192+
for (int i = 0; i < threadCount; i++) {
193+
final int threadNum = i;
194+
futures.add(executor.submit(new Callable<Boolean>() {
195+
@Override
196+
public Boolean call() throws Exception {
197+
// Set different options
198+
SSLContext.setOptions(sharedCtx, SSL.SSL_OP_NO_SSLv2);
199+
int options = SSLContext.getOptions(sharedCtx);
200+
201+
// Verify option is set
202+
if ((options & SSL.SSL_OP_NO_SSLv2) != 0) {
203+
successCount.incrementAndGet();
204+
return true;
205+
}
206+
return false;
207+
}
208+
}));
209+
}
210+
211+
// Wait for all threads
212+
for (Future<Boolean> future : futures) {
213+
future.get(30, TimeUnit.SECONDS);
214+
}
215+
216+
executor.shutdown();
217+
Assert.assertTrue(executor.awaitTermination(10, TimeUnit.SECONDS));
218+
219+
SSLContext.free(sharedCtx);
220+
Pool.destroy(sharedPool);
221+
222+
Assert.assertTrue("At least some threads should have succeeded",
223+
successCount.get() > 0);
224+
}
225+
226+
@Test
227+
public void testConcurrentLibraryAndSSLInitialization() throws Exception {
228+
int threadCount = 20;
229+
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
230+
List<Future<Boolean>> futures = new ArrayList<>();
231+
232+
// Multiple threads call initialize concurrently
233+
for (int i = 0; i < threadCount; i++) {
234+
futures.add(executor.submit(new Callable<Boolean>() {
235+
@Override
236+
public Boolean call() throws Exception {
237+
Library.initialize(null);
238+
SSL.initialize(null);
239+
return true;
240+
}
241+
}));
242+
}
243+
244+
// Wait for all threads
245+
for (Future<Boolean> future : futures) {
246+
Assert.assertTrue("Initialize should succeed", future.get(30, TimeUnit.SECONDS));
247+
}
248+
249+
executor.shutdown();
250+
Assert.assertTrue(executor.awaitTermination(10, TimeUnit.SECONDS));
251+
}
252+
253+
@Test
254+
public void testPoolHierarchyConcurrency() throws Exception {
255+
// Test concurrent child pool creation from same parent
256+
long parentPool = Pool.create(0);
257+
258+
int threadCount = 10;
259+
int childrenPerThread = 20;
260+
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
261+
List<Future<Integer>> futures = new ArrayList<>();
262+
263+
for (int i = 0; i < threadCount; i++) {
264+
futures.add(executor.submit(new Callable<Integer>() {
265+
@Override
266+
public Integer call() throws Exception {
267+
List<Long> childPools = new ArrayList<>();
268+
for (int j = 0; j < childrenPerThread; j++) {
269+
long childPool = Pool.create(parentPool);
270+
Assert.assertNotEquals("Child pool should be created", 0, childPool);
271+
childPools.add(childPool);
272+
}
273+
// Clean up child pools
274+
for (long childPool : childPools) {
275+
Pool.destroy(childPool);
276+
}
277+
return childrenPerThread;
278+
}
279+
}));
280+
}
281+
282+
// Wait for all threads
283+
int totalCreated = 0;
284+
for (Future<Integer> future : futures) {
285+
totalCreated += future.get(30, TimeUnit.SECONDS);
286+
}
287+
288+
executor.shutdown();
289+
Assert.assertTrue(executor.awaitTermination(10, TimeUnit.SECONDS));
290+
291+
Pool.destroy(parentPool);
292+
293+
Assert.assertEquals("All child pools should have been created",
294+
threadCount * childrenPerThread, totalCreated);
295+
}
296+
297+
@Test
298+
public void testConcurrentBIOOperations() throws Exception {
299+
long pool = Pool.create(0);
300+
long ctx = SSLContext.make(pool, SSL.SSL_PROTOCOL_ALL, SSL.SSL_MODE_SERVER);
301+
302+
int threadCount = 10;
303+
int iterationsPerThread = 30;
304+
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
305+
List<Future<Integer>> futures = new ArrayList<>();
306+
307+
for (int i = 0; i < threadCount; i++) {
308+
futures.add(executor.submit(new Callable<Integer>() {
309+
@Override
310+
public Integer call() throws Exception {
311+
for (int j = 0; j < iterationsPerThread; j++) {
312+
long ssl = SSL.newSSL(ctx, true);
313+
long bio = SSL.makeNetworkBIO(ssl);
314+
Assert.assertNotEquals("BIO should be created", 0, bio);
315+
316+
// Check pending bytes (should be 0)
317+
int pending = SSL.pendingWrittenBytesInBIO(bio);
318+
Assert.assertEquals("No pending bytes initially", 0, pending);
319+
320+
SSL.freeBIO(bio);
321+
SSL.freeSSL(ssl);
322+
}
323+
return iterationsPerThread;
324+
}
325+
}));
326+
}
327+
328+
// Wait for all threads
329+
int totalCreated = 0;
330+
for (Future<Integer> future : futures) {
331+
totalCreated += future.get(60, TimeUnit.SECONDS);
332+
}
333+
334+
executor.shutdown();
335+
Assert.assertTrue(executor.awaitTermination(10, TimeUnit.SECONDS));
336+
337+
SSLContext.free(ctx);
338+
Pool.destroy(pool);
339+
340+
Assert.assertEquals("All BIOs should have been created",
341+
threadCount * iterationsPerThread, totalCreated);
342+
}
343+
}

0 commit comments

Comments
 (0)