Dangit! I'm reallllly sorry for the spam y'all. I'm doing some experimentation and my repo remotes are backwards apparently; I meant to push the branches to my fork instead so I could start submitting some PRs for review.
Please ignore the emails for the 11 new branches in tomcat-native; I've already pruned them ;( On Wed, Jan 7, 2026 at 2:35 PM <[email protected]> wrote: > 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] > >
