Skip to content

Artificial fibers. Allows native threads to be transparently reused

License

Notifications You must be signed in to change notification settings

palantir/nylon-threads

Repository files navigation

Nylon Threads

Nylon is an artificial fiber.

This library provides functionality to transparently reuse a single pool of threads between many executor objects, each of which may be configured with a separate concurrency limit, queue size, and observability wrapper.

We expect that this functionality will no longer be needed once Loom becomes generally available.

Usage

NylonExecutor

The NylonExecutor utility allows a common cached executor to be shared between consumers with different constraints, with behavior matching a cached or fixed-size executor, using a configurable-sized queue. This utility handles renaming threads when they're in use such that the thread-names match what would be expected of an isolated (non-shared) pool.

This is important in applications which use several executors, often work is passed through several stages, each of which rely on an executor. When these executors each use their own thread pool, sufficient threads must be available for all stages, despite the fact that all stages are not (and often cannot be) saturated at all times. Threads use a fairly large amount of memory, are expensive to create and park, and impact garbage collectors in unexpected ways, so it's advantageous to reuse a smaller pool.

// Core threadpool is only used to create NylonExecutor views
Executor threadPool = Executors.newCachedThreadPool();

// All code interacts with these views
        
// executorOne emulates a cached executor, it has
// no thread limit. This is useful because it can be
// wrapped with metrics, tracing, and logging.
ExecutorService executorOne = NylonExecutor.builder()
    .name("one")
    .executor(threadPool)
    .build();

// executorTwo emulates a fixed-size executor, it has
// a thread limit and maximum queue size, despite the
// thread pool itself providing neither of these features.
ExecutorService executorTwo = NylonExecutor.builder()
    .name("two")
    .executor(threadPool)
    .maxThreads(5)
    .queueSize(100)
    .build();

ThreadNames

The ThreadNames utility allows thread names to be updated just like Thread.setName, however it only updates the name used by Java, not the OS thread name which can have unexpected costs in some configurations. For example, cgrulesengd may handle events each time a thread is renamed, potentially far more expensive than the update operation and syscall themselves.

This may reduce the utility of some observability tools which interact with generic processes, however JVM-based tools like jstack and logging frameworks continue to use the java thread name as expected.

VirtualThreads

The ThreadNames utility allows virtual threads to be safely used from libraries which target older jdk bytecode when run using a jdk-21+ runtime.

VirtualThreads provides a static utility method to detect virtual threads:

boolean virtual = VirtualThreads.isVirtual(Thread.currentThread())

As well as an API shim over Thread.ofVirtual():

VirtualThreadSupport support = VirtualThreads.get().orElseThrow();
ExecutorService executor = support.newThreadPerTaskExecutor(support.ofVirtual().factory());

Gradle Tasks

./gradlew tasks - to get the list of gradle tasks

Start Developing

Run one of the following commands:

  • ./gradlew idea for IntelliJ
  • ./gradlew eclipse for Eclipse