This is an automated email from the ASF dual-hosted git repository. csutherl pushed a commit to branch add-java-unit-tests-11 in repository https://gitbox.apache.org/repos/asf/tomcat-native.git
commit 76bb29f5f4d347f2045b14924f3c029aaecdabb7 Author: Coty Sutherland <[email protected]> AuthorDate: Wed Jan 7 11:55:59 2026 -0500 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 <[email protected]> --- test/org/apache/tomcat/jni/TestThreadSafety.java | 343 +++++++++++++++++++++++ 1 file changed, 343 insertions(+) diff --git a/test/org/apache/tomcat/jni/TestThreadSafety.java b/test/org/apache/tomcat/jni/TestThreadSafety.java new file mode 100644 index 000000000..3ad177ac9 --- /dev/null +++ b/test/org/apache/tomcat/jni/TestThreadSafety.java @@ -0,0 +1,343 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.tomcat.jni; + +import org.junit.Assert; +import org.junit.Assume; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Thread safety tests for Tomcat Native. + * + * These tests verify that the library can be safely used in multi-threaded + * environments. Since Tomcat is a multi-threaded servlet container, thread + * safety is critical. + * + * These tests only run if the native library was built with APR_HAS_THREADS + * enabled. If threads are not supported, tests will be skipped. + */ +public class TestThreadSafety extends BaseTest { + + private static boolean supportsThreads = false; + + @BeforeClass + public static void checkThreadSupport() { + // Assume library is loaded (BaseTest.initializeLibrary already called) + if (!isLibraryLoaded()) { + return; + } + + // APR and OpenSSL 3.x both support threads by default + // If the library loaded successfully, assume thread support + // In a real implementation, you might call a native method to check APR_HAS_THREADS + supportsThreads = true; + } + + @Before + public void setup() { + requireLibrary(); + Assume.assumeTrue("Thread support required for this test", supportsThreads); + } + + @Test + public void testConcurrentPoolCreation() throws Exception { + int threadCount = 10; + int iterationsPerThread = 100; + ExecutorService executor = Executors.newFixedThreadPool(threadCount); + List<Future<Integer>> futures = new ArrayList<>(); + + // Each thread creates and destroys many pools + for (int i = 0; i < threadCount; i++) { + futures.add(executor.submit(new Callable<Integer>() { + @Override + public Integer call() throws Exception { + for (int j = 0; j < iterationsPerThread; j++) { + long pool = Pool.create(0); + Assert.assertNotEquals("Pool should be created", 0, pool); + Pool.destroy(pool); + } + return iterationsPerThread; + } + })); + } + + // Wait for all threads and verify success + int totalCreated = 0; + for (Future<Integer> future : futures) { + totalCreated += future.get(30, TimeUnit.SECONDS); + } + + executor.shutdown(); + Assert.assertTrue(executor.awaitTermination(10, TimeUnit.SECONDS)); + Assert.assertEquals("All pools should have been created", + threadCount * iterationsPerThread, totalCreated); + } + + @Test + public void testConcurrentSSLContextCreation() throws Exception { + int threadCount = 10; + int iterationsPerThread = 50; + ExecutorService executor = Executors.newFixedThreadPool(threadCount); + List<Future<Integer>> futures = new ArrayList<>(); + + // Each thread creates and destroys many SSL contexts + for (int i = 0; i < threadCount; i++) { + futures.add(executor.submit(new Callable<Integer>() { + @Override + public Integer call() throws Exception { + for (int j = 0; j < iterationsPerThread; j++) { + long pool = Pool.create(0); + long ctx = SSLContext.make(pool, SSL.SSL_PROTOCOL_ALL, SSL.SSL_MODE_SERVER); + Assert.assertNotEquals("SSL context should be created", 0, ctx); + SSLContext.free(ctx); + Pool.destroy(pool); + } + return iterationsPerThread; + } + })); + } + + // Wait for all threads + int totalCreated = 0; + for (Future<Integer> future : futures) { + totalCreated += future.get(60, TimeUnit.SECONDS); + } + + executor.shutdown(); + Assert.assertTrue(executor.awaitTermination(10, TimeUnit.SECONDS)); + Assert.assertEquals("All SSL contexts should have been created", + threadCount * iterationsPerThread, totalCreated); + } + + @Test + public void testConcurrentSSLInstanceCreation() throws Exception { + // Create a shared pool and context + long sharedPool = Pool.create(0); + long sharedCtx = SSLContext.make(sharedPool, SSL.SSL_PROTOCOL_ALL, SSL.SSL_MODE_SERVER); + + int threadCount = 10; + int iterationsPerThread = 50; + ExecutorService executor = Executors.newFixedThreadPool(threadCount); + List<Future<Integer>> futures = new ArrayList<>(); + + // Each thread creates SSL instances from the shared context + for (int i = 0; i < threadCount; i++) { + futures.add(executor.submit(new Callable<Integer>() { + @Override + public Integer call() throws Exception { + for (int j = 0; j < iterationsPerThread; j++) { + long ssl = SSL.newSSL(sharedCtx, true); + Assert.assertNotEquals("SSL instance should be created", 0, ssl); + SSL.freeSSL(ssl); + } + return iterationsPerThread; + } + })); + } + + // Wait for all threads + int totalCreated = 0; + for (Future<Integer> future : futures) { + totalCreated += future.get(60, TimeUnit.SECONDS); + } + + executor.shutdown(); + Assert.assertTrue(executor.awaitTermination(10, TimeUnit.SECONDS)); + + // Clean up shared resources + SSLContext.free(sharedCtx); + Pool.destroy(sharedPool); + + Assert.assertEquals("All SSL instances should have been created", + threadCount * iterationsPerThread, totalCreated); + } + + @Test + public void testConcurrentSSLContextConfiguration() throws Exception { + long sharedPool = Pool.create(0); + long sharedCtx = SSLContext.make(sharedPool, SSL.SSL_PROTOCOL_ALL, SSL.SSL_MODE_SERVER); + + int threadCount = 10; + ExecutorService executor = Executors.newFixedThreadPool(threadCount); + List<Future<Boolean>> futures = new ArrayList<>(); + + final AtomicInteger successCount = new AtomicInteger(0); + + // Each thread tries to configure the context concurrently + for (int i = 0; i < threadCount; i++) { + final int threadNum = i; + futures.add(executor.submit(new Callable<Boolean>() { + @Override + public Boolean call() throws Exception { + // Set different options + SSLContext.setOptions(sharedCtx, SSL.SSL_OP_NO_SSLv2); + int options = SSLContext.getOptions(sharedCtx); + + // Verify option is set + if ((options & SSL.SSL_OP_NO_SSLv2) != 0) { + successCount.incrementAndGet(); + return true; + } + return false; + } + })); + } + + // Wait for all threads + for (Future<Boolean> future : futures) { + future.get(30, TimeUnit.SECONDS); + } + + executor.shutdown(); + Assert.assertTrue(executor.awaitTermination(10, TimeUnit.SECONDS)); + + SSLContext.free(sharedCtx); + Pool.destroy(sharedPool); + + Assert.assertTrue("At least some threads should have succeeded", + successCount.get() > 0); + } + + @Test + public void testConcurrentLibraryAndSSLInitialization() throws Exception { + int threadCount = 20; + ExecutorService executor = Executors.newFixedThreadPool(threadCount); + List<Future<Boolean>> futures = new ArrayList<>(); + + // Multiple threads call initialize concurrently + for (int i = 0; i < threadCount; i++) { + futures.add(executor.submit(new Callable<Boolean>() { + @Override + public Boolean call() throws Exception { + Library.initialize(null); + SSL.initialize(null); + return true; + } + })); + } + + // Wait for all threads + for (Future<Boolean> future : futures) { + Assert.assertTrue("Initialize should succeed", future.get(30, TimeUnit.SECONDS)); + } + + executor.shutdown(); + Assert.assertTrue(executor.awaitTermination(10, TimeUnit.SECONDS)); + } + + @Test + public void testPoolHierarchyConcurrency() throws Exception { + // Test concurrent child pool creation from same parent + long parentPool = Pool.create(0); + + int threadCount = 10; + int childrenPerThread = 20; + ExecutorService executor = Executors.newFixedThreadPool(threadCount); + List<Future<Integer>> futures = new ArrayList<>(); + + for (int i = 0; i < threadCount; i++) { + futures.add(executor.submit(new Callable<Integer>() { + @Override + public Integer call() throws Exception { + List<Long> childPools = new ArrayList<>(); + for (int j = 0; j < childrenPerThread; j++) { + long childPool = Pool.create(parentPool); + Assert.assertNotEquals("Child pool should be created", 0, childPool); + childPools.add(childPool); + } + // Clean up child pools + for (long childPool : childPools) { + Pool.destroy(childPool); + } + return childrenPerThread; + } + })); + } + + // Wait for all threads + int totalCreated = 0; + for (Future<Integer> future : futures) { + totalCreated += future.get(30, TimeUnit.SECONDS); + } + + executor.shutdown(); + Assert.assertTrue(executor.awaitTermination(10, TimeUnit.SECONDS)); + + Pool.destroy(parentPool); + + Assert.assertEquals("All child pools should have been created", + threadCount * childrenPerThread, totalCreated); + } + + @Test + public void testConcurrentBIOOperations() throws Exception { + long pool = Pool.create(0); + long ctx = SSLContext.make(pool, SSL.SSL_PROTOCOL_ALL, SSL.SSL_MODE_SERVER); + + int threadCount = 10; + int iterationsPerThread = 30; + ExecutorService executor = Executors.newFixedThreadPool(threadCount); + List<Future<Integer>> futures = new ArrayList<>(); + + for (int i = 0; i < threadCount; i++) { + futures.add(executor.submit(new Callable<Integer>() { + @Override + public Integer call() throws Exception { + for (int j = 0; j < iterationsPerThread; j++) { + long ssl = SSL.newSSL(ctx, true); + long bio = SSL.makeNetworkBIO(ssl); + Assert.assertNotEquals("BIO should be created", 0, bio); + + // Check pending bytes (should be 0) + int pending = SSL.pendingWrittenBytesInBIO(bio); + Assert.assertEquals("No pending bytes initially", 0, pending); + + SSL.freeBIO(bio); + SSL.freeSSL(ssl); + } + return iterationsPerThread; + } + })); + } + + // Wait for all threads + int totalCreated = 0; + for (Future<Integer> future : futures) { + totalCreated += future.get(60, TimeUnit.SECONDS); + } + + executor.shutdown(); + Assert.assertTrue(executor.awaitTermination(10, TimeUnit.SECONDS)); + + SSLContext.free(ctx); + Pool.destroy(pool); + + Assert.assertEquals("All BIOs should have been created", + threadCount * iterationsPerThread, totalCreated); + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
