co.paralleluniverse.fibers.Fiber Maven / Gradle / Ivy
/*
* Quasar: lightweight threads and actors for the JVM.
* Copyright (c) 2013-2017, Parallel Universe Software Co. All rights reserved.
*
* This program and the accompanying materials are dual-licensed under
* either the terms of the Eclipse Public License v1.0 as published by
* the Eclipse Foundation
*
* or (per the licensee's choosing)
*
* under the terms of the GNU Lesser General Public License version 3.0
* as published by the Free Software Foundation.
*/
package co.paralleluniverse.fibers;
import co.paralleluniverse.common.monitoring.FlightRecorder;
import co.paralleluniverse.common.monitoring.FlightRecorderMessage;
import co.paralleluniverse.common.reflection.ASMUtil;
import co.paralleluniverse.common.util.*;
import co.paralleluniverse.concurrent.util.ThreadAccess;
import co.paralleluniverse.concurrent.util.ThreadUtil;
import co.paralleluniverse.fibers.FiberForkJoinScheduler.FiberForkJoinTask;
import co.paralleluniverse.fibers.instrument.SuspendableHelper;
import co.paralleluniverse.io.serialization.ByteArraySerializer;
import co.paralleluniverse.io.serialization.JDKSerializer;
import co.paralleluniverse.strands.Strand;
import co.paralleluniverse.strands.Stranded;
import co.paralleluniverse.strands.SuspendableCallable;
import co.paralleluniverse.strands.SuspendableRunnable;
import co.paralleluniverse.strands.SuspendableUtils.VoidSuspendableCallable;
import co.paralleluniverse.strands.dataflow.Val;
import java.io.PrintWriter;
import java.io.Serializable;
import java.io.StringWriter;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.util.Arrays;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;
import static co.paralleluniverse.strands.SuspendableUtils.runnableToCallable;
// import java.lang.reflect.Executable;
/**
* A lightweight thread.
*
* There are two ways to create a new fiber: either subclass the {@code Fiber} class and override the {@code run} method,
* or pass the code to be executed in the fiber as the {@code target} parameter to the constructor. All in all, the Fiber API
* resembles the {@link Thread} class in many ways.
*
* A fiber runs inside a ForkJoinPool.
*
* A Fiber can be serialized if it's not running and all involved classes and data types are also {@link Serializable}.
*
* A new Fiber occupies under 400 bytes of memory (when using the default stack size, and compressed OOPs are turned on, as they are by default).
*
* @param The type of the fiber's result value. Should be set to {@link Void} if no value is to be returned by the fiber.
*
* @author pron
*/
public class Fiber extends Strand implements Joinable, Serializable, Future {
static final boolean USE_VAL_FOR_RESULT = true;
private static final Object RESET = new Object();
private static final JDKSerializer jdkSerializer = new JDKSerializer();
private static final boolean traceInterrupt = SystemProperties.isEmptyOrTrue("co.paralleluniverse.fibers.traceInterrupt");
private static final boolean disableAgentWarning = true; // SystemProperties.isEmptyOrTrue("co.paralleluniverse.fibers.disableAgentWarning");
public static final int DEFAULT_STACK_SIZE = 32;
private static final Object SERIALIZER_BLOCKER = new Object();
private static final boolean MAINTAIN_ACCESS_CONTROL_CONTEXT = (System.getSecurityManager() != null);
// private static final boolean PREEMPTION = SystemProperties.isEmptyOrTrue("co.paralleluniverse.fibers.enablePreemption");
// private static final int PREEMPTION_CREDITS = 3000;
private static final long serialVersionUID = 2783452871536981L;
protected static final FlightRecorder flightRecorder = Debug.isDebug() ? Debug.getGlobalFlightRecorder() : null;
private static final boolean verifyInstrumentation = SystemProperties.isEmptyOrTrue("co.paralleluniverse.fibers.verifyInstrumentation");
static {
if (Debug.isDebug())
System.err.println("QUASAR WARNING: Debug mode enabled. This may harm performance.");
if (Debug.isAssertionsEnabled())
System.err.println("QUASAR WARNING: Assertions enabled. This may harm performance.");
if (!SuspendableHelper.isJavaAgentActive() && !disableAgentWarning)
System.err.println("QUASAR WARNING: Quasar Java Agent isn't running. If you're using another instrumentation method you can ignore this message; "
+ "otherwise, please refer to the Getting Started section in the Quasar documentation.");
assert printVerifyInstrumentationWarning();
}
private static boolean printVerifyInstrumentationWarning() {
if (verifyInstrumentation)
System.err.println("QUASAR WARNING: Fibers are set to verify instrumentation. This may *severely* harm performance.");
return true;
}
// private static final FiberTimedScheduler timeoutService = new FiberTimedScheduler(new ThreadFactoryBuilder().setNameFormat("fiber-timeout-%d").setDaemon(true).build());
private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler = new UncaughtExceptionHandler() {
@Override
public void uncaughtException(Strand s, Throwable e) {
System.err.print("Exception in Fiber \"" + s.getName() + "\" ");
if (e instanceof NullPointerException || e instanceof ClassCastException
|| Exceptions.unwrap(e) instanceof NullPointerException || Exceptions.unwrap(e) instanceof ClassCastException)
System.err.println("If this exception looks strange, perhaps you've forgotten to instrument a blocking method. Run your program with -Dco.paralleluniverse.fibers.verifyInstrumentation to catch the culprit!");
System.err.println(e);
Strand.printStackTrace(threadToFiberStack(e.getStackTrace()), System.err);
checkInstrumentation(ExtendedStackTrace.of(e), true);
}
};
private static final AtomicLong idGen = new AtomicLong(10000000L);
private static long nextFiberId() {
return idGen.incrementAndGet();
}
//
private /*final*/ transient FiberScheduler scheduler;
private /*final*/ transient FiberTask task;
private String name;
private /*final*/ int initialStackSize;
private /*final*/ transient long fid;
final Stack stack;
private volatile State state;
private InterruptedException interruptStack;
private volatile boolean interrupted;
private long run;
private transient boolean noPreempt;
// private int preemptionCredits;
private transient Thread runningThread;
private final SuspendableCallable target;
private byte priority;
private boolean noLocals = false;
private transient ClassLoader contextClassLoader;
private transient AccessControlContext inheritedAccessControlContext;
// These are typed as Object because they store JRE-internal ThreadLocalMap objects, which is a package private
// class. Also, they're swapped for Object[] during serialisation, as ThreadLocalMap is not a serialisable type.
private Object fiberLocals;
private Object inheritableFiberLocals;
private long sleepStart;
private transient Future timeoutTask;
private transient ParkAction prePark;
private transient ParkAction postPark;
//private boolean inExec;
private transient Object result; // transient b/c completed fibers are not serialized
private transient boolean getStackTrace;
private volatile UncaughtExceptionHandler uncaughtExceptionHandler;
/*final*/ transient DummyRunnable fiberRef = new DummyRunnable(this);
/**
* Creates a new fiber from the given {@link SuspendableCallable}.
*
* @param name The name of the fiber (may be {@code null})
* @param scheduler The {@link FiberScheduler} that will schedule the fiber.
* @param stackSize the initial size of the data stack.
* @param target the {@link SuspendableCallable} for the fiber. May {@code null} if the {@link #run() run} method is overriden.
*/
@SuppressWarnings("LeakingThisInConstructor")
public Fiber(String name, FiberScheduler scheduler, int stackSize, SuspendableCallable target) {
this.state = State.NEW;
this.fid = nextFiberId();
this.scheduler = scheduler;
setName(name);
Strand parent = Strand.currentStrand(); // retaining the parent as a field is a huge, complex memory leak
this.target = target;
this.task = scheduler != null ? scheduler.newFiberTask(this) : new FiberForkJoinTask(this);
this.initialStackSize = stackSize;
this.stack = new Stack(this, stackSize > 0 ? stackSize : DEFAULT_STACK_SIZE);
this.priority = (byte)NORM_PRIORITY;
if (Debug.isDebug())
record(1, "Fiber", "", "Creating fiber name: %s, scheduler: %s, parent: %s, target: %s, task: %s, stackSize: %s", name, scheduler, parent, target, task, stackSize);
if (target != null) {
if (verifyInstrumentation)
verifyInstrumentedTarget(target);
if (target instanceof Stranded)
((Stranded) target).setStrand(this);
} else if (!isInstrumented(this.getClass())) {
throw new IllegalArgumentException("Fiber class " + this.getClass().getName() + " has not been instrumented.");
}
final Thread currentThread = Thread.currentThread();
Object inheritableThreadLocals = ThreadAccess.getInheritableThreadLocals(currentThread);
if (inheritableThreadLocals != null)
this.inheritableFiberLocals = ThreadAccess.createInheritedMap(inheritableThreadLocals);
this.contextClassLoader = ThreadAccess.getContextClassLoader(currentThread);
if (MAINTAIN_ACCESS_CONTROL_CONTEXT)
this.inheritedAccessControlContext = AccessController.getContext();
if (USE_VAL_FOR_RESULT /*&& !isVoidResult(target)*/)
this.result = new Val();
record(1, "Fiber", "", "Created fiber %s", this);
}
// private Fiber() {
// this.fid = nextFiberId();
// this.scheduler = scheduler;
// Strand parent = Strand.currentStrand();
// // this.parent = Strand.currentStrand();
// this.target = target;
// this.task = scheduler != null ? scheduler.newFiberTask(this) : new FiberForkJoinScheduler.FiberForkJoinTask(this);
// final Thread currentThread = Thread.currentThread();
// this.contextClassLoader = ThreadAccess.getContextClassLoader(currentThread);
// if (MAINTAIN_ACCESS_CONTROL_CONTEXT)
// this.inheritedAccessControlContext = AccessController.getContext();
// if (USE_VAL_FOR_RESULT /*&& !isVoidResult(target)*/)
// this.result = new Val();
// }
/**
* Creates a new fiber from the given {@link SuspendableCallable} scheduled in the {@link DefaultFiberScheduler default fiber scheduler}
*
* @param name The name of the fiber (may be {@code null})
* @param stackSize the initial size of the data stack.
* @param target the {@link SuspendableCallable} for the fiber. May {@code null} if the {@link #run() run} method is overriden.
*/
public Fiber(String name, int stackSize, SuspendableCallable target) {
this(name, defaultScheduler(), stackSize, target);
}
private static FiberScheduler defaultScheduler() {
final Fiber parent = currentFiber();
if (parent == null)
return DefaultFiberScheduler.getInstance();
else
return parent.getScheduler();
}
private static Fiber verifyParent() {
final Fiber parent = currentFiber();
if (parent == null)
throw new IllegalStateException("This constructor may only be used from within a Fiber");
return parent;
}
private static void verifyInstrumentedTarget(SuspendableCallable> target) {
Object t = target;
if (target instanceof VoidSuspendableCallable)
t = ((VoidSuspendableCallable) target).getRunnable();
if (t.getClass().getName().contains("$$Lambda$"))
return;
if (verifyInstrumentation && !isInstrumented(t.getClass()))
throw new VerifyInstrumentationException("Target class " + t.getClass() + " has not been instrumented.");
}
private Future future() {
return USE_VAL_FOR_RESULT ? (Val) result : task;
}
public final SuspendableCallable getTarget() {
return target;
}
@Override
public final int hashCode() {
return System.identityHashCode(this);
}
@Override
public final boolean equals(Object obj) {
return this == obj;
}
@Override
public final String getName() {
if (name == null) // benign race
this.name = "fiber-" + ((scheduler != null && scheduler != DefaultFiberScheduler.getInstance()) ? (scheduler.getName() + '-') : "") + fid;
return name;
}
@Override
public final Fiber setName(String name) {
if (state != State.NEW)
throw new IllegalStateException("Fiber name cannot be changed once it has started");
this.name = name;
return this;
}
/**
* Sets the priority of this fiber.
*
*
* The fiber priority's semantics - or even if it is ignored completely -
* is entirely up to the fiber's scheduler.
* The default fiber scheduler completely ignores fiber priority.
*
* @param newPriority priority to set this fiber to
*
* @exception IllegalArgumentException If the priority is not in the
* range {@code MIN_PRIORITY} to {@code MAX_PRIORITY}
* @see #getPriority
* @see #MAX_PRIORITY
* @see #MIN_PRIORITY
*/
@Override
public Fiber setPriority(int newPriority) {
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY)
throw new IllegalArgumentException();
this.priority = (byte) newPriority;
return this;
}
/**
* Returns this fiber's priority.
* @see #setPriority
*/
@Override
public int getPriority() {
return priority;
}
@Override
public long getId() {
return fid;
}
public Object getTask() {
return task;
}
public FiberScheduler getScheduler() {
return scheduler;
}
long getRun() {
return run;
}
@Deprecated
public Fiber setNoLocals(boolean value) {
this.noLocals = value;
if (value) {
this.fiberLocals = null;
this.inheritableFiberLocals = null;
this.inheritedAccessControlContext = null;
this.contextClassLoader = null;
}
return this;
}
@Deprecated
public boolean getNoLocals() {
return noLocals;
}
//
/////////// Constructors ///////////////////////////////////
/**
* Creates a new Fiber from the given {@link SuspendableCallable}.
* The new fiber uses the default initial stack size.
*
* @param name The name of the fiber (may be null)
* @param scheduler The scheduler pool in which the fiber should run.
* @param target the SuspendableCallable for the Fiber.
* @throws NullPointerException when proto is null
* @throws IllegalArgumentException when stackSize is <= 0
*/
public Fiber(String name, FiberScheduler scheduler, SuspendableCallable target) {
this(name, scheduler, -1, target);
}
/**
* Creates a new Fiber from the given {@link SuspendableCallable}.
* The new fiber has no name, and uses the default initial stack size.
*
* @param scheduler The scheduler pool in which the fiber should run.
* @param target the SuspendableRunnable for the Fiber.
* @throws NullPointerException when proto is null
* @throws IllegalArgumentException when stackSize is <= 0
*/
public Fiber(FiberScheduler scheduler, SuspendableCallable target) {
this(null, scheduler, -1, target);
}
/**
* Creates a new Fiber from the given {@link SuspendableRunnable}.
*
* @param name The name of the fiber (may be null)
* @param scheduler The scheduler pool in which the fiber should run.
* @param stackSize the initial size of the data stack.
* @param target the SuspendableRunnable for the Fiber.
* @throws NullPointerException when proto is null
* @throws IllegalArgumentException when stackSize is <= 0
*/
public Fiber(String name, FiberScheduler scheduler, int stackSize, SuspendableRunnable target) {
this(name, scheduler, stackSize, (SuspendableCallable) runnableToCallable(target));
}
/**
* Creates a new Fiber from the given {@link SuspendableRunnable}.
* The new fiber uses the default initial stack size.
*
* @param name The name of the fiber (may be null)
* @param scheduler The scheduler pool in which the fiber should run.
* @param target the SuspendableRunnable for the Fiber.
* @throws NullPointerException when proto is null
* @throws IllegalArgumentException when stackSize is <= 0
*/
public Fiber(String name, FiberScheduler scheduler, SuspendableRunnable target) {
this(name, scheduler, -1, target);
}
/**
* Creates a new Fiber from the given SuspendableRunnable.
* The new fiber has no name, and uses the default initial stack size.
*
* @param scheduler The scheduler pool in which the fiber should run.
* @param target the SuspendableRunnable for the Fiber.
* @throws NullPointerException when proto is null
* @throws IllegalArgumentException when stackSize is <= 0
*/
public Fiber(FiberScheduler scheduler, SuspendableRunnable target) {
this(null, scheduler, -1, target);
}
/**
* Creates a new Fiber subclassing the Fiber class and overriding the {@link #run() run} method.
*
* @param name The name of the fiber (may be null)
* @param scheduler The scheduler pool in which the fiber should run.
* @param stackSize the initial size of the data stack.
* @throws NullPointerException when proto is null
* @throws IllegalArgumentException when stackSize is <= 0
*/
public Fiber(String name, FiberScheduler scheduler, int stackSize) {
this(name, scheduler, stackSize, (SuspendableCallable) null);
}
/**
* Creates a new Fiber subclassing the Fiber class and overriding the {@link #run() run} method.
* The new fiber uses the default initial stack size.
*
* @param name The name of the fiber (may be null)
* @param scheduler The scheduler pool in which the fiber should run.
* @throws NullPointerException when proto is null
* @throws IllegalArgumentException when stackSize is <= 0
*/
public Fiber(String name, FiberScheduler scheduler) {
this(name, scheduler, -1, (SuspendableCallable) null);
}
/**
* Creates a new Fiber subclassing the Fiber class and overriding the {@link #run() run} method.
* The new fiber has no name, and uses the default initial stack size.
*
* @param scheduler The scheduler pool in which the fiber should run.
* @throws NullPointerException when proto is null
* @throws IllegalArgumentException when stackSize is <= 0
*/
public Fiber(FiberScheduler scheduler) {
this(null, scheduler, -1, (SuspendableCallable) null);
}
/**
* Creates a new child Fiber from the given {@link SuspendableCallable}.
* This constructor may only be called from within another fiber. This fiber will use the same fork/join pool as the creating fiber.
* The new fiber uses the default initial stack size.
*
* @param name The name of the fiber (may be null)
* @param target the SuspendableRunnable for the Fiber.
* @throws IllegalArgumentException when stackSize is <= 0
*/
public Fiber(String name, SuspendableCallable target) {
this(name, -1, target);
}
/**
* Creates a new child Fiber from the given {@link SuspendableCallable}.
* This constructor may only be called from within another fiber. This fiber will use the same fork/join pool as the creating fiber.
* The new fiber has no name, and uses the default initial stack size.
*
* @param target the SuspendableRunnable for the Fiber.
* @throws IllegalArgumentException when stackSize is <= 0
*/
public Fiber(SuspendableCallable target) {
this(null, -1, target);
}
/**
* Creates a new child Fiber from the given {@link SuspendableRunnable}.
* This constructor may only be called from within another fiber. This fiber will use the same fork/join pool as the creating fiber.
*
* @param name The name of the fiber (may be null)
* @param stackSize the initial size of the data stack.
* @param target the SuspendableRunnable for the Fiber.
* @throws NullPointerException when proto is null
* @throws IllegalArgumentException when stackSize is <= 0
*/
public Fiber(String name, int stackSize, SuspendableRunnable target) {
this(name, stackSize, (SuspendableCallable) runnableToCallable(target));
}
/**
* Creates a new child Fiber from the given {@link SuspendableRunnable}.
* This constructor may only be called from within another fiber. This fiber will use the same fork/join pool as the creating fiber.
* The new fiber uses the default initial stack size.
*
* @param name The name of the fiber (may be null)
* @param target the SuspendableRunnable for the Fiber.
* @throws NullPointerException when proto is null
* @throws IllegalArgumentException when stackSize is <= 0
*/
public Fiber(String name, SuspendableRunnable target) {
this(name, -1, target);
}
/**
* Creates a new child Fiber from the given {@link SuspendableRunnable}.
* This constructor may only be called from within another fiber. This fiber will use the same fork/join pool as the creating fiber.
* The new fiber has no name, and uses the default initial stack size.
*
* @param target the SuspendableRunnable for the Fiber.
* @throws NullPointerException when proto is null
* @throws IllegalArgumentException when stackSize is <= 0
*/
public Fiber(SuspendableRunnable target) {
this(null, -1, target);
}
/**
* Creates a new child Fiber subclassing the Fiber class and overriding the {@link #run() run} method.
* This constructor may only be called from within another fiber. This fiber will use the same fork/join pool as the creating fiber.
*
* @param name The name of the fiber (may be null)
* @param stackSize the initial size of the data stack.
* @throws NullPointerException when proto is null
* @throws IllegalArgumentException when stackSize is <= 0
*/
public Fiber(String name, int stackSize) {
this(name, stackSize, (SuspendableCallable) null);
}
/**
* Creates a new child Fiber subclassing the Fiber class and overriding the {@link #run() run} method.
* This constructor may only be called from within another fiber. This fiber will use the same fork/join pool as the creating fiber.
* The new fiber uses the default initial stack size.
*
* @param name The name of the fiber (may be null)
* @throws NullPointerException when proto is null
* @throws IllegalArgumentException when stackSize is <= 0
*/
public Fiber(String name) {
this(name, -1, (SuspendableCallable) null);
}
/**
* Creates a new child Fiber subclassing the Fiber class and overriding the {@link #run() run} method.
* This constructor may only be called from within another fiber. This fiber will use the same fork/join pool as the creating fiber.
* The new fiber has no name, and uses the default initial stack size.
*
* @throws NullPointerException when proto is null
* @throws IllegalArgumentException when stackSize is <= 0
*/
public Fiber() {
this(null, -1, (SuspendableCallable) null);
}
public Fiber(Fiber fiber, SuspendableCallable target) {
this(fiber.name, fiber.scheduler, fiber.initialStackSize, target);
}
public Fiber(Fiber fiber, SuspendableRunnable target) {
this(fiber.name, fiber.scheduler, fiber.initialStackSize, target);
}
public Fiber(Fiber fiber, FiberScheduler scheduler, SuspendableCallable target) {
this(fiber.name, scheduler, fiber.initialStackSize, target);
}
public Fiber(Fiber fiber, FiberScheduler scheduler, SuspendableRunnable target) {
this(fiber.name, scheduler, fiber.initialStackSize, target);
}
//
/**
* Returns the active Fiber on this thread or NULL if no Fiber is running.
*
* @return the active Fiber on this thread or NULL if no Fiber is running.
*/
public static Fiber currentFiber() {
return getCurrentFiber();
}
/**
* Tests whether current code is executing in a fiber.
* This method might be faster than {@code Fiber.currentFiber() != null}.
*
* @return {@code true} if called in a fiber; {@code false} otherwise.
*/
public static boolean isCurrentFiber() {
return FiberForkJoinScheduler.isFiberThread(Thread.currentThread()) || getCurrentFiber() != null;
}
public static long getCurrentRun() {
Fiber f = currentFiber();
if (f == null)
throw new IllegalStateException("Not in fiber");
return f.getRun();
}
@Override
public final boolean isFiber() {
return true;
}
@Override
public final Object getUnderlying() {
return this;
}
/**
* Suspends (deschedules) the currently running Fiber unless the
* permit is available.
*
* This method must be called directly on the fiber class, even when subclassing Fiber, i.e., like this {@code Fiber.park}.
*
* Returns {@code true} iff we've been suspended and then resumed.
*
* @throws SuspendExecution This exception is used for control transfer and must never be caught.
* @throws IllegalStateException If not called from a Fiber
*/
static boolean park(Object blocker, ParkAction postParkActions, long timeout, TimeUnit unit) throws SuspendExecution {
return verifySuspend().park1(blocker, postParkActions, timeout, unit);
}
static boolean park(Object blocker, ParkAction postParkActions) throws SuspendExecution {
return park(blocker, postParkActions, 0, null);
}
public static boolean park(Object blocker, long timeout, TimeUnit unit) throws SuspendExecution {
return park(blocker, null, timeout, unit);
}
public static void park(Object blocker) throws SuspendExecution {
park(blocker, null, 0, null);
}
public static void park(long timeout, TimeUnit unit) throws SuspendExecution {
park(null, null, timeout, unit);
}
public static void park() throws SuspendExecution {
park(null, null, 0, null);
}
public static void yield() throws SuspendExecution {
verifySuspend().yield1();
}
public static void parkAndUnpark(Fiber other) throws SuspendExecution {
parkAndUnpark(other, null);
}
public static void parkAndUnpark(Fiber other, Object blocker) throws SuspendExecution {
verifySuspend().parkAndUnpark1(other, blocker, 0, TimeUnit.NANOSECONDS);
}
public static void yieldAndUnpark(Fiber other, Object blocker) throws SuspendExecution {
verifySuspend().yieldAndUnpark1(other, blocker, 0, TimeUnit.NANOSECONDS);
}
public static void yieldAndUnpark(Fiber other) throws SuspendExecution {
yieldAndUnpark(other, null);
}
public static void sleep(long millis) throws InterruptedException, SuspendExecution {
sleep(millis, TimeUnit.MILLISECONDS);
}
public static void sleep(long millis, int nanos) throws InterruptedException, SuspendExecution {
sleep(TimeUnit.MILLISECONDS.toNanos(millis) + nanos, TimeUnit.NANOSECONDS);
}
public static void sleep(long duration, TimeUnit unit) throws InterruptedException, SuspendExecution {
verifySuspend().sleep1(duration, unit);
}
public static boolean interrupted() {
final Fiber current = currentFiber();
if (current == null)
throw new IllegalStateException("Not called on a fiber");
final boolean interrupted = current.isInterrupted();
if (interrupted)
current.interrupted = false;
return interrupted;
}
/**
* Returns {@code true} iff we've been suspended and then resumed.
* (The return value in the Java code is actually ignored. It is generated and injected in InstrumentMethod.accept())
*
*
* @param blocker
* @param postParkAction
* @param timeout
* @param unit
* @return {@code true} if this fiber actually parked; {@code false} otherwise (if the lease was already available).
* @throws SuspendExecution
*/
private boolean park1(Object blocker, ParkAction postParkAction, long timeout, TimeUnit unit) throws SuspendExecution {
record(1, "Fiber", "park", "Parking %s blocker: %s", this, blocker);
if (isRecordingLevel(2) && !getStackTrace)
record(2, "Fiber", "park", "Parking %s at %s", this, Arrays.toString(getStackTrace()));
if (prePark != null)
prePark.run(this);
this.postPark = postParkAction;
if (timeout > 0 && unit != null)
this.timeoutTask = scheduler.schedule(this, blocker, timeout, unit);
return task.park(blocker, postParkAction != null); // postParkActions != null iff parking for FiberAsync
}
private void yield1() throws SuspendExecution {
if (isRecordingLevel(2))
record(2, "Fiber", "yield", "Yielding %s at %s", this, Arrays.toString(getStackTrace()));
if (prePark != null)
prePark.run(this);
task.yield();
}
private void parkAndUnpark1(Fiber other, Object blocker, long timeout, TimeUnit unit) throws SuspendExecution {
record(1, "Fiber", "parkAndUnpark", "Parking %s and unparking %s blocker: %s", this, other, blocker);
if (!other.exec(blocker, timeout, unit))
other.unpark(blocker);
park1(blocker, null, -1, null);
}
private void yieldAndUnpark1(Fiber other, Object blocker, long timeout, TimeUnit unit) throws SuspendExecution {
record(1, "Fiber", "yieldAndUnpark", "Yielding %s and unparking %s blocker: %s", this, other, blocker);
if (!other.exec(blocker, timeout, unit)) {
other.unpark(blocker);
yield1();
}
}
void preempt() throws SuspendExecution {
if (isRecordingLevel(2))
record(2, "Fiber", "preempt", "Preempting %s at %s", this, Arrays.toString(getStackTrace()));
task.yield();
}
boolean exec() {
if (result != RESET && future().isDone()) // RESET only in overhead benchamrk
return true;
if (state == State.RUNNING)
throw new IllegalStateException("Not new or suspended");
cancelTimeoutTask();
final FibersMonitor monitor = getMonitor();
if (Debug.isDebug())
record(1, "Fiber", "exec", "running %s %s %s", state, this, run);
// if (monitor != null && state == State.STARTED)
// monitor.fiberStarted(this); - done elsewhere
final Thread currentThread = Thread.currentThread();
final Object old = getCurrentTarget(currentThread);// getCurrentFiber(); // a fiber can directly call exec on another fiber, e.g.: Channel.sendSync
installFiberDataInThread(currentThread);
// as of now we're no longer running in the enclosing thread, but in the fiber itself.
run++;
// preemptionCredits = PREEMPTION_CREDITS;
runningThread = currentThread;
state = State.RUNNING; // TODO: ??? orderedSetState(State.RUNNING);
boolean restored = false;
try {
try {
final V res = run1(); // we jump into the continuation
runningThread = null;
state = State.TERMINATED;
record(1, "Fiber", "exec", "finished %s %s res: %s", state, this, this.result);
monitorFiberTerminated(monitor);
onCompletion();
setResult(res);
return true;
} catch (RuntimeSuspendExecution e) {
throw (SuspendExecution) e.getCause();
}
} catch (SuspendExecution ex) {
assert ex == SuspendExecution.PARK || ex == SuspendExecution.YIELD;
//stack.dump();
stack.resumeStack();
runningThread = null;
orderedSetState(timeoutTask != null ? State.TIMED_WAITING : State.WAITING);
final ParkAction ppa = postPark;
clearRunSettings();
restoreThreadData(currentThread, old);
// as of now we're no longer running in the fiber (but in the enclosing thread)
restored = true;
record(1, "Fiber", "exec", "parked %s %s", state, this);
task.doPark(ex == SuspendExecution.YIELD); // now we can complete parking
assert ppa == null || ex == SuspendExecution.PARK; // can't have postParkActions on yield
if (ppa != null) {
// Redirect any exceptions thrown by the user code provided in the post-park action, as exceptions
// which propagate out of here may not always be caught and printed.
try {
ppa.run(this);
} catch (Throwable t) {
onException(t);
throw Exceptions.rethrow(t);
}
}
// if (monitor != null)
// monitor.fiberSuspended();
return false;
} catch (Throwable t) {
clearRunSettings();
runningThread = null;
if (Debug.isDebug()) {
if (t instanceof InterruptedException)
record(1, "Fiber", "exec", "InterruptedException: %s, %s", state, this);
else {
StringWriter sw = new StringWriter();
t.printStackTrace(new PrintWriter(sw));
record(1, "Fiber", "exec", "Exception in %s %s: %s %s", state, this, t, sw.toString());
}
}
try {
if (t instanceof InterruptedException) {
throw new RuntimeException(t);
} else {
onException(t);
throw Exceptions.rethrow(t);
}
} finally {
state = State.TERMINATED;
task.setState(0); // Some error conditions -- when the fiber isn't instrumented well -- may leave it in an inconsistent state (PARKING)
monitorFiberTerminated(monitor);
setException(t);
}
} finally {
if (!restored)
restoreThreadData(currentThread, old);
if (scheduler instanceof FiberForkJoinScheduler)
((FiberForkJoinScheduler) scheduler).tryOnIdle();
}
}
void setResult(V res) {
if (result == RESET) // only in overhead benchmark
return;
try {
if (USE_VAL_FOR_RESULT)
((Val) this.result).set(res);
else
this.result = res;
} catch (IllegalStateException e) {
}
}
private void setException(Throwable t) {
try {
if (USE_VAL_FOR_RESULT)
((Val) this.result).setException(t);
} catch (IllegalStateException e) {
}
}
private void clearRunSettings() {
this.prePark = null;
this.postPark = null;
//this.inExec = false;
this.noPreempt = false;
}
private StackTraceElement[] execStackTrace1() {
if (future().isDone())
return null;
if (state == State.RUNNING)
throw new IllegalStateException("Not new or suspended");
this.getStackTrace = true;
final Thread currentThread = Thread.currentThread();
final Object old = getCurrentTarget(currentThread);
setCurrentFiber(this, currentThread);
try {
try {
run1(); // we jump into the continuation
throw new AssertionError();
} catch (RuntimeSuspendExecution e) {
throw (SuspendExecution) e.getCause();
}
} catch (SuspendExecution | IllegalStateException ex) {
assert ex != SuspendExecution.PARK && ex != SuspendExecution.YIELD;
//stack.dump();
stack.resumeStack();
setCurrentTarget(old, currentThread);
this.noPreempt = false;
this.getStackTrace = false;
task.doPark(false); // now we can complete parking
StackTraceElement[] st = ex.getStackTrace();
if (ex instanceof IllegalStateException) { // special case for sleep (not recognized as a yield method by instrumentation
int index = -1;
for (int i = 0; i < st.length; i++) {
if (Fiber.class.getName().equals(st[i].getClassName()) && "sleep".equals(st[i].getMethodName())) {
index = i;
break;
}
}
assert index >= 0;
st = skipStackTraceElements(st, index);
} else
st = skipStackTraceElements(st, 2); // skip Fiber.onResume and Stack.postRestore
return st;
} catch (Throwable ex) {
throw new AssertionError(ex);
}
}
public FibersMonitor getMonitor() {
if (scheduler == null)
return null;
return scheduler.getMonitor();
}
private void monitorFiberTerminated(FibersMonitor monitor) {
if (monitor != null)
monitor.fiberTerminated(this);
}
private void cancelTimeoutTask() {
if (timeoutTask != null) {
timeoutTask.cancel(false);
timeoutTask = null;
}
}
private void installFiberDataInThread(Thread currentThread) {
record(1, "Fiber", "installFiberDataInThread", "%s <-> %s", this, currentThread);
if (!noLocals) {
installFiberLocals(currentThread);
installFiberContextClassLoader(currentThread);
if (MAINTAIN_ACCESS_CONTROL_CONTEXT)
installFiberInheritedAccessControlContext(currentThread);
}
setCurrentFiber(this, currentThread);
}
private void restoreThreadData(Thread currentThread, Object old) {
record(1, "Fiber", "restoreThreadData", "%s <-> %s", this, currentThread);
if (!noLocals) {
restoreThreadLocals(currentThread);
restoreThreadContextClassLoader(currentThread);
if (MAINTAIN_ACCESS_CONTROL_CONTEXT)
restoreThreadInheritedAccessControlContext(currentThread);
}
setCurrentTarget(old, currentThread);
}
/**
* Also called by {@link TrueThreadLocal}.
*
* @param currentThread
*/
void installFiberLocals(Thread currentThread) {
switchFiberAndThreadLocals(currentThread, true);
}
/**
* Also called by {@link TrueThreadLocal}.
*
* @param currentThread
*/
void restoreThreadLocals(Thread currentThread) {
switchFiberAndThreadLocals(currentThread, false);
}
private void switchFiberAndThreadLocals(Thread currentThread, boolean install) {
if (noLocals || scheduler == null) // in tests
return;
Object tmpThreadLocals = ThreadAccess.getThreadLocals(currentThread);
Object tmpInheritableThreadLocals = ThreadAccess.getInheritableThreadLocals(currentThread);
if (isRecordingLevel(2)) {
record(2, "Fiber", "switchFiberAndThreadLocals", "fiberLocals: %s", ThreadUtil.getThreadLocalsString(install ? this.fiberLocals : tmpThreadLocals));
record(2, "Fiber", "switchFiberAndThreadLocals", "inheritableFiberLocals: %s", ThreadUtil.getThreadLocalsString(install ? this.inheritableFiberLocals : tmpInheritableThreadLocals));
}
ThreadAccess.setThreadLocals(currentThread, this.fiberLocals);
ThreadAccess.setInheritableThreadLocals(currentThread, this.inheritableFiberLocals);
this.fiberLocals = tmpThreadLocals;
this.inheritableFiberLocals = tmpInheritableThreadLocals;
}
private void installFiberContextClassLoader(Thread currentThread) {
final ClassLoader origContextClassLoader = ThreadAccess.getContextClassLoader(currentThread);
ThreadAccess.setContextClassLoader(currentThread, contextClassLoader);
this.contextClassLoader = origContextClassLoader;
}
private void restoreThreadContextClassLoader(Thread currentThread) {
final ClassLoader origContextClassLoader = contextClassLoader;
this.contextClassLoader = ThreadAccess.getContextClassLoader(currentThread);
ThreadAccess.setContextClassLoader(currentThread, origContextClassLoader);
}
private void installFiberInheritedAccessControlContext(Thread currentThread) {
final AccessControlContext origAcc = ThreadAccess.getInheritedAccessControlContext(currentThread);
ThreadAccess.setInheritedAccessControlContext(currentThread, inheritedAccessControlContext);
this.inheritedAccessControlContext = origAcc;
}
private void restoreThreadInheritedAccessControlContext(Thread currentThread) {
final AccessControlContext origAcc = inheritedAccessControlContext;
this.inheritedAccessControlContext = ThreadAccess.getInheritedAccessControlContext(currentThread);
ThreadAccess.setInheritedAccessControlContext(currentThread, origAcc);
}
private void setCurrentFiber(Fiber fiber, Thread currentThread) {
if (scheduler != null)
scheduler.setCurrentFiber(fiber, currentThread);
else
currentStrand.set(fiber);
}
private void setCurrentTarget(Object target, Thread currentThread) {
if (scheduler != null) // in tests
scheduler.setCurrentTarget(target, currentThread);
else
currentStrand.set(null);
}
private Object getCurrentTarget(Thread currentThread) {
if (scheduler == null) // in tests
return null;
return scheduler.getCurrentTarget(currentThread);
}
private static Fiber getCurrentFiber() {
final Thread currentThread = Thread.currentThread();
if (FiberForkJoinScheduler.isFiberThread(currentThread))
return FiberForkJoinScheduler.getTargetFiber(currentThread);
else {
final Strand s = currentStrand.get();
return s instanceof Fiber ? (Fiber) s : null;
}
}
static final class DummyRunnable implements Runnable {
final Fiber fiber;
public DummyRunnable(Fiber fiber) {
this.fiber = fiber;
}
@Override
public void run() {
throw new RuntimeException("This method shouldn't be run. This object is a placeholder.");
}
}
private V run1() throws SuspendExecution, InterruptedException {
return run(); // this method is always on the stack trace. used for verify instrumentation
}
protected V run() throws SuspendExecution, InterruptedException {
if (target != null)
return target.run();
return null;
}
/**
* Causes the current strand's {@link ThreadLocal thread-locals} to be inherited by this fiber. By default only {@link InheritableThreadLocal}s
* are inherited.
* This method must be called before the fiber is started (i.e. before the {@link #start() start} method is called.
* Otherwise, an {@link IllegalStateException} is thrown.
*
* @return {@code this}
*/
public Fiber inheritThreadLocals() {
if (state != State.NEW)
throw new IllegalStateException("Method called on a started fiber");
if (!noLocals)
this.fiberLocals = ThreadAccess.cloneThreadLocalMap(ThreadAccess.getThreadLocals(Thread.currentThread()));
return this;
}
/**
*
* @return {@code this}
*/
@Override
public final Fiber start() {
if (!casState(State.NEW, State.STARTED)) {
if (state == State.TERMINATED && future().isCancelled())
return this;
throw new IllegalThreadStateException("Fiber has already been started or has died");
}
getMonitor().fiberStarted(this);
task.submit();
return this;
}
protected void onParked() {
}
protected void onResume() throws SuspendExecution, InterruptedException {
if (getStackTrace) {
try {
park1(null, null, 0, null);
} catch (SuspendExecution e) {
}
SuspendExecution ex = new SuspendExecution();
ex.setStackTrace(new Throwable().getStackTrace());
throw ex;
}
record(1, "Fiber", "onResume", "Resuming %s", this);
if (isRecordingLevel(2))
record(2, "Fiber", "onResume", "Resuming %s at: %s", this, Arrays.toString(getStackTrace()));
}
final void preemptionPoint(int type) throws SuspendExecution {
if (noPreempt)
return;
if (shouldPreempt(type))
preempt();
}
protected boolean shouldPreempt(int type) {
// 0 - backbranch
// 1 - call
// if (PREEMPTION) {
// assert type == 1;
// preemptionCredits -= 3;
// if (preemptionCredits < 0) {
// final long now = System.nanoTime();
// if (runStart == 0)
// runStart = now;
// else if (TimeUnit.NANOSECONDS.toMicros(now - runStart) > TIME_SLICE_MICRO)
// return true;
// preemptionCredits = 1000;
// }
// }
return false;
}
protected void onCompletion() {
}
protected void onException(Throwable t) {
try {
UncaughtExceptionHandler ueh;
if ((ueh = uncaughtExceptionHandler) != null)
ueh.uncaughtException(this, t);
else if ((ueh = defaultUncaughtExceptionHandler) != null)
ueh.uncaughtException(this, t);
} catch (Exception e) {
if (e != t && t != null)
t.addSuppressed(e);
}
throw Exceptions.rethrow(t);
}
@Override
public final void interrupt() {
if (traceInterrupt)
interruptStack = new InterruptedException();
interrupted = true;
unpark(FiberTask.EMERGENCY_UNBLOCKER);
}
@Override
public final boolean isInterrupted() {
return interrupted;
}
@Override
public final InterruptedException getInterruptStack() {
if (!traceInterrupt)
return null;
return interruptStack;
}
@Override
public final boolean isAlive() {
return state != State.NEW && !future().isDone();
}
@Override
public final State getState() {
return state;
}
@Override
public final boolean isTerminated() {
return state == State.TERMINATED;
}
@Override
public final Object getBlocker() {
return task.getBlocker();
}
// public final Strand getParent() {
// return parent;
// }
final boolean exec(Object blocker, long timeout, TimeUnit unit) {
if (!scheduler.isCurrentThreadInScheduler())
return false;
record(1, "Fiber", "exec", "Blocker %s attempting to immediately execute %s", blocker, this);
if (!tryUnpark(blocker, timeout, unit)) {
record(1, "Fiber", "exec", "Blocker %s attempt to immediately execute %s FAILED", blocker, this);
return false;
}
immediateExecHelper();
return true;
}
/**
* Executes fiber on this thread, after waiting until the given blocker is indeed the fiber's blocker, and that the fiber is not being run concurrently.
*
* @param blocker
* @return {@code true} if the task has been executed by this method; {@code false} otherwise.
*/
final boolean exec(Object blocker, ParkAction prePark) {
if (!scheduler.isCurrentThreadInScheduler())
return false;
record(1, "Fiber", "exec", "Blocker %s attempting to immediately execute %s", blocker, this);
if (blocker != getBlocker() || !task.tryUnpark(blocker)) {
record(1, "Fiber", "exec", "Blocker %s attempt to immediately execute %s FAILED", blocker, this);
return false;
}
this.prePark = prePark;
immediateExecHelper();
return true;
}
private void immediateExecHelper() {
// final FibersMonitor monitor = getMonitor();
// if (monitor != null)
// monitor.fiberResumed();
this.noPreempt = true;
//this.inExec = true;
task.doExec();
}
// boolean isInExec() {
// return inExec;
// }
private StackTraceElement[] execStackTrace(long timeout, TimeUnit unit) {
if (!tryUnpark(null, timeout, unit))
return null;
this.noPreempt = true;
return execStackTrace1();
}
private FiberInfo execFiberInfo(long timeout, TimeUnit unit) {
if (!tryUnpark(null, timeout, unit))
return null;
final State s = this.state;
this.noPreempt = true;
final StackTraceElement[] st = execStackTrace1();
final Object blocker = getBlocker();
return makeFiberInfo(s, blocker, st);
}
private boolean tryUnpark(Object unblocker, long timeout, TimeUnit unit) {
long start = 0;
for (int i = 0;; i++) {
Object b = getBlocker();
boolean tu;
if ((unblocker != null ? b == unblocker : true) && (tu = task.tryUnpark(unblocker)))
return true;
if ((start = isTimeoutExpired(i, start, timeout, unit)) < 0)
return false;
}
}
private long isTimeoutExpired(int iter, long start, long timeout, TimeUnit unit) {
if (unit != null && timeout == 0)
return -1;
if (unit != null && timeout > 0 && iter > (1 << 12)) {
if (start == 0)
start = System.nanoTime();
else if (iter % 100 == 0) {
if (System.nanoTime() - start > unit.toNanos(timeout))
return -1;
}
}
return start;
}
Object getUnparker() {
return task.getUnparker();
}
StackTraceElement[] getUnparkStackTrace() {
return task.getUnparkStackTrace();
}
/**
* Makes available the permit for this fiber, if it was not already available.
* If the fiber was blocked on {@code park} then it will unblock.
* Otherwise, its next call to {@code park} is guaranteed not to block.
*/
@Override
public final void unpark() {
record(1, "Fiber", "unpark", "Unpark %s", this);
task.unpark();
}
@Override
public final void unpark(Object unblocker) {
record(1, "Fiber", "unpark", "Unpark %s by %s", this, unblocker);
task.unpark(unblocker);
}
final boolean unpark1(Object unblocker) {
record(1, "Fiber", "unpark1", "Unpark %s by %s", this, unblocker);
return task.unpark(unblocker);
}
@Override
@Suspendable
public final void join() throws ExecutionException, InterruptedException {
get();
}
@Override
@Suspendable
public final void join(long timeout, TimeUnit unit) throws ExecutionException, InterruptedException, TimeoutException {
get(timeout, unit);
}
public final Fiber joinNoSuspend() throws ExecutionException, InterruptedException {
task.get();
return this;
}
public final Fiber joinNoSuspend(long timeout, TimeUnit unit) throws ExecutionException, InterruptedException, TimeoutException {
task.get(timeout, unit);
return this;
}
@Override
@Suspendable
public final V get() throws ExecutionException, InterruptedException {
try {
return future().get();
} catch (RuntimeExecutionException t) {
throw new ExecutionException(t.getCause());
}
}
@Override
@Suspendable
public final V get(long timeout, TimeUnit unit) throws ExecutionException, InterruptedException, TimeoutException {
try {
return future().get(timeout, unit);
} catch (RuntimeExecutionException t) {
throw new ExecutionException(t.getCause());
}
}
@Override
public final boolean isDone() {
return isTerminated();
}
@Override
public final boolean cancel(boolean mayInterruptIfRunning) {
if (casState(State.NEW, State.TERMINATED))
future().cancel(mayInterruptIfRunning);
else
interrupt();
return !isDone();
}
@Override
public final boolean isCancelled() {
return future().isCancelled();
}
private void sleep1(long timeout, TimeUnit unit) throws InterruptedException, SuspendExecution {
if (getStackTrace) { // special case because this method isn't instrumented
onResume();
assert false : "shouldn't get here";
}
// this class's methods aren't instrumented, so we can't rely on the stack. This method will be called again when unparked
try {
for (;;) {
if (interrupted)
throw new InterruptedException();
final long now = System.nanoTime();
if (sleepStart == 0)
this.sleepStart = now;
final long deadline = sleepStart + unit.toNanos(timeout);
final long left = deadline - now;
if (left <= 0) {
this.sleepStart = 0;
return;
}
park1(null, null, left, TimeUnit.NANOSECONDS); // must be the last statement because we're not instrumented so we don't return here when awakened
}
} catch (SuspendExecution s) {
throw s;
} catch (Throwable t) {
this.sleepStart = 0;
throw t;
}
}
/**
* Set the handler invoked when this fiber abruptly terminates
* due to an uncaught exception.
*
* A fiber can take full control of how it responds to uncaught
* exceptions by having its uncaught exception handler explicitly set.
*
* @param eh the object to use as this fiber's uncaught exception
* handler. If {@code null} then this fiber has no explicit handler.
* @see #setDefaultUncaughtExceptionHandler
*/
@Override
public final void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
this.uncaughtExceptionHandler = eh;
}
/**
* Returns the handler invoked when this fiber abruptly terminates
* due to an uncaught exception.
*/
@Override
public final UncaughtExceptionHandler getUncaughtExceptionHandler() {
return uncaughtExceptionHandler;
}
/**
* Returns the default handler invoked when a fiber abruptly terminates
* due to an uncaught exception. If the returned value is {@code null},
* there is no default.
*
* @see #setDefaultUncaughtExceptionHandler
*/
public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler() {
return defaultUncaughtExceptionHandler;
}
/**
* Set the default handler invoked when a fiber abruptly terminates
* due to an uncaught exception, and no other handler has been defined
* for that fiber.
*
* @param eh the object to use as the default uncaught exception handler.
* If {@code null} then there is no default handler.
*
* @see #setUncaughtExceptionHandler
* @see #getUncaughtExceptionHandler
*/
public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
Fiber.defaultUncaughtExceptionHandler = eh;
}
static void setCurrentStrand(Strand f) {
currentStrand.set(f);
}
static Strand getCurrentStrand() {
return currentStrand.get();
}
Thread getRunningThread() {
return runningThread;
}
@Override
public final StackTraceElement[] getStackTrace() {
StackTraceElement[] threadStack = null;
if (currentFiber() == this)
threadStack = skipStackTraceElements(Thread.currentThread().getStackTrace(), 1); // remove Thread.getStackTrace
else {
for (;;) {
if (state == State.TERMINATED || state == State.NEW)
break;
if (state == State.RUNNING) {
final long r = run;
final Thread t = runningThread;
if (t != null)
threadStack = t.getStackTrace();
if (t != null && state == State.RUNNING && run == r && runningThread == t)
break;
} else {
threadStack = execStackTrace(1, TimeUnit.MILLISECONDS);
if (threadStack != null) {
// we need to unpark because if someone else had tried to unpark while we were in execStackTrace(), it would have silently failed.
unpark("getStackTrace");
break;
}
}
}
}
return threadToFiberStack(threadStack);
}
final FiberInfo getFiberInfo(boolean stack) {
if (currentFiber() == this)
return makeFiberInfo(State.RUNNING, null, stack ? skipStackTraceElements(Thread.currentThread().getStackTrace(), 1) : null); // remove Thread.getStackTrace
else {
for (;;) {
if (state == State.TERMINATED || state == State.NEW)
return makeFiberInfo(state, null, null);
if (state == State.RUNNING) {
if (stack) {
final long r = run;
final Thread t = runningThread;
StackTraceElement[] threadStack = null;
if (t != null)
threadStack = t.getStackTrace();
if (state == State.RUNNING && run == r && runningThread == t)
return makeFiberInfo(State.RUNNING, null, threadStack);
} else
return makeFiberInfo(State.RUNNING, null, null);
} else if (stack) {
FiberInfo fi = execFiberInfo(1, TimeUnit.MILLISECONDS);
if (fi != null) {
// we need to unpark because if someone else had tried to unpark while we were in execStackTrace(), it would have silently failed.
unpark();
return fi;
}
} else {
State s;
if ((s = state) == State.WAITING || s == State.TIMED_WAITING) {
Object blocker = getBlocker();
if ((s = state) == State.WAITING || s == State.TIMED_WAITING)
return makeFiberInfo(s, blocker, null);
}
}
}
}
}
private FiberInfo makeFiberInfo(State state, Object blocker, StackTraceElement[] stackTrace) {
return new FiberInfo(fid, getName(), state, blocker, threadToFiberStack(stackTrace));
}
private static StackTraceElement[] threadToFiberStack(StackTraceElement[] threadStack) {
if (threadStack == null)
return null;
if (threadStack.length == 0)
return threadStack;
int count = 0;
for (StackTraceElement ste : threadStack) {
count++;
if (Fiber.class.getName().equals(ste.getClassName())) {
if ("run".equals(ste.getMethodName()))
break;
if ("run1".equals(ste.getMethodName())) {
count--;
break;
}
}
}
StackTraceElement[] fiberStack = new StackTraceElement[count];
System.arraycopy(threadStack, 0, fiberStack, 0, count);
return fiberStack;
}
public static void dumpStack() {
verifyCurrent();
printStackTrace(new Exception("Stack trace"), System.err);
}
@SuppressWarnings("CallToThrowablePrintStackTrace")
private static void printStackTrace(Throwable t, java.io.OutputStream out) {
t.printStackTrace(new java.io.PrintStream(out) {
boolean seenExec;
@Override
public void println(String x) {
if (x.startsWith("\tat ")) {
if (seenExec)
return;
if (x.startsWith("\tat " + Fiber.class.getName() + ".exec")) {
seenExec = true;
return;
}
}
super.println(x);
}
});
}
V getResult() {
if (USE_VAL_FOR_RESULT)
return null;
return (V) result;
}
@Override
public final String toString() {
return "Fiber@" + fid + (name != null ? (':' + name) : "")
+ "[task: " + task + ", target: " + Objects.systemToString(target) + ", scheduler: " + scheduler + ']';
}
final Stack getStack() {
return stack;
}
/**
* An action to perform before or after a park (but always before the fiber has woken up)
*/
interface ParkAction {
/**
* Called by Fiber immediately before or after park.
* This method may not use any ThreadLocals as they have been rest by the time the method is called.
*
* @param current
*/
void run(Fiber current);
}
private static Fiber verifySuspend() {
return verifySuspend(verifyCurrent());
}
static Fiber verifySuspend(Fiber current) {
if (verifyInstrumentation)
checkInstrumentation();
return current;
}
private static Fiber verifyCurrent() {
Fiber current = currentFiber();
if (current == null) {
final Stack stack = Stack.getStack();
if (stack != null) {
current = stack.getFiber();
if (!current.getStackTrace)
throw new AssertionError();
return current;
}
throw new IllegalStateException("Not called on a fiber (current strand: " + Strand.currentStrand() + ")");
}
return current;
}
private static String sourceLineToDesc(int sourceLine) {
if (sourceLine == -1)
return "UNKNOWN";
else
return Integer.toString(sourceLine);
}
private static boolean checkInstrumentation() {
return checkInstrumentation(ExtendedStackTrace.here());
}
private static boolean checkInstrumentation(ExtendedStackTrace of) {
return checkInstrumentation(of, false);
}
@SuppressWarnings("null")
private static boolean checkInstrumentation(ExtendedStackTrace st, boolean fromUncaughtExc) {
if (fromUncaughtExc && st.get().length > 0 && st.get()[0] != null) {
final ExtendedStackTraceElement first = st.get()[0];
if (!first.getDeclaringClass().equals(ClassCastException.class)
&& !(first.getDeclaringClass().equals(NullPointerException.class) &&
first.getDeclaringClass().getName().startsWith("co.paralleluniverse.fibers")))
return true;
}
boolean ok = true;
StringBuilder stackTrace = null;
final ExtendedStackTraceElement[] stes = st.get();
for (int i = 0; i < stes.length; i++) {
final ExtendedStackTraceElement ste = stes[i];
if (ste.getClassName().equals(Thread.class.getName()) && ste.getMethodName().equals("getStackTrace"))
continue;
if (ste.getClassName().equals(ExtendedStackTrace.class.getName()))
continue;
if (!ok)
printTraceLine(stackTrace, ste);
if (ste.getClassName().contains("$$Lambda$"))
continue;
if (!ste.getClassName().equals(Fiber.class.getName()) && !ste.getClassName().startsWith(Fiber.class.getName() + '$')
&& !ste.getClassName().equals(Stack.class.getName()) && !SuspendableHelper.isWaiver(ste.getClassName(), ste.getMethodName())) {
final Class> clazz = ste.getDeclaringClass();
final boolean classInstrumented = SuspendableHelper.isInstrumented(clazz);
final /*Executable*/ Member m = SuspendableHelper.lookupMethod(ste);
if (m != null) {
final boolean methodInstrumented = SuspendableHelper.isInstrumented(m);
final Pair callSiteInstrumented = SuspendableHelper.isCallSiteInstrumented(m, ste.getLineNumber(), ste.getBytecodeIndex(), stes, i);
if (!classInstrumented || !methodInstrumented || !callSiteInstrumented.getFirst()) {
if (ok)
stackTrace = initTrace(i, stes);
if (!classInstrumented || !methodInstrumented)
stackTrace.append(" **");
else if (!callSiteInstrumented.getFirst())
stackTrace.append(" !! (instrumented suspendable calls at: ")
.append(callSitesString(callSiteInstrumented.getSecond()))
.append(")");
ok = false;
}
} else {
if (ok)
stackTrace = initTrace(i, stes);
stackTrace.append(" **"); // Methods can only be found via source lines in @Instrumented annotations
ok = false;
}
} else if (ste.getClassName().equals(Fiber.class.getName()) && ste.getMethodName().equals("run1")) {
if (!ok) {
final String str = "Uninstrumented whole methods ('**') or single calls ('!!') detected: " + stackTrace;
if (Debug.isUnitTest())
throw new VerifyInstrumentationException(str);
System.err.println("WARNING: " + str);
}
return ok;
}
}
throw new IllegalStateException("Not run through Fiber.exec(). (trace: " + Arrays.toString(stes) + ")");
}
private static String callSitesString(Instrumented i) {
if (i == null)
return "N/A";
return
"lines " + Arrays.toString(i.suspendableCallSites())
+ ", "
+ " calls " + Arrays.toString(getReadableCallsites(i.suspendableCallSiteNames()))
// + "BCIs " + Arrays.toString(i.suspendableCallSitesOffsetsAfterInstr()) +
;
}
private static String[] getReadableCallsites(String[] callsites) {
String[] readable = new String[callsites.length];
for (int i = 0; i < callsites.length; i++)
readable[i] = SuspendableHelper.getCallsiteOwner(callsites[i]) + "."
+ SuspendableHelper.getCallsiteName(callsites[i])
+ ASMUtil.getReadableDescriptor(SuspendableHelper.getCallsiteDesc(callsites[i]));
return readable;
}
private static StringBuilder initTrace(int i, ExtendedStackTraceElement[] stes) {
final StringBuilder stackTrace = new StringBuilder();
for (int j = 0; j <= i; j++) {
final ExtendedStackTraceElement ste2 = stes[j];
if (ste2.getClassName().equals(Thread.class.getName()) && ste2.getMethodName().equals("getStackTrace"))
continue;
printTraceLine(stackTrace, ste2);
}
return stackTrace;
}
private static void printTraceLine(StringBuilder stackTrace, ExtendedStackTraceElement ste) {
final Member m = SuspendableHelper.lookupMethod(ste);
stackTrace.append("\n\tat ").append(ste.getMethod() == null ? ste.toString(m) : ste.toString());
if (SuspendableHelper.isOptimized(m))
stackTrace.append(" (optimized)");
}
@SuppressWarnings("unchecked")
private static boolean isInstrumented(Class clazz) {
boolean res = clazz.isAnnotationPresent(Instrumented.class);
if (!res)
res = isInstrumented0(clazz); // a second chance
return res;
}
private static boolean isInstrumented0(Class clazz) {
// Sometimes, a child class does not implement any suspendable methods AND is loaded before its superclass (that does). Test for that:
Class superclazz = clazz.getSuperclass();
if (superclazz != null) {
if (superclazz.isAnnotationPresent(Instrumented.class)) {
// make sure the child class doesn't have any suspendable methods
Method[] ms = clazz.getDeclaredMethods();
for (Method m : ms) {
for (Class et : m.getExceptionTypes()) {
if (et.equals(SuspendExecution.class))
return false;
}
if (m.isAnnotationPresent(Suspendable.class))
return false;
}
return true;
} else
return isInstrumented0(superclazz);
} else
return false;
}
@VisibleForTesting
void resetState() {
task.tryUnpark(null);
assert task.getState() == FiberTask.RUNNABLE;
}
@VisibleForTesting // called _only_ in Fiber overhead benchmark
void reset() {
this.result = RESET;
stack.resetStack();
}
private static final VarHandle STATE;
static {
try {
MethodHandles.Lookup l = MethodHandles.lookup();
STATE = l.findVarHandle(Fiber.class, "state", Strand.State.class);
} catch (ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e);
}
}
private boolean casState(State expected, State update) {
return STATE.compareAndSet(this, expected, update);
}
private void orderedSetState(State value) {
STATE.setOpaque(this, value); // UNSAFE.putOrderedObject(this, stateOffset, value);
}
//
/////////// Recording ///////////////////////////////////
protected final boolean isRecordingLevel(int level) {
if (!Debug.isDebug())
return false;
final FlightRecorder.ThreadRecorder recorder = flightRecorder != null ? flightRecorder.get() : null;
if (recorder == null)
return false;
return recorder.recordsLevel(level);
}
protected final void record(int level, String clazz, String method, String format) {
if (flightRecorder != null)
record(flightRecorder.get(), level, clazz, method, format);
}
protected final void record(int level, String clazz, String method, String format, Object arg1) {
if (flightRecorder != null)
record(flightRecorder.get(), level, clazz, method, format, arg1);
}
protected final void record(int level, String clazz, String method, String format, Object arg1, Object arg2) {
if (flightRecorder != null)
record(flightRecorder.get(), level, clazz, method, format, arg1, arg2);
}
protected final void record(int level, String clazz, String method, String format, Object arg1, Object arg2, Object arg3) {
if (flightRecorder != null)
record(flightRecorder.get(), level, clazz, method, format, arg1, arg2, arg3);
}
protected final void record(int level, String clazz, String method, String format, Object arg1, Object arg2, Object arg3, Object arg4) {
if (flightRecorder != null)
record(flightRecorder.get(), level, clazz, method, format, arg1, arg2, arg3, arg4);
}
protected final void record(int level, String clazz, String method, String format, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5) {
if (flightRecorder != null)
record(flightRecorder.get(), level, clazz, method, format, arg1, arg2, arg3, arg4, arg5);
}
protected final void record(int level, String clazz, String method, String format, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6) {
if (flightRecorder != null)
record(flightRecorder.get(), level, clazz, method, format, arg1, arg2, arg3, arg4, arg5, arg6);
}
protected final void record(int level, String clazz, String method, String format, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7) {
if (flightRecorder != null)
record(flightRecorder.get(), level, clazz, method, format, arg1, arg2, arg3, arg4, arg5, arg6, arg7);
}
protected final void record(int level, String clazz, String method, String format, Object... args) {
if (flightRecorder != null)
record(flightRecorder.get(), level, clazz, method, format, args);
}
private static void record(FlightRecorder.ThreadRecorder recorder, int level, String clazz, String method, String format) {
if (recorder != null)
recorder.record(level, makeFlightRecorderMessage(recorder, clazz, method, format, null));
}
private static void record(FlightRecorder.ThreadRecorder recorder, int level, String clazz, String method, String format, Object arg1) {
if (recorder != null)
recorder.record(level, makeFlightRecorderMessage(recorder, clazz, method, format, new Object[]{arg1}));
}
private static void record(FlightRecorder.ThreadRecorder recorder, int level, String clazz, String method, String format, Object arg1, Object arg2) {
if (recorder != null)
recorder.record(level, makeFlightRecorderMessage(recorder, clazz, method, format, new Object[]{arg1, arg2}));
}
private static void record(FlightRecorder.ThreadRecorder recorder, int level, String clazz, String method, String format, Object arg1, Object arg2, Object arg3) {
if (recorder != null)
recorder.record(level, makeFlightRecorderMessage(recorder, clazz, method, format, new Object[]{arg1, arg2, arg3}));
}
private static void record(FlightRecorder.ThreadRecorder recorder, int level, String clazz, String method, String format, Object arg1, Object arg2, Object arg3, Object arg4) {
if (recorder != null)
recorder.record(level, makeFlightRecorderMessage(recorder, clazz, method, format, new Object[]{arg1, arg2, arg3, arg4}));
}
private static void record(FlightRecorder.ThreadRecorder recorder, int level, String clazz, String method, String format, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5) {
if (recorder != null)
recorder.record(level, makeFlightRecorderMessage(recorder, clazz, method, format, new Object[]{arg1, arg2, arg3, arg4, arg5}));
}
private static void record(FlightRecorder.ThreadRecorder recorder, int level, String clazz, String method, String format, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6) {
if (recorder != null)
recorder.record(level, makeFlightRecorderMessage(recorder, clazz, method, format, new Object[]{arg1, arg2, arg3, arg4, arg5, arg6}));
}
private static void record(FlightRecorder.ThreadRecorder recorder, int level, String clazz, String method, String format, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7) {
if (recorder != null)
recorder.record(level, makeFlightRecorderMessage(recorder, clazz, method, format, new Object[]{arg1, arg2, arg3, arg4, arg5, arg6, arg7}));
}
private static void record(FlightRecorder.ThreadRecorder recorder, int level, String clazz, String method, String format, Object... args) {
if (recorder != null)
recorder.record(level, makeFlightRecorderMessage(recorder, clazz, method, format, args));
}
private static FlightRecorderMessage makeFlightRecorderMessage(FlightRecorder.ThreadRecorder recorder, String clazz, String method, String format, Object[] args) {
return new FlightRecorderMessage(clazz, method, format, args);
//return ((FlightRecorderMessageFactory) recorder.getAux()).makeFlightRecorderMessage(clazz, method, format, args);
}
//
private static StackTraceElement[] skipStackTraceElements(StackTraceElement[] st, int skip) {
if (skip >= st.length)
return st; // something is wrong, but all the more reason not to lose the stacktrace
final StackTraceElement[] st1 = new StackTraceElement[st.length - skip];
System.arraycopy(st, skip, st1, 0, st1.length);
return st1;
}
/**
* Parks the fiber and allows the given callback to serialize it.
*
* @param writer a callback that can serialize the fiber.
* @throws SuspendExecution
*/
@SuppressWarnings("empty-statement")
public static void parkAndSerialize(final FiberWriter writer) throws SuspendExecution {
// if (writer == null)
// return; // should only happen during unparkSerialized
while (!park(SERIALIZER_BLOCKER, new ParkAction() {
@Override
public void run(Fiber f) {
f.record(1, "Fiber", "parkAndSerialize", "Serializing fiber %s", f);
writer.write(f, getFiberSerializer());
}
}));
}
/**
* Parks the fiber and allows the given callback to serialize it, optimized for use cases where
* the callback object has a custom way to obtain the required serializer (e.g. from a serializer pool)
*
* @param writer a callback that can serialize the fiber.
* @throws SuspendExecution
*/
@SuppressWarnings("empty-statement")
public static void parkAndCustomSerialize(final CustomFiberWriter writer) throws SuspendExecution {
while (!park(SERIALIZER_BLOCKER, new ParkAction() {
@Override
public void run(Fiber f) {
f.record(1, "Fiber", "parkAndCustomSerialize", "Serializing fiber %s", f);
writer.write(f);
}
})) ;
}
/**
* Deserializes a fiber from the given byte array and unparks it.
*
* @param serFiber The byte array containing a fiber's serialized form.
* @param scheduler The {@link FiberScheduler} to use for scheduling the fiber.
* @return The deserialized, running fiber.
*/
public static Fiber unparkSerialized(byte[] serFiber, FiberScheduler scheduler) {
final Fiber f = (Fiber) getFiberSerializer().read(serFiber);
return unparkDeserialized(f, scheduler);
}
/**
* Unparks a fiber that's been deserialized (with the help of {@link #getFiberSerializer()}
*
* @param f The deserialized fiber
* @param scheduler The {@link FiberScheduler} to use for scheduling the fiber.
* @return The fiber
*/
public static Fiber unparkDeserialized(Fiber f, FiberScheduler scheduler) {
f.record(1, "Fiber", "unparkDeserialized", "Deserialized fiber %s", f);
final Thread currentThread = Thread.currentThread();
f.fiberRef = new DummyRunnable(f);
f.fid = nextFiberId();
f.scheduler = scheduler;
f.task = scheduler.newFiberTask(f);
f.task.setState(FiberTask.PARKED);
if (USE_VAL_FOR_RESULT /*&& !isVoidResult(target)*/)
f.result = new Val();
f.contextClassLoader = ThreadAccess.getContextClassLoader(currentThread);
if (MAINTAIN_ACCESS_CONTROL_CONTEXT)
f.inheritedAccessControlContext = AccessController.getContext();
f.record(1, "Fiber", "unparkDeserialized", "Unparking deserialized fiber %s", f);
f.unpark(SERIALIZER_BLOCKER);
return f;
}
/**
* Returns a {@link ByteArraySerializer} capable of serializing an object graph containing fibers.
*/
public static ByteArraySerializer getFiberSerializer() {
return getFiberSerializer(true);
}
/**
* Returns a {@link ByteArraySerializer} capable of serializing an object graph containing fibers.
*
* @param includeThreadLocals if true, thread/fiber local storage slots will also be serialised.
* You may want to set this to false if you are using frameworks that put
* things that cannot be properly serialised into TLS slots, or if the feature
* causes other issues.
*/
public static ByteArraySerializer getFiberSerializer(boolean includeThreadLocals) {
return jdkSerializer;
}
/*
private static class FiberSerializer extends Serializer {
private boolean includeThreadLocals;
public FiberSerializer(boolean includeThreadLocals) {
this.includeThreadLocals = includeThreadLocals;
setImmutable(true);
}
@Override
@SuppressWarnings({"CallToPrintStackTrace", "unchecked"})
public void write(Kryo kryo, Output output, Fiber f) {
final Thread currentThread = Thread.currentThread();
// If we need to serialise thread local storage slots as well, then we have to do a swap to avoid
// type problems. If we don't, then it's much easier to serialise things.
if (!includeThreadLocals || f.noLocals) {
Object tmpFiberLocals = f.fiberLocals;
Object tmpInheritableFiberLocals = f.inheritableFiberLocals;
f.fiberLocals = null;
f.inheritableFiberLocals = null;
try {
f.stack.resumeStack();
kryo.writeClass(output, f.getClass());
new FieldSerializer(kryo, f.getClass()).write(kryo, output, f);
} finally {
f.fiberLocals = tmpFiberLocals;
f.inheritableFiberLocals = tmpInheritableFiberLocals;
}
} else {
final Object tmpThreadLocals = ThreadAccess.getThreadLocals(currentThread);
final Object tmpInheritableThreadLocals = ThreadAccess.getInheritableThreadLocals(currentThread);
ThreadAccess.setThreadLocals(currentThread, f.fiberLocals);
ThreadAccess.setInheritableThreadLocals(currentThread, f.inheritableFiberLocals);
Object realFiberLocals = f.fiberLocals;
Object realInheritableFiberLocals = f.inheritableFiberLocals;
try {
// Switch the type of fiberLocals here to Object[] from ThreadLocalMap, to ease serialisation.
// We must switch it back when we are done.
f.fiberLocals = realFiberLocals != null ? ThreadAccess.toMap(realFiberLocals).keySet().toArray() : null;
f.inheritableFiberLocals = realInheritableFiberLocals != null
? ThreadAccess.toMap(realInheritableFiberLocals).keySet().toArray() : null;
f.stack.resumeStack();
kryo.writeClass(output, f.getClass());
new FieldSerializer(kryo, f.getClass()).write(kryo, output, f);
} catch (Throwable t) {
t.printStackTrace();
throw t;
} finally {
f.fiberLocals = realFiberLocals;
f.inheritableFiberLocals = realInheritableFiberLocals;
ThreadAccess.setThreadLocals(currentThread, tmpThreadLocals);
ThreadAccess.setInheritableThreadLocals(currentThread, tmpInheritableThreadLocals);
}
}
}
@Override
@SuppressWarnings("CallToPrintStackTrace")
public Fiber read(Kryo kryo, Input input, Class type) {
final Fiber f;
final Thread currentThread = Thread.currentThread();
final Object tmpThreadLocals = ThreadAccess.getThreadLocals(currentThread);
final Object tmpInheritableThreadLocals = ThreadAccess.getInheritableThreadLocals(currentThread);
ThreadAccess.setThreadLocals(currentThread, null);
ThreadAccess.setInheritableThreadLocals(currentThread, null);
try {
final Registration reg = kryo.readClass(input);
if (reg == null)
return null;
f = (Fiber) new FieldSerializer(kryo, reg.getType()).read(kryo, input, reg.getType());
if (!f.noLocals) {
f.fiberLocals = ThreadAccess.getThreadLocals(currentThread);
f.inheritableFiberLocals = ThreadAccess.getInheritableThreadLocals(currentThread);
}
return f;
} catch (Throwable t) {
t.printStackTrace();
throw t;
} finally {
ThreadAccess.setThreadLocals(currentThread, tmpThreadLocals);
ThreadAccess.setInheritableThreadLocals(currentThread, tmpInheritableThreadLocals);
}
}
}
*/
}