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]
>
>

Reply via email to