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

com.sun.xml.ws.api.pipe.Fiber Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 1997, 2022 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Distribution License v. 1.0, which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

package com.sun.xml.ws.api.pipe;

import com.sun.istack.NotNull;
import com.sun.istack.Nullable;
import com.sun.xml.ws.api.Cancelable;
import com.sun.xml.ws.api.Component;
import com.sun.xml.ws.api.ComponentRegistry;
import com.sun.xml.ws.api.SOAPVersion;
import com.sun.xml.ws.api.addressing.AddressingVersion;
import com.sun.xml.ws.api.message.AddressingUtils;
import com.sun.xml.ws.api.message.Packet;
import com.sun.xml.ws.api.pipe.helper.AbstractFilterTubeImpl;
import com.sun.xml.ws.api.pipe.helper.AbstractTubeImpl;
import com.sun.xml.ws.api.server.Adapter;
import com.sun.xml.ws.api.server.Container;
import com.sun.xml.ws.api.server.ContainerResolver;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;

import jakarta.xml.ws.Holder;
import jakarta.xml.ws.WebServiceException;

/**
 * User-level thread. Represents the execution of one request/response processing.
 * 
*
* JAX-WS RI is capable of running a large number of request/response concurrently by * using a relatively small number of threads. This is made possible by utilizing * a {@link Fiber} — a user-level thread that gets created for each request/response * processing. *
*
* A fiber remembers where in the pipeline the processing is at, what needs to be * executed on the way out (when processing response), and other additional information * specific to the execution of a particular request/response. *
*

Suspend/Resume

*
* Fiber can be {@link NextAction#suspend() suspended} by a {@link Tube}. * When a fiber is suspended, it will be kept on the side until it is * {@link #resume(Packet) resumed}. This allows threads to go execute * other runnable fibers, allowing efficient utilization of smaller number of * threads. *
*

Context-switch Interception

*
* {@link FiberContextSwitchInterceptor} allows {@link Tube}s and {@link Adapter}s * to perform additional processing every time a thread starts running a fiber * and stops running it. *
*

Context ClassLoader

*
* Just like thread, a fiber has a context class loader (CCL.) A fiber's CCL * becomes the thread's CCL when it's executing the fiber. The original CCL * of the thread will be restored when the thread leaves the fiber execution. *
*
*

Debugging Aid

*
* Because {@link Fiber} doesn't keep much in the call stack, and instead use * {@link #conts} to store the continuation, debugging fiber related activities * could be harder. *
*
* Setting the {@link #LOGGER} for FINE would give you basic start/stop/resume/suspend * level logging. Using FINER would cause more detailed logging, which includes * what tubes are executed in what order and how they behaved. *
*
* When you debug the server side, consider setting {@link Fiber#serializeExecution} * to true, so that execution of fibers are serialized. Debugging a server * with more than one running threads is very tricky, and this switch will * prevent that. This can be also enabled by setting the system property on. * See the source code. * * @author Kohsuke Kawaguchi * @author Jitendra Kotamraju */ public final class Fiber implements Runnable, Cancelable, ComponentRegistry { /** * Callback interface for notification of suspend and resume. * * @since 2.2.6 * @deprecated Use {@link NextAction#suspend(Runnable)} */ @Deprecated public interface Listener { /** * Fiber has been suspended. Implementations of this callback may resume the Fiber. * @param fiber Fiber */ void fiberSuspended(Fiber fiber); /** * Fiber has been resumed. Behavior is undefined if implementations of this callback attempt to suspend the Fiber. * @param fiber Fiber */ void fiberResumed(Fiber fiber); } private final List _listeners = new ArrayList<>(); /** * Adds suspend/resume callback listener * @param listener Listener * @since 2.2.6 * @deprecated */ @Deprecated public void addListener(Listener listener) { synchronized(_listeners) { if (!_listeners.contains(listener)) { _listeners.add(listener); } } } /** * Removes suspend/resume callback listener * @param listener Listener * @since 2.2.6 * @deprecated */ @Deprecated public void removeListener(Listener listener) { synchronized(_listeners) { _listeners.remove(listener); } } List getCurrentListeners() { synchronized(_listeners) { return new ArrayList<>(_listeners); } } private void clearListeners() { synchronized(_listeners) { _listeners.clear(); } } /** * {@link Tube}s whose {@link Tube#processResponse(Packet)} method needs * to be invoked on the way back. */ private Tube[] conts = new Tube[16]; private int contsSize; /** * If this field is non-null, the next instruction to execute is * to call its {@link Tube#processRequest(Packet)}. Otherwise * the instruction is to call {@link #conts}. */ private Tube next; private Packet packet; private Throwable/*but really it's either RuntimeException or Error*/ throwable; public final Engine owner; /** * Is this thread suspended? 0=not suspended, 1=suspended. *
*
* Logically this is just a boolean, but we need to prepare for the case * where the thread is {@link #resume(Packet) resumed} before we get to the {@link #suspend(Holder, Runnable)}. * This happens when things happen in the following order: *
*
    *
  1. Tube decides that the fiber needs to be suspended to wait for the external event. *
  2. Tube hooks up fiber with some external mechanism (like NIO channel selector) *
  3. Tube returns with {@link NextAction#suspend()}. *
  4. "External mechanism" becomes signal state and invokes {@link Fiber#resume(Packet)} * to wake up fiber *
  5. {@link Fiber#doRun} invokes {@link Fiber#suspend(Holder, Runnable)}. *
*
*
* Using int, this will work OK because {@code suspendedCount} becomes -1 when * {@link #resume(Packet)} occurs before {@link #suspend(Holder, Runnable)}. *
*
* Increment and decrement is guarded by 'this' object. */ private volatile int suspendedCount = 0; private volatile boolean isInsideSuspendCallbacks = false; /** * Is this {@link Fiber} currently running in the synchronous mode? */ private boolean synchronous; private boolean interrupted; private final int id; /** * Active {@link FiberContextSwitchInterceptor}s for this fiber. */ private List interceptors; /** * Fiber's context {@link ClassLoader}. */ private @Nullable ClassLoader contextClassLoader; private @Nullable CompletionCallback completionCallback; private boolean isDeliverThrowableInPacket = false; public void setDeliverThrowableInPacket(boolean isDeliverThrowableInPacket) { this.isDeliverThrowableInPacket = isDeliverThrowableInPacket; } /** * The thread on which this Fiber is currently executing, if applicable. */ private Thread currentThread; /** * Replace uses of synchronized(this) with this lock so that we can control * unlocking for resume use cases */ private final ReentrantLock lock = new ReentrantLock(); private final Condition condition = lock.newCondition(); private volatile boolean isCanceled; /** * Set to true if this fiber is started asynchronously, to avoid * doubly-invoking completion code. */ private boolean started; /** * Set to true if this fiber is started sync but allowed to run async. * This property exists for use cases where the processing model is fundamentally async * but some requirement or feature mandates that part of the tubeline run synchronously. For * instance, WS-ReliableMessaging with non-anonymous addressing is compatible with running * asynchronously, but if in-order message delivery is used then message processing must assign * a message number before the remainder of the processing can be asynchronous. */ private boolean startedSync; /** * Callback to be invoked when a {@link Fiber} finishes execution. */ public interface CompletionCallback { /** * Indicates that the fiber has finished its execution. *
*
* Since the JAX-WS RI runs asynchronously, * this method maybe invoked by a different thread * than any of the threads that started it or run a part of tubeline. */ void onCompletion(@NotNull Packet response); /** * Indicates that the fiber has finished abnormally, by throwing a given {@link Throwable}. */ void onCompletion(@NotNull Throwable error); } Fiber(Engine engine) { this.owner = engine; id = iotaGen.incrementAndGet(); if (isTraceEnabled()) { LOGGER.log(Level.FINE, "{0} created", getName()); } // if this is run from another fiber, then we naturally inherit its context classloader, // so this code works for fiber->fiber inheritance just fine. contextClassLoader = Thread.currentThread().getContextClassLoader(); } /** * Starts the execution of this fiber asynchronously. *
*
* This method works like {@link Thread#start()}. * * @param tubeline The first tube of the tubeline that will act on the packet. * @param request The request packet to be passed to {@code startPoint.processRequest()}. * @param completionCallback The callback to be invoked when the processing is finished and the * final response packet is available. * @see #runSync(Tube, Packet) */ public void start(@NotNull Tube tubeline, @NotNull Packet request, @Nullable CompletionCallback completionCallback) { start(tubeline, request, completionCallback, false); } private void dumpFiberContext(String desc) { if(isTraceEnabled()) { String action = null; String msgId = null; if (packet != null) { for (SOAPVersion sv: SOAPVersion.values()) { for (AddressingVersion av: AddressingVersion.values()) { action = packet.getMessage() != null ? AddressingUtils.getAction(packet.getMessage().getHeaders(), av, sv) : null; msgId = packet.getMessage() != null ? AddressingUtils.getMessageID(packet.getMessage().getHeaders(), av, sv) : null; if (action != null || msgId != null) { break; } } if (action != null || msgId != null) { break; } } } String actionAndMsgDesc; if (action == null && msgId == null) { actionAndMsgDesc = "NO ACTION or MSG ID"; } else { actionAndMsgDesc = "'" + action + "' and msgId '" + msgId + "'"; } String tubeDesc; if (next != null) { tubeDesc = next + ".processRequest()"; } else { tubeDesc = peekCont() + ".processResponse()"; } LOGGER.log(Level.FINE, "{0} {1} with {2} and ''current'' tube {3} from thread {4} with Packet: {5}", new Object[]{getName(), desc, actionAndMsgDesc, tubeDesc, Thread.currentThread().getName(), packet != null ? packet.toShortString() : null}); } } /** * Starts the execution of this fiber. * * If forceSync is true, then the fiber is started for an ostensibly async invocation, * but allows for some portion of the tubeline to run sync with the calling * client instance (Port/Dispatch instance). This allows tubes that enforce * ordering to see requests in the order they were sent at the point the * client invoked them. *

* The forceSync parameter will be true only when the caller (e.g. AsyncInvoker or * SEIStub) knows one or more tubes need to enforce ordering and thus need * to run sync with the client. Such tubes can return * NextAction.INVOKE_ASYNC to indicate that the next tube in the tubeline * should be invoked async to the current thread. * *

* This method works like {@link Thread#start()}. * * @param tubeline * The first tube of the tubeline that will act on the packet. * @param request * The request packet to be passed to {@code startPoint.processRequest()}. * @param completionCallback * The callback to be invoked when the processing is finished and the * final response packet is available. * * @see #start(Tube,Packet,CompletionCallback) * @see #runSync(Tube,Packet) * @since 2.2.6 */ public void start(@NotNull Tube tubeline, @NotNull Packet request, @Nullable CompletionCallback completionCallback, boolean forceSync) { next = tubeline; this.packet = request; this.completionCallback = completionCallback; if (forceSync) { this.startedSync = true; dumpFiberContext("starting (sync)"); run(); } else { this.started = true; dumpFiberContext("starting (async)"); owner.addRunnable(this); } } /** * Wakes up a suspended fiber. *
*
* If a fiber was suspended without specifying the next {@link Tube}, * then the execution will be resumed in the response processing direction, * by calling the {@link Tube#processResponse(Packet)} method on the next/first * {@link Tube} in the {@link Fiber}'s processing stack with the specified resume * packet as the parameter. *
*
* If a fiber was suspended with specifying the next {@link Tube}, * then the execution will be resumed in the request processing direction, * by calling the next tube's {@link Tube#processRequest(Packet)} method with the * specified resume packet as the parameter. *
*
* This method is implemented in a race-free way. Another thread can invoke * this method even before this fiber goes into the suspension mode. So the caller * need not worry about synchronizing {@link NextAction#suspend()} and this method. * * @param resumePacket packet used in the resumed processing */ public void resume(@NotNull Packet resumePacket) { resume(resumePacket, false); } /** * Similar to resume(Packet) but allowing the Fiber to be resumed * synchronously (in the current Thread). If you want to know when the * fiber completes (not when this method returns) then add/wrap a * CompletionCallback on this Fiber. * For example, an asynchronous response endpoint that supports WS-ReliableMessaging * including in-order message delivery may need to resume the Fiber synchronously * until message order is confirmed prior to returning to asynchronous processing. * @since 2.2.6 */ public void resume(@NotNull Packet resumePacket, boolean forceSync) { resume(resumePacket, forceSync, null); } /** * Similar to resume(Packet, boolean) but allowing the Fiber to be resumed * and at the same time atomically assign a new CompletionCallback to it. * @since 2.2.6 */ public void resume(@NotNull Packet resumePacket, boolean forceSync, CompletionCallback callback) { lock.lock(); try { if (callback != null) { setCompletionCallback(callback); } if(isTraceEnabled()) LOGGER.log(Level.FINE, "{0} resuming. Will have suspendedCount={1}", new Object[]{getName(), suspendedCount-1}); packet = resumePacket; if( --suspendedCount == 0 ) { if (!isInsideSuspendCallbacks) { List listeners = getCurrentListeners(); for (Listener listener: listeners) { try { listener.fiberResumed(this); } catch (Throwable e) { if (isTraceEnabled()) LOGGER.log(Level.FINE, "Listener {0} threw exception: {1}", new Object[]{listener, e.getMessage()}); } } if(synchronous) { condition.signalAll(); } else if (forceSync || startedSync) { run(); } else { dumpFiberContext("resuming (async)"); owner.addRunnable(this); } } } else { if (isTraceEnabled()) { LOGGER.log(Level.FINE, "{0} taking no action on resume because suspendedCount != 0: {1}", new Object[]{getName(), suspendedCount}); } } } finally { lock.unlock(); } } /** * Wakes up a suspended fiber and begins response processing. * @since 2.2.6 */ public void resumeAndReturn(@NotNull Packet resumePacket, boolean forceSync) { if(isTraceEnabled()) LOGGER.log(Level.FINE, "{0} resumed with Return Packet", getName()); next = null; resume(resumePacket, forceSync); } /** * Wakes up a suspended fiber with an exception. *
*
* The execution of the suspended fiber will be resumed in the response * processing direction, by calling the {@link Tube#processException(Throwable)} method * on the next/first {@link Tube} in the {@link Fiber}'s processing stack with * the specified exception as the parameter. *
*
* This method is implemented in a race-free way. Another thread can invoke * this method even before this fiber goes into the suspension mode. So the caller * need not worry about synchronizing {@link NextAction#suspend()} and this method. * * @param throwable exception that is used in the resumed processing */ public void resume(@NotNull Throwable throwable) { resume(throwable, packet, false); } /** * Wakes up a suspended fiber with an exception. *
*
* The execution of the suspended fiber will be resumed in the response * processing direction, by calling the {@link Tube#processException(Throwable)} method * on the next/first {@link Tube} in the {@link Fiber}'s processing stack with * the specified exception as the parameter. *
*
* This method is implemented in a race-free way. Another thread can invoke * this method even before this fiber goes into the suspension mode. So the caller * need not worry about synchronizing {@link NextAction#suspend()} and this method. * * @param throwable exception that is used in the resumed processing * @param packet Packet that will be visible on the Fiber after the resume * @since 2.2.8 */ public void resume(@NotNull Throwable throwable, @NotNull Packet packet) { resume(throwable, packet, false); } /** * Wakes up a suspend fiber with an exception. * * If forceSync is true, then the suspended fiber will resume with * synchronous processing on the current thread. This will continue * until some Tube indicates that it is safe to switch to asynchronous * processing. * * @param error exception that is used in the resumed processing * @param forceSync if processing begins synchronously * @since 2.2.6 */ public void resume(@NotNull Throwable error, boolean forceSync) { resume(error, packet, forceSync); } /** * Wakes up a suspend fiber with an exception. * * If forceSync is true, then the suspended fiber will resume with * synchronous processing on the current thread. This will continue * until some Tube indicates that it is safe to switch to asynchronous * processing. * * @param error exception that is used in the resumed processing * @param packet Packet that will be visible on the Fiber after the resume * @param forceSync if processing begins synchronously * @since 2.2.8 */ public void resume(@NotNull Throwable error, @NotNull Packet packet, boolean forceSync) { if(isTraceEnabled()) LOGGER.log(Level.FINE, "{0} resumed with Return Throwable", getName()); next = null; throwable = error; resume(packet, forceSync); } /** * Marks this Fiber as cancelled. A cancelled Fiber will never invoke its completion callback * @param mayInterrupt if cancel should use {@link Thread#interrupt()} * @see java.util.concurrent.Future#cancel(boolean) * @since 2.2.6 */ @Override public void cancel(boolean mayInterrupt) { isCanceled = true; if (mayInterrupt) { // synchronized(this) is used as Thread running Fiber will be holding lock synchronized(this) { if (currentThread != null) currentThread.interrupt(); } } } /** * Suspends this fiber's execution until the resume method is invoked. *
* The call returns immediately, and when the fiber is resumed * the execution picks up from the last scheduled continuation. * @param onExitRunnable runnable to be invoked after fiber is marked for suspension * @return if control loop must exit */ private boolean suspend(Holder isRequireUnlock, Runnable onExitRunnable) { if(isTraceEnabled()) { LOGGER.log(Level.FINE, "{0} suspending. Will have suspendedCount={1}", new Object[]{getName(), suspendedCount+1}); if (suspendedCount > 0) { LOGGER.log(Level.FINE, "WARNING - {0} suspended more than resumed. Will require more than one resume to actually resume this fiber.", getName()); } } List listeners = getCurrentListeners(); if (++suspendedCount == 1) { isInsideSuspendCallbacks = true; try { for (Listener listener: listeners) { try { listener.fiberSuspended(this); } catch (Throwable e) { if(isTraceEnabled()) LOGGER.log(Level.FINE, "Listener {0} threw exception: {1}", new Object[]{listener, e.getMessage()}); } } } finally { isInsideSuspendCallbacks = false; } } if (suspendedCount <= 0) { // suspend callback caused fiber to resume for (Listener listener: listeners) { try { listener.fiberResumed(this); } catch (Throwable e) { if(isTraceEnabled()) LOGGER.log(Level.FINE, "Listener {0} threw exception: {1}", new Object[]{listener, e.getMessage()}); } } } else if (onExitRunnable != null) { // synchronous use cases cannot disconnect from the current thread if (!synchronous) { /* INTENTIONALLY UNLOCKING EARLY */ synchronized(this) { // currentThread is protected by the monitor for this fiber so // that it is accessible to cancel() even when the lock is held currentThread = null; } lock.unlock(); assert(!lock.isHeldByCurrentThread()); isRequireUnlock.value = Boolean.FALSE; try { onExitRunnable.run(); } catch(Throwable t) { throw new OnExitRunnableException(t); } return true; } else { // for synchronous we will stay with current thread, so do not disconnect if (isTraceEnabled()) LOGGER.fine("onExitRunnable used with synchronous Fiber execution -- not exiting current thread"); onExitRunnable.run(); } } return false; } private static final class OnExitRunnableException extends RuntimeException { private static final long serialVersionUID = 1L; Throwable target; public OnExitRunnableException(Throwable target) { super((Throwable)null); // see pattern for InvocationTargetException this.target = target; } } /** * Adds a new {@link FiberContextSwitchInterceptor} to this fiber. *
*
* The newly installed fiber will take effect immediately after the current * tube returns from its {@link Tube#processRequest(Packet)} or * {@link Tube#processResponse(Packet)}, before the next tube begins processing. *
*
* So when the tubeline consists of X and Y, and when X installs an interceptor, * the order of execution will be as follows: *
*

    *
  1. X.processRequest() *
  2. interceptor gets installed *
  3. interceptor.execute() is invoked *
  4. Y.processRequest() *
*/ public synchronized void addInterceptor(@NotNull FiberContextSwitchInterceptor interceptor) { if (interceptors == null) { interceptors = new ArrayList<>(); } else { List l = new ArrayList<>(interceptors); interceptors = l; } interceptors.add(interceptor); } /** * Removes a {@link FiberContextSwitchInterceptor} from this fiber. *
*
* The removal of the interceptor takes effect immediately after the current * tube returns from its {@link Tube#processRequest(Packet)} or * {@link Tube#processResponse(Packet)}, before the next tube begins processing. *
*
*
* So when the tubeline consists of X and Y, and when Y uninstalls an interceptor * on the way out, then the order of execution will be as follows: *
*
    *
  1. Y.processResponse() (notice that this happens with interceptor.execute() in the callstack) *
  2. interceptor gets uninstalled *
  3. interceptor.execute() returns *
  4. X.processResponse() *
* * @return true if the specified interceptor was removed. False if * the specified interceptor was not registered with this fiber to begin with. */ public synchronized boolean removeInterceptor(@NotNull FiberContextSwitchInterceptor interceptor) { if (interceptors != null) { boolean result = interceptors.remove(interceptor); if (interceptors.isEmpty()) interceptors = null; else { List l = new ArrayList<>(interceptors); interceptors = l; } return result; } return false; } /** * Gets the context {@link ClassLoader} of this fiber. */ public @Nullable ClassLoader getContextClassLoader() { return contextClassLoader; } /** * Sets the context {@link ClassLoader} of this fiber. */ public ClassLoader setContextClassLoader(@Nullable ClassLoader contextClassLoader) { ClassLoader r = this.contextClassLoader; this.contextClassLoader = contextClassLoader; return r; } /** * DO NOT CALL THIS METHOD. This is an implementation detail * of {@link Fiber}. */ @Deprecated @Override public void run() { Container old = ContainerResolver.getDefault().enterContainer(owner.getContainer()); try { assert !synchronous; // doRun returns true to indicate an early exit from fiber processing if (!doRun()) { if (startedSync && suspendedCount == 0 && (next != null || contsSize > 0)) { // We bailed out of running this fiber we started as sync, and now // want to finish running it async startedSync = false; // Start back up as an async fiber dumpFiberContext("restarting (async) after startSync"); owner.addRunnable(this); } else { completionCheck(); } } } finally { ContainerResolver.getDefault().exitContainer(old); } } /** * Runs a given {@link Tube} (and everything thereafter) synchronously. *
*
* This method blocks and returns only when all the successive {@link Tube}s * complete their request/response processing. This method can be used * if a {@link Tube} needs to fallback to synchronous processing. *
*

* Example: *

     * class FooTube extends {@link AbstractFilterTubeImpl} {
     *   NextAction processRequest(Packet request) {
     *     // run everything synchronously and return with the response packet
     *     return doReturnWith(Fiber.current().runSync(next,request));
     *   }
     *   NextAction processResponse(Packet response) {
     *     // never be invoked
     *   }
     * }
     * 
* * @param tubeline The first tube of the tubeline that will act on the packet. * @param request The request packet to be passed to {@code startPoint.processRequest()}. * @return The response packet to the {@code request}. * @see #start(Tube, Packet, CompletionCallback) */ public @NotNull Packet runSync(@NotNull Tube tubeline, @NotNull Packet request) { lock.lock(); try { // save the current continuation, so that we return runSync() without executing them. final Tube[] oldCont = conts; final int oldContSize = contsSize; final boolean oldSynchronous = synchronous; final Tube oldNext = next; if (oldContSize > 0) { conts = new Tube[16]; contsSize = 0; } try { synchronous = true; this.packet = request; next = tubeline; doRun(); if (throwable != null) { if (isDeliverThrowableInPacket) { packet.addSatellite(new ThrowableContainerPropertySet(throwable)); } else { if (throwable instanceof RuntimeException) { throw (RuntimeException) throwable; } if (throwable instanceof Error) { throw (Error) throwable; } // our system is supposed to only accept Error or RuntimeException throw new AssertionError(throwable); } } return this.packet; } finally { conts = oldCont; contsSize = oldContSize; synchronous = oldSynchronous; next = oldNext; if(interrupted) { Thread.currentThread().interrupt(); interrupted = false; } if(!started && !startedSync) completionCheck(); } } finally { lock.unlock(); } } private void completionCheck() { lock.lock(); try { // Don't trigger completion and callbacks if fiber is suspended if(!isCanceled && contsSize==0 && suspendedCount == 0) { if(isTraceEnabled()) LOGGER.log(Level.FINE, "{0} completed", getName()); clearListeners(); condition.signalAll(); if (completionCallback != null) { if (throwable != null) { if (isDeliverThrowableInPacket) { packet.addSatellite(new ThrowableContainerPropertySet(throwable)); completionCallback.onCompletion(packet); } else completionCallback.onCompletion(throwable); } else completionCallback.onCompletion(packet); } } } finally { lock.unlock(); } } /** * Invokes all registered {@link InterceptorHandler}s and then call into * {@link #__doRun(Holder, List)}. */ private class InterceptorHandler implements FiberContextSwitchInterceptor.Work { private final Holder isUnlockRequired; private final List ints; /** * Index in {@link Fiber#interceptors} to invoke next. */ private int idx; public InterceptorHandler(Holder isUnlockRequired, List ints) { this.isUnlockRequired = isUnlockRequired; this.ints = ints; } /** * Initiate the interception, and eventually invokes {@link #__doRun(Holder, List)}. */ Tube invoke(Tube next) { idx = 0; return execute(next); } @Override public Tube execute(Tube next) { if (idx == ints.size()) { Fiber.this.next = next; if (__doRun(isUnlockRequired, ints)) return PLACEHOLDER; } else { FiberContextSwitchInterceptor interceptor = ints.get(idx++); return interceptor.execute(Fiber.this, next, this); } return Fiber.this.next; } } private static final PlaceholderTube PLACEHOLDER = new PlaceholderTube(); private static class PlaceholderTube extends AbstractTubeImpl { @Override public NextAction processRequest(Packet request) { throw new UnsupportedOperationException(); } @Override public NextAction processResponse(Packet response) { throw new UnsupportedOperationException(); } @Override public NextAction processException(Throwable t) { return doThrow(t); } @Override public void preDestroy() { } @Override public PlaceholderTube copy(TubeCloner cloner) { throw new UnsupportedOperationException(); } } /** * Executes the fiber as much as possible. * */ private boolean doRun() { dumpFiberContext("running"); if (serializeExecution) { serializedExecutionLock.lock(); try { return _doRun(next); } finally { serializedExecutionLock.unlock(); } } else { return _doRun(next); } } private boolean _doRun(Tube next) { // isRequireUnlock will contain Boolean.FALSE when lock has already been released in suspend Holder isRequireUnlock = new Holder<>(Boolean.TRUE); lock.lock(); try { List ints; ClassLoader old; synchronized(this) { ints = interceptors; // currentThread is protected by the monitor for this fiber so // that it is accessible to cancel() even when the lock is held currentThread = Thread.currentThread(); if (isTraceEnabled()) { LOGGER.log(Level.FINE, "Thread entering _doRun(): {0}", currentThread); } old = currentThread.getContextClassLoader(); currentThread.setContextClassLoader(contextClassLoader); } try { boolean needsToReenter; do { // if interceptors are set, go through the interceptors. if (ints == null) { this.next = next; if (__doRun(isRequireUnlock, null /*ints*/)) { return true; } } else { next = new InterceptorHandler(isRequireUnlock, ints).invoke(next); if (next == PLACEHOLDER) { return true; } } synchronized(this) { needsToReenter = (ints != interceptors); if (needsToReenter) ints = interceptors; } } while (needsToReenter); } catch(OnExitRunnableException o) { // catching this exception indicates onExitRunnable in suspend() threw. // we must still avoid double unlock Throwable t = o.target; if (t instanceof WebServiceException) throw (WebServiceException) t; throw new WebServiceException(t); } finally { // don't reference currentThread here because fiber processing // may already be running on a different thread (Note: isAlreadyExited // tracks this state Thread thread = Thread.currentThread(); thread.setContextClassLoader(old); if (isTraceEnabled()) { LOGGER.log(Level.FINE, "Thread leaving _doRun(): {0}", thread); } } return false; } finally { if (isRequireUnlock.value) { synchronized(this) { currentThread = null; } lock.unlock(); } } } /** * To be invoked from {@link #doRun()}. * * @see #doRun() */ private boolean __doRun(Holder isRequireUnlock, List originalInterceptors) { assert(lock.isHeldByCurrentThread()); final Fiber old = CURRENT_FIBER.get(); CURRENT_FIBER.set(this); // if true, lots of debug messages to show what's being executed final boolean traceEnabled = LOGGER.isLoggable(Level.FINER); try { boolean abortResponse = false; while(isReady(originalInterceptors)) { if (isCanceled) { next = null; throwable = null; contsSize = 0; break; } try { NextAction na; Tube last; if(throwable!=null) { if(contsSize==0 || abortResponse) { contsSize = 0; // abortResponse case // nothing else to execute. we are done. return false; } last = popCont(); if (traceEnabled) LOGGER.log(Level.FINER, "{0} {1}.processException({2})", new Object[]{getName(), last, throwable}); na = last.processException(throwable); } else { if(next!=null) { if(traceEnabled) LOGGER.log(Level.FINER, "{0} {1}.processRequest({2})", new Object[]{getName(), next, packet != null ? "Packet@"+Integer.toHexString(packet.hashCode()) : "null"}); na = next.processRequest(packet); last = next; } else { if(contsSize==0 || abortResponse) { // nothing else to execute. we are done. contsSize = 0; return false; } last = popCont(); if(traceEnabled) LOGGER.log(Level.FINER, "{0} {1}.processResponse({2})", new Object[]{getName(), last, packet != null ? "Packet@"+Integer.toHexString(packet.hashCode()) : "null"}); na = last.processResponse(packet); } } if (traceEnabled) LOGGER.log(Level.FINER, "{0} {1} returned with {2}", new Object[]{getName(), last, na}); // If resume is called before suspend, then make sure // resume(Packet) is not lost if (na.kind != NextAction.SUSPEND) { // preserve in-flight packet so that processException may inspect if (na.kind != NextAction.THROW && na.kind != NextAction.THROW_ABORT_RESPONSE) packet = na.packet; throwable = na.throwable; } switch(na.kind) { case NextAction.INVOKE: case NextAction.INVOKE_ASYNC: pushCont(last); // fall through next case NextAction.INVOKE_AND_FORGET: next = na.next; if (na.kind == NextAction.INVOKE_ASYNC && startedSync) { // Break out here return false; } break; case NextAction.THROW_ABORT_RESPONSE: case NextAction.ABORT_RESPONSE: abortResponse = true; if (isTraceEnabled()) { LOGGER.log(Level.FINE, "Fiber {0} is aborting a response due to exception: {1}", new Object[]{this, na.throwable}); } case NextAction.RETURN: case NextAction.THROW: next = null; break; case NextAction.SUSPEND: if (next != null) { // Only store the 'last' tube when we're processing // a request, since conts array is for processResponse pushCont(last); } next = na.next; if(suspend(isRequireUnlock, na.onExitRunnable)) return true; // explicitly exiting control loop break; default: throw new AssertionError(); } } catch (RuntimeException | Error t) { if (traceEnabled) LOGGER.log(Level.FINER, getName() + " Caught " + t + ". Start stack unwinding", t); throwable = t; } dumpFiberContext("After tube execution"); } // there's nothing we can execute right away. // we'll be back when this fiber is resumed. } finally { CURRENT_FIBER.set(old); } return false; } private void pushCont(Tube tube) { conts[contsSize++] = tube; // expand if needed int len = conts.length; if (contsSize == len) { Tube[] newBuf = new Tube[len * 2]; System.arraycopy(conts, 0, newBuf, 0, len); conts = newBuf; } } private Tube popCont() { return conts[--contsSize]; } private Tube peekCont() { int index = contsSize - 1; if (index >= 0 && index < conts.length) { return conts[index]; } else { return null; } } /** * Only to be used by Tubes that manipulate the Fiber to create alternate flows * @since 2.2.6 */ public void resetCont(Tube[] conts, int contsSize) { this.conts = conts; this.contsSize = contsSize; } /** * Returns true if the fiber is ready to execute. */ private boolean isReady(List originalInterceptors) { if (synchronous) { while (suspendedCount == 1) try { if (isTraceEnabled()) { LOGGER.log(Level.FINE, "{0} is blocking thread {1}", new Object[]{getName(), Thread.currentThread().getName()}); } condition.await(); // the synchronized block is the whole runSync method. } catch (InterruptedException e) { // remember that we are interrupted, but don't respond to it // right away. This behavior is in line with what happens // when you are actually running the whole thing synchronously. interrupted = true; } synchronized(this) { return interceptors == originalInterceptors; } } else { if (suspendedCount>0) return false; synchronized(this) { return interceptors == originalInterceptors; } } } private String getName() { return "engine-" + owner.id + "fiber-" + id; } @Override public String toString() { return getName(); } /** * Gets the current {@link Packet} associated with this fiber. *
*
* This method returns null if no packet has been associated with the fiber yet. */ public @Nullable Packet getPacket() { return packet; } /** * Returns completion callback associated with this Fiber * @return Completion callback * @since 2.2.6 */ public CompletionCallback getCompletionCallback() { return completionCallback; } /** * Updates completion callback associated with this Fiber * @param completionCallback Completion callback * @since 2.2.6 */ public void setCompletionCallback(CompletionCallback completionCallback) { this.completionCallback = completionCallback; } /** * (ADVANCED) Returns true if the current fiber is being executed synchronously. *
*
* Fiber may run synchronously for various reasons. Perhaps this is * on client side and application has invoked a synchronous method call. * Perhaps this is on server side and we have deployed on a synchronous * transport (like servlet.) *
*
* When a fiber is run synchronously (IOW by {@link #runSync(Tube, Packet)}), * further invocations to {@link #runSync(Tube, Packet)} can be done * without degrading the performance. *
*
* So this value can be used as a further optimization hint for * advanced {@link Tube}s to choose the best strategy to invoke * the next {@link Tube}. For example, a tube may want to install * a {@link FiberContextSwitchInterceptor} if running async, yet * it might find it faster to do {@link #runSync(Tube, Packet)} * if it's already running synchronously. */ public static boolean isSynchronous() { return current().synchronous; } /** * Returns true if the current Fiber on the current thread was started * synchronously. Note, this is not strictly the same as being synchronous * because the assumption is that the Fiber will ultimately be dispatched * asynchronously, possibly have a completion callback associated with it, etc. * Note, the 'startedSync' flag is cleared once the current Fiber is * converted to running asynchronously. * @since 2.2.6 */ public boolean isStartedSync() { return startedSync; } /** * Gets the current fiber that's running. *
*
* This works like {@link Thread#currentThread()}. * This method only works when invoked from {@link Tube}. */ public static @NotNull @SuppressWarnings({"null", "ConstantConditions"}) Fiber current() { Fiber fiber = CURRENT_FIBER.get(); if (fiber == null) throw new IllegalStateException("Can be only used from fibers"); return fiber; } /** * Gets the current fiber that's running, if set. */ public static Fiber getCurrentIfSet() { return CURRENT_FIBER.get(); } private static final ThreadLocal CURRENT_FIBER = new ThreadLocal<>(); /** * Used to allocate unique number for each fiber. */ private static final AtomicInteger iotaGen = new AtomicInteger(); private static boolean isTraceEnabled() { return LOGGER.isLoggable(Level.FINE); } private static final Logger LOGGER = Logger.getLogger(Fiber.class.getName()); private static final ReentrantLock serializedExecutionLock = new ReentrantLock(); /** * Set this boolean to true to execute fibers sequentially one by one. * See class javadoc. */ public static volatile boolean serializeExecution = Boolean.getBoolean(Fiber.class.getName() + ".serialize"); private final Set components = new CopyOnWriteArraySet<>(); @Override public S getSPI(Class spiType) { for (Component c : components) { S spi = c.getSPI(spiType); if (spi != null) { return spi; } } return null; } @Override public Set getComponents() { return components; } }