Skip to content

bug: JdkHttpApiServer executor uses default thread factory with non-descriptive names #294

@sfloess

Description

@sfloess

Bug Description

The executor service is created with Executors.newFixedThreadPool(10) which uses the default thread factory. This creates threads with generic names like "pool-1-thread-1" making it difficult to identify API server threads in thread dumps or monitoring tools.

Location

jplatform-rest-api/src/main/java/org/flossware/jplatform/rest/JdkHttpApiServer.java:136

Problematic Code

// Configure executor for handling requests
executor = Executors.newFixedThreadPool(10);  // Default thread factory!
server.setExecutor(executor);

Impact

  • Difficult debugging: Can't identify API threads in thread dumps
  • Poor monitoring: Thread metrics not labeled properly
  • Confusing profiling: Generic names in profiler output
  • Thread leak detection: Hard to find which component leaked threads
  • Production troubleshooting: Can't tell what threads are doing

Example

JdkHttpApiServer server = new JdkHttpApiServer(config, manager);
server.start();

// Thread dump shows:
"pool-1-thread-1" #15 prio=5 os_prio=0 tid=0x00007f1234567890 nid=0x1234 waiting
"pool-1-thread-2" #16 prio=5 os_prio=0 tid=0x00007f1234567891 nid=0x1235 waiting
"pool-1-thread-3" #17 prio=5 os_prio=0 tid=0x00007f1234567892 nid=0x1236 waiting

// What are these threads for?
// - API server?
// - Database connection pool?
// - Background job executor?
// - Message queue consumer?
// Can't tell without stack trace!

// In production with multiple thread pools:
"pool-1-thread-1" ... "pool-1-thread-10"  <- API server
"pool-2-thread-1" ... "pool-2-thread-20"  <- Database pool
"pool-3-thread-1" ... "pool-3-thread-5"   <- Job executor
"pool-4-thread-1" ... "pool-4-thread-50"  <- Message consumer

// All generic names, impossible to distinguish

// When thread leak occurs:
// Thread count keeps growing
// Monitoring shows "pool-N-thread-X" increasing
// Which component is leaking? Can't tell
// Must attach debugger and inspect each thread

Proposed Fix

Use a custom thread factory with descriptive names:

import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

// Configure executor for handling requests with custom thread factory
executor = Executors.newFixedThreadPool(10, new ThreadFactory() {
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    
    @Override
    public Thread newThread(Runnable r) {
        Thread thread = new Thread(r, "jplatform-api-server-" + threadNumber.getAndIncrement());
        thread.setDaemon(false);  // Explicit non-daemon
        thread.setPriority(Thread.NORM_PRIORITY);
        thread.setUncaughtExceptionHandler((t, e) -> {
            logger.error("Uncaught exception in thread {}", t.getName(), e);
        });
        return thread;
    }
});
server.setExecutor(executor);

// Now thread dump shows:
"jplatform-api-server-1" #15 prio=5 os_prio=0 tid=0x00007f1234567890 nid=0x1234 waiting
"jplatform-api-server-2" #16 prio=5 os_prio=0 tid=0x00007f1234567891 nid=0x1235 waiting
"jplatform-api-server-3" #17 prio=5 os_prio=0 tid=0x00007f1234567892 nid=0x1236 waiting

// Much clearer!

Or use Guava's ThreadFactoryBuilder:

import com.google.common.util.concurrent.ThreadFactoryBuilder;

executor = Executors.newFixedThreadPool(10, 
    new ThreadFactoryBuilder()
        .setNameFormat("jplatform-api-server-%d")
        .setDaemon(false)
        .setPriority(Thread.NORM_PRIORITY)
        .setUncaughtExceptionHandler((t, e) -> 
            logger.error("Uncaught exception in {}", t.getName(), e))
        .build()
);
server.setExecutor(executor);

Or create reusable factory class:

private static class NamedThreadFactory implements ThreadFactory {
    private final String namePrefix;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final boolean daemon;
    
    NamedThreadFactory(String namePrefix, boolean daemon) {
        this.namePrefix = namePrefix;
        this.daemon = daemon;
    }
    
    @Override
    public Thread newThread(Runnable r) {
        Thread thread = new Thread(r, namePrefix + threadNumber.getAndIncrement());
        thread.setDaemon(daemon);
        thread.setUncaughtExceptionHandler((t, e) -> {
            LoggerFactory.getLogger(t.getName()).error("Uncaught exception", e);
        });
        return thread;
    }
}

// Usage:
executor = Executors.newFixedThreadPool(10, 
    new NamedThreadFactory("jplatform-api-server-", false));

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions