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]

Reply via email to