All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.avaje.json.stream.core.HybridBufferRecycler Maven / Gradle / Ivy

There is a newer version: 3.0-RC2
Show newest version
package io.avaje.json.stream.core;

import io.avaje.json.stream.JsonOutput;
import io.avaje.json.stream.core.Recyclers.ThreadLocalPool;

import java.io.InputStream;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.concurrent.atomic.AtomicReferenceArray;
import java.util.function.Predicate;

/**
 * This is a custom implementation of the Jackson's RecyclerPool intended to work equally well with
 * both platform and virtual threads. This pool works regardless of the version of the JVM in use
 * and internally uses 2 distinct pools one for platform threads (which is exactly the same {@link
 * ThreadLocal} based one provided by Jackson out of the box) and the other designed for being
 * virtual threads friendly. It switches between the 2 only depending on the nature of thread
 * (virtual or not) requiring the acquisition of a pooled resource, obtained via {@link
 * MethodHandle} to guarantee compatibility also with old JVM versions. The pool also guarantees
 * that the pooled resource is always released to the same internal pool from where it has been
 * acquired, regardless if the releasing thread is different from the one that originally made the
 * acquisition.
 *
 * 

The virtual thread friendly inner pool is implemented with N striped linked lists using a * simple lock free algorithm based on CAS. The striping is performed shuffling the id of the thread * requiring to acquire a pooled resource with a xorshift based computation. The resulting of this * computation is also stored in the pooled resource, bringing the twofold advantage of always * releasing the resource in the same bucket from where it has been taken regardless if the * releasing thread is different from the one that did the acquisition and avoiding the need of * recalculating the position of that bucket also during the release. The heads of the linked lists * are hold in an {@link AtomicReferenceArray} where each head has a distance of 16 positions from * the adjacent ones to prevent the false sharing problem. */ @SuppressWarnings("resource") final class HybridBufferRecycler implements BufferRecycler { private static final HybridBufferRecycler INSTANCE = new HybridBufferRecycler(); private static final Predicate isVirtual = VirtualPredicate.findIsVirtualPredicate(); private static final BufferRecycler NATIVE_RECYCLER = ThreadLocalPool.shared(); private static final BufferRecycler VIRTUAL_RECYCLER = StripedLockFreePool.shared(); private HybridBufferRecycler() { } static HybridBufferRecycler shared() { return INSTANCE; } @Override public JsonGenerator generator(JsonOutput target) { return isVirtual.test(Thread.currentThread()) ? VIRTUAL_RECYCLER.generator(target) : NATIVE_RECYCLER.generator(target); } @Override public JsonParser parser(byte[] bytes) { return isVirtual.test(Thread.currentThread()) ? VIRTUAL_RECYCLER.parser(bytes) : NATIVE_RECYCLER.parser(bytes); } @Override public JsonParser parser(InputStream in) { return isVirtual.test(Thread.currentThread()) ? VIRTUAL_RECYCLER.parser(in) : NATIVE_RECYCLER.parser(in); } @Override public void recycle(JsonGenerator recycler) { if (recycler instanceof VThreadJGenerator) { VIRTUAL_RECYCLER.recycle(recycler); } } @Override public void recycle(JsonParser recycler) { if (recycler instanceof VThreadJParser) { VIRTUAL_RECYCLER.recycle(recycler); } } static final class StripedLockFreePool implements BufferRecycler { private static final StripedLockFreePool INSTANCE = new StripedLockFreePool(Runtime.getRuntime().availableProcessors()); private static final int CACHE_LINE_SHIFT = 4; private static final int CACHE_LINE_PADDING = 1 << CACHE_LINE_SHIFT; private final XorShiftThreadProbe threadProbe; private final AtomicReferenceArray generatorStacks; private final AtomicReferenceArray parserStacks; private StripedLockFreePool(int stripesCount) { final int size = roundToPowerOfTwo(stripesCount); this.generatorStacks = new AtomicReferenceArray<>(size * CACHE_LINE_PADDING); this.parserStacks = new AtomicReferenceArray<>(size * CACHE_LINE_PADDING); int mask = (size - 1) << CACHE_LINE_SHIFT; this.threadProbe = new XorShiftThreadProbe(mask); } static StripedLockFreePool shared() { return INSTANCE; } @Override public JsonParser parser(byte[] bytes) { return parser().process(bytes, bytes.length); } @Override public JsonParser parser(InputStream in) { return parser().process(in); } @Override public JsonGenerator generator(JsonOutput target) { final int index = threadProbe.index(); var currentHead = generatorStacks.get(index); while (true) { if (currentHead == null) { return new VThreadJGenerator(index).prepare(target); } if (generatorStacks.compareAndSet(index, currentHead, currentHead.next)) { currentHead.next = null; return currentHead.value.prepare(target); } else { currentHead = generatorStacks.get(index); } } } private JsonParser parser() { int index = threadProbe.index(); var currentHead = parserStacks.get(index); while (true) { if (currentHead == null) { return new VThreadJParser(index); } if (parserStacks.compareAndSet(index, currentHead, currentHead.next)) { currentHead.next = null; return currentHead.value; } else { currentHead = parserStacks.get(index); } } } @Override public void recycle(JsonGenerator recycler) { var vThreadBufferRecycler = (VThreadJGenerator) recycler; var newHead = new JNode(vThreadBufferRecycler); var next = generatorStacks.get(vThreadBufferRecycler.slot); while (true) { newHead.level = next == null ? 1 : next.level + 1; if (generatorStacks.compareAndSet(vThreadBufferRecycler.slot, next, newHead)) { newHead.next = next; return; } else { next = generatorStacks.get(vThreadBufferRecycler.slot); } } } @Override public void recycle(JsonParser recycler) { var vThreadBufferRecycler = (VThreadJParser) recycler; var newHead = new PNode(vThreadBufferRecycler); var next = parserStacks.get(vThreadBufferRecycler.slot); while (true) { newHead.level = next == null ? 1 : next.level + 1; if (parserStacks.compareAndSet(vThreadBufferRecycler.slot, next, newHead)) { newHead.next = next; return; } else { next = parserStacks.get(vThreadBufferRecycler.slot); } } } private static final class JNode { final VThreadJGenerator value; JNode next; int level = 0; JNode(VThreadJGenerator value) { this.value = value; } } private static final class PNode { final VThreadJParser value; PNode next; int level = 0; PNode(VThreadJParser value) { this.value = value; } } } private static final class VThreadJGenerator extends JGenerator { private final int slot; private VThreadJGenerator(int slot) { super(Recyclers.GENERATOR_BUFFER_SIZE); this.slot = slot; } } private static final class VThreadJParser extends JParser { private final int slot; private VThreadJParser(int slot) { super( new char[Recyclers.PARSER_CHAR_BUFFER_SIZE], new byte[Recyclers.PARSER_BUFFER_SIZE], 0, JParser.ErrorInfo.MINIMAL, JParser.DoublePrecision.DEFAULT, JParser.UnknownNumberParsing.BIGDECIMAL, 100, 50_000); this.slot = slot; } } private static final class VirtualPredicate { private static final MethodHandle virtualMh = findVirtualMH(); private static MethodHandle findVirtualMH() { try { return MethodHandles.publicLookup() .findVirtual(Thread.class, "isVirtual", MethodType.methodType(boolean.class)); } catch (Exception e) { return null; } } private static Predicate findIsVirtualPredicate() { return virtualMh == null ? VirtualPredicate::notVirtual : VirtualPredicate::isVirtual; } private static boolean isVirtual(Thread thread) { try { return (boolean) virtualMh.invokeExact(thread); } catch (Throwable e) { throw new RuntimeException(e); } } private static boolean notVirtual(Thread thread) { return false; } } private static final class XorShiftThreadProbe { private final int mask; XorShiftThreadProbe(int mask) { this.mask = mask; } public int index() { return probe() & mask; } private int probe() { // Multiplicative Fibonacci hashing implementation // 0x9e3779b9 is the integral part of the Golden Ratio's fractional part 0.61803398875… // (sqrt(5)-1)/2 // multiplied by 2^32, which has the best possible scattering properties. int probe = (int) ((Thread.currentThread().getId() * 0x9e3779b9) & Integer.MAX_VALUE); // xorshift probe ^= probe << 13; probe ^= probe >>> 17; probe ^= probe << 5; return probe; } } private static final int MAX_POW2 = 1 << 30; private static int roundToPowerOfTwo(final int value) { if (value > MAX_POW2) { throw new IllegalArgumentException("Exceeded max power of 2 (2^31), got " + value); } if (value < 0) { throw new IllegalArgumentException("Expecting value >= 0, got " + value); } return 1 << (32 - Integer.numberOfLeadingZeros(value - 1)); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy