Skip to content

Core Functionality

Idan Sheinberg edited this page Oct 16, 2020 · 38 revisions

This section of the wiki covers core concepts and functionality, placed in the needle-core module

These are the foundations on which the entire needle suite of libraries and modules are built upon.
Taking the time to familiarize yourself them will help you make the most out of this library while avoiding common operational mistakes.

Affinity Descriptor

Simply put, an AffinityDescriptor contains core affinity specifications. It has two factory methods you can choose from

Textual specification

var descriptor = AffinityDescriptor.from("0-2,4");

Binary mask

var descriptor = AffinityDescriptor.from(23);

Notes

  • The above two affinity descriptors are effectually identical: "0-2,4" implies cores 1,2,3 and 5 (the core count starts at 0) while 23, which is 10111 in binary, refers to an identical core specification (core #1 is the right-most bit).
  • Using either AffinityDescriptor.from("") or AffinityDescriptor.from(0) will result in a 'no affinity' (empty) descriptor. This is useful for scenarios where affinity settings are irrelevant or undesired (for example, when running tests)

Pinned Thread

Basic Usage

At the core of this library lies the PinnedThread - a JVM Thread subclass that adds affinity awareness.
Once the thread is started, affinity settings passed via the AffinityDescriptor are applied.

AffinityDescriptor descriptor = AffinityDescriptor.from("0-1,4-5");
Runnable runnable = () -> System.out.printf("My affinity - '%s'%n", Pinned.current().affinity());
PinnedThread thread = new PinnedThread(runnable, descriptor);
thread.start();
thread.join();

Mutable Affinity

For maximum flexibility, a PinnedThread can be started with or without an initial affinity, as various constructors exist.
Unlike traditional affinity solutions, one of the key strengths of the PinnedThread is its ability have its affinity settings altered externally (not from within the thread's execution scope), beyond its inception!

Runnable runnable = () -> {
   try {
         System.out.printf("My affinity - '%s'%n",  Pinned.current().affinity());
         Thread current = Thread.currentThread();
         synchronized (current) { current.wait(); }
   } catch (InterruptedException ix) {
   } finally {
     System.out.printf("My affinity - '%s'%n",  Pinned.current().affinity());    
   }
};
PinnedThread thread = new PinnedThread(runnable);
thread.start();
Thread.sleep(1000L);
AffinityDescriptor descriptor = AffinityDescriptor.from(3L);
thread.affinity(descriptor);
thread.interrupt();
thread.join();

Caveats

The only requirement is for the thread to be started before trying to access affinity related functionality. Failure in doing so will result in an error

Runnable runnable = () -> {};
PinnedThread thread = new PinnedThread(runnable);
AffinityDescriptor descriptor = AffinityDescriptor.from("1,3");
thread.affinity(descriptor); // a NeedleException is thrown

Subclassing the PinnedThread class is also a viable option, if your design works that way. Note that this approach requires additional attention to affinity setting initialization.

class ExtensionThread extends PinnedThread {
   
   ExtensionThread(AffinityDescriptor descriptor) {
      super(descriptor);
   }

   @Override
   public void run() {
      super.run(); // Always call super.run() first in order to ensure affinity settings are properly applied

      // Your code goes here
   }
}

Minimal Integration

In scenarios where it's undesirable or impossible to replace Thread implementations with PinnedThread, you can apply affinity setting from within the run() method in the following way:

// Put the affinity setting inside the Runnable
Runnable runnable = () -> {
   var descriptor = AffinityDescriptor.from("0-1,3");
   Needle.affinity(descriptor);

   // Your code goes here
};

// Create a thread and start it
Thread thread = new Thread(runnable);
thread.start();