/* * Copyright 2005-2006 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ package com.sun.xml.internal.ws.api.pipe; import com.sun.istack.internal.NotNull; import com.sun.istack.internal.Nullable; import com.sun.xml.internal.ws.api.message.Packet; import com.sun.xml.internal.ws.api.pipe.helper.AbstractFilterTubeImpl; import com.sun.xml.internal.ws.api.server.Adapter; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.logging.Logger; /** * User-level thread. Represents the execution of one request/response processing. * * <p> * 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. * * <p> * 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. * * <h2>Suspend/Resume</h2> * <p> * 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. * * <h2>Context-switch Interception</h2> * <p> * {@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. * * <h2>Context ClassLoader</h2> * <p> * 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. * * * <h2>Debugging Aid</h2> * <p> * 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. * * <p> * 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. * * <p> * 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 { /** * {@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. * * <p> * 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()}. * This happens when things happen in the following order: * * <ol> * <li>Tube decides that the fiber needs to be suspended to wait for the external event. * <li>Tube hooks up fiber with some external mechanism (like NIO channel selector) * <li>Tube returns with {@link NextAction#suspend()}. * <li>"External mechanism" becomes signal state and invokes {@link Fiber#resume(Packet)} * to wake up fiber * <li>{@link Fiber#doRun} invokes {@link Fiber#suspend()}. * </ol> * * <p> * Using int, this will work OK because {@link #suspendedCount} becomes -1 when * {@link #resume(Packet)} occurs before {@link #suspend()}. * * <p> * Increment and decrement is guarded by 'this' object. */ private volatile int suspendedCount = 0; /** * Is this fiber completed? */ private volatile boolean completed; /** * 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<FiberContextSwitchInterceptor> interceptors; /** * Not null when {@link #interceptors} is not null. */ private InterceptorHandler interceptorHandler; /** * This flag is set to true when a new interceptor is added. * * When that happens, we need to first exit the current interceptors * and then reenter them, so that the newly added interceptors start * taking effect. This flag is used to control that flow. */ private boolean needsToReenter; /** * Fiber's context {@link ClassLoader}. */ private @Nullable ClassLoader contextClassLoader; private @Nullable CompletionCallback completionCallback; /** * Set to true if this fiber is started asynchronously, to avoid * doubly-invoking completion code. */ private boolean started; /** * Callback to be invoked when a {@link Fiber} finishs execution. */ public interface CompletionCallback { /** * Indicates that the fiber has finished its execution. * * <p> * 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; if(isTraceEnabled()) { id = iotaGen.incrementAndGet(); LOGGER.fine(getName()+" created"); } else { id = -1; } // 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. * * <p> * 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 <tt>startPoint.processRequest()</tt>. * @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) { next = tubeline; this.packet = request; this.completionCallback = completionCallback; this.started = true; owner.addRunnable(this); } /** * Wakes up a suspended fiber. * * <p> * If a fiber was suspended from the {@link Tube#processRequest(Packet)} method, * then the execution will be resumed from the corresponding * {@link Tube#processResponse(Packet)} method with the specified response packet * as the parameter. * * <p> * If a fiber was suspended from the {@link Tube#processResponse(Packet)} method, * then the execution will be resumed from the next tube's * {@link Tube#processResponse(Packet)} method with the specified response packet * as the parameter. * * <p> * 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. */ public synchronized void resume(@NotNull Packet response) { if(isTraceEnabled()) LOGGER.fine(getName()+" resumed"); packet = response; if( --suspendedCount == 0 ) { if(synchronous) { notifyAll(); } else { owner.addRunnable(this); } } } /** * 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. */ private synchronized void suspend() { if(isTraceEnabled()) LOGGER.fine(getName()+" suspended"); suspendedCount++; } /** * Adds a new {@link FiberContextSwitchInterceptor} to this fiber. * * <p> * 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. * * <p> * So when the tubeline consists of X and Y, and when X installs an interceptor, * the order of execution will be as follows: * * <ol> * <li>X.processRequest() * <li>interceptor gets installed * <li>interceptor.execute() is invoked * <li>Y.processRequest() * </ol> */ public void addInterceptor(@NotNull FiberContextSwitchInterceptor interceptor) { if(interceptors ==null) { interceptors = new ArrayList<FiberContextSwitchInterceptor>(); interceptorHandler = new InterceptorHandler(); } interceptors.add(interceptor); needsToReenter = true; } /** * Removes a {@link FiberContextSwitchInterceptor} from this fiber. * * <p> * 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. * * * <p> * 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: * * <ol> * <li>Y.processResponse() (notice that this happens with interceptor.execute() in the callstack) * <li>interceptor gets uninstalled * <li>interceptor.execute() returns * <li>X.processResponse() * </ol> * * @return * true if the specified interceptor was removed. False if * the specified interceptor was not registered with this fiber to begin with. */ public boolean removeInterceptor(@NotNull FiberContextSwitchInterceptor interceptor) { if(interceptors !=null && interceptors.remove(interceptor)) { needsToReenter = true; return true; } 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 public void run() { assert !synchronous; next = doRun(next); completionCheck(); } /** * Runs a given {@link Tube} (and everything thereafter) synchronously. * * <p> * 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. * * <h3>Example:</h3> * <pre> * 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 * } * } * </pre> * * @param tubeline * The first tube of the tubeline that will act on the packet. * @param request * The request packet to be passed to <tt>startPoint.processRequest()</tt>. * @return * The response packet to the <tt>request</tt>. * * @see #start(Tube, Packet, CompletionCallback) */ public synchronized @NotNull Packet runSync(@NotNull Tube tubeline, @NotNull Packet request) { // save the current continuation, so that we return runSync() without executing them. final Tube[] oldCont = conts; final int oldContSize = contsSize; final boolean oldSynchronous = synchronous; if(oldContSize>0) { conts = new Tube[16]; contsSize=0; } try { synchronous = true; this.packet = request; doRun(tubeline); if(throwable!=null) { 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; if(interrupted) { Thread.currentThread().interrupt(); interrupted = false; } if(!started) completionCheck(); } } private synchronized void completionCheck() { if(contsSize==0) { if(isTraceEnabled()) LOGGER.fine(getName()+" completed"); completed = true; notifyAll(); if(completionCallback!=null) { if(throwable!=null) completionCallback.onCompletion(throwable); else completionCallback.onCompletion(packet); } } } ///** // * Blocks until the fiber completes. // */ //public synchronized void join() throws InterruptedException { // while(!completed) // wait(); //} /** * Invokes all registered {@link InterceptorHandler}s and then call into * {@link Fiber#__doRun(Tube)}. */ private class InterceptorHandler implements FiberContextSwitchInterceptor.Work<Tube,Tube> { /** * Index in {@link Fiber#interceptors} to invoke next. */ private int idx; /** * Initiate the interception, and eventually invokes {@link Fiber#__doRun(Tube)}. */ Tube invoke(Tube next) { idx=0; return execute(next); } public Tube execute(Tube next) { if(idx==interceptors.size()) { return __doRun(next); } else { FiberContextSwitchInterceptor interceptor = interceptors.get(idx++); return interceptor.execute(Fiber.this,next,this); } } } /** * Executes the fiber as much as possible. * * @param next * The next tube whose {@link Tube#processRequest(Packet)} is to be invoked. If null, * that means we'll just call {@link Tube#processResponse(Packet)} on the continuation. * * @return * If non-null, the next time execution resumes, it should resume from calling * the {@link Tube#processRequest(Packet)}. Otherwise it means just finishing up * the continuation. */ @SuppressWarnings({"LoopStatementThatDoesntLoop"}) // IntelliJ reports this bogus error private Tube doRun(Tube next) { Thread currentThread = Thread.currentThread(); if(isTraceEnabled()) LOGGER.fine(getName()+" running by "+currentThread.getName()); if(serializeExecution) { serializedExecutionLock.lock(); try { return _doRun(next); } finally { serializedExecutionLock.unlock(); } } else { return _doRun(next); } } private Tube _doRun(Tube next) { Thread currentThread = Thread.currentThread(); ClassLoader old = currentThread.getContextClassLoader(); currentThread.setContextClassLoader(contextClassLoader); try { do { needsToReenter = false; // if interceptors are set, go through the interceptors. if(interceptorHandler ==null) next = __doRun(next); else next = interceptorHandler.invoke(next); } while(needsToReenter); return next; } finally { currentThread.setContextClassLoader(old); } } /** * To be invoked from {@link #doRun(Tube)}. * * @see #doRun(Tube) */ private Tube __doRun(Tube next) { 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 { while(!isBlocking() && !needsToReenter) { try { NextAction na; Tube last; if(throwable!=null) { if(contsSize==0) { // nothing else to execute. we are done. return null; } last = popCont(); if(traceEnabled) LOGGER.finer(getName()+' '+last+".processException("+throwable+')'); na = last.processException(throwable); } else { if(next!=null) { if(traceEnabled) LOGGER.finer(getName()+' '+next+".processRequest("+packet+')'); na = next.processRequest(packet); last = next; } else { if(contsSize==0) { // nothing else to execute. we are done. return null; } last = popCont(); if(traceEnabled) LOGGER.finer(getName()+' '+last+".processResponse("+packet+')'); na = last.processResponse(packet); } } if(traceEnabled) LOGGER.finer(getName()+' '+last+" returned with "+na); // If resume is called before suspend, then make sure // resume(Packet) is not lost if (na.kind != NextAction.SUSPEND) { packet = na.packet; throwable = na.throwable; } switch(na.kind) { case NextAction.INVOKE: pushCont(last); // fall through next case NextAction.INVOKE_AND_FORGET: next = na.next; break; case NextAction.RETURN: case NextAction.THROW: next = null; break; case NextAction.SUSPEND: pushCont(last); next = null; suspend(); break; default: throw new AssertionError(); } } catch (RuntimeException t) { if(traceEnabled) LOGGER.log(Level.FINER,getName()+" Caught "+t+". Start stack unwinding",t); throwable = t; } catch (Error t) { if(traceEnabled) LOGGER.log(Level.FINER,getName()+" Caught "+t+". Start stack unwinding",t); throwable = t; } } // there's nothing we can execute right away. // we'll be back when this fiber is resumed. return next; } finally { CURRENT_FIBER.set(old); } } 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]; } /** * Returns true if the fiber needs to block its execution. */ // TODO: synchronization on synchronous case is wrong. private boolean isBlocking() { if(synchronous) { while(suspendedCount==1) try { if (isTraceEnabled()) { LOGGER.fine(getName()+" is blocking thread "+Thread.currentThread().getName()); } wait(); // 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; } return false; } else return suspendedCount==1; } private String getName() { return "engine-"+owner.id+"fiber-"+id; } public String toString() { return getName(); } /** * Gets the current {@link Packet} associated with this fiber. * * <p> * This method returns null if no packet has been associated with the fiber yet. */ public @Nullable Packet getPacket() { return packet; } /** * Returns true if this fiber is still running or suspended. */ public boolean isAlive() { return !completed; } /** * (ADVANCED) Returns true if the current fiber is being executed synchronously. * * <p> * 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.) * * <p> * 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. * * <p> * 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; } /** * Gets the current fiber that's running. * * <p> * This works like {@link Thread#currentThread()}. * This method only works when invoked from {@link Tube}. */ public static @NotNull Fiber current() { Fiber fiber = CURRENT_FIBER.get(); if(fiber==null) throw new IllegalStateException("Can be only used from fibers"); return fiber; } private static final ThreadLocal<Fiber> CURRENT_FIBER = new ThreadLocal<Fiber>(); /** * 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"); }