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

org.jvnet.hk2.component.internal.runlevel.DefaultRunLevelService Maven / Gradle / Ivy

There is a newer version: 1.6.9
Show newest version
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2007-2011 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package org.jvnet.hk2.component.internal.runlevel;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.annotation.PostConstruct;

import org.jvnet.hk2.annotations.RunLevel;
import org.jvnet.hk2.component.ComponentException;
import org.jvnet.hk2.component.Enableable;
import org.jvnet.hk2.component.Habitat;
import org.jvnet.hk2.component.HabitatListener;
import org.jvnet.hk2.component.Inhabitant;
import org.jvnet.hk2.component.InhabitantActivator;
import org.jvnet.hk2.component.InhabitantListener;
import org.jvnet.hk2.component.InhabitantSorter;
import org.jvnet.hk2.component.RunLevelListener;
import org.jvnet.hk2.component.RunLevelService;
import org.jvnet.hk2.component.RunLevelState;
import org.jvnet.hk2.component.ServiceContext;

import com.sun.hk2.component.AbstractInhabitantImpl;
import com.sun.hk2.component.ClassLoaderHolder;
import com.sun.hk2.component.RunLevelInhabitant;

/**
 * The default {@link RunLevelService} implementation for Hk2.  See the 
 * {@link RunLevelService} javadoc for general details regarding this service.
 * 
 * Here is a brief example of the behavior of this service:
* * Imagine services ServiceA, ServiceB, and ServiceC are all in the same RunLevel X and the * dependencies are ServiceA -> ServiceB -> ServiceC: *

* *  @RunLevel(X)
*  @Service
* public class ServiceA {
*  @Inject ServiceB b;
* }
*
*  @RunLevel(X)
*  @Service
* public class ServiceB {
*  @Inject ServiceC c;
* }
*
*  @RunLevel(X)
*  @Service
* public class ServiceC {
* }
*
*

* When the DefaultRunLevelService is asked to proceedTo(X), the * expected start order is: ServiceC, ServiceB, ServiceA, and the expected shutdown * order is: ServiceA, ServiceB, ServiceC *

* RunLevel-annotated services correspond to {@link RunLevelInhabitant}'s and they * hook into the {@link PostConstruct} activation sequence to record the activation order * of inhabitants within each RunLevel. *

* Note that no model of dependencies between services are kept in the habitat to * make the implementation work. Any inhabitant in RunLevel X is arbitrarily picked * to start with upon activation, and {@link Inhabitant#get()} is issued. *

* Consider the cases of possible activation orderings: *

* Case 1: A, B, then C by RLS. get ServiceA (called by RLS) Start ServiceA: get * ServiceB Start ServiceB: get ServiceC Start ServiceC wire ServiceC * PostConstruct ServiceC wire ServiceB PostConstruct ServiceB wire ServiceA * PostConstruct ServiceA get ServiceB (called by RLS) get ServiceC (called by * RLS) *

* Case 2: B, C, then A by RLS. get ServiceB (called by RLS) Start ServiceB: get * ServiceC Start ServiceC wire ServiceC PostConstruct ServiceC wire ServiceB * PostConstruct ServiceB get ServiceC (called by RLS) get ServiceA (called by * RLS) Start ServiceA: get ServiceB wire ServiceA PostConstruct ServiceA *

* Case 3: B, A, then C by RLS. get ServiceB (called by RLS) Start ServiceB: get * ServiceC Start ServiceC wire ServiceC PostConstruct ServiceC wire ServiceB * PostConstruct ServiceB get ServiceA (called by RLS) Start ServiceA: get * ServiceB wire ServiceA PostConstruct ServiceA get ServiceC (called by RLS) *

* Case 4: C, B, then A by RLS. get ServiceC (called by RLS) Start ServiceC: * wire ServiceC PostConstruct ServiceC get ServiceB (called by RLS) Start * ServiceB: get ServiceC wire ServiceB PostConstruct ServiceB get ServiceA * (called by RLS) Start ServiceA: get ServiceB wire ServiceA PostConstruct * ServiceA get ServiceA (called by RLS) *

* You can see that the order is always correct without needing to keep the model * of dependencies. *

* ~~~ *

* Note that the implementation performs some level of constraint checking * during injection. For example, *

* - It is an error to have a RunLevel-annotated service at RunLevel X to depend * on (i.e., be injected with) a RunLevel-annotated service at RunLevel Y when Y * > X. *

* - It is an error to have a non-RunLevel-annotated service to depend on a * RunLevel-annotated service at any RunLevel. *

* Note that the implementation does not handle Holder and Collection injection * constraint validations. *

* ~~~ *

* The implementation will automatically proceedTo(-1) after the habitat has * been initialized. The value of "-1" is symbolic of the kernel run level. *

* Note that all RunLevel values less than -1 will be ignored. *

* ~~~ *

* The implementation is written to support two modes of operation, asynchronous * / threaded, and synchronous / single threaded. The DefaultRunLevelService * implementation mode is pre-configured to be synchronous. The * DefaultRunLevelService is thread safe. *

* In the synchronous mode, calls can be made to proceedTo() to interrupt * processing of any currently executing proceedTo() operation. This might occur: * in another thread, in the {@link RunLevelListener} handlers, or in a * {@link RunLevel} annotated service's {@link PostConstruct} method call. *

* Note, however, that even in synchronous mode the proceedTo() operation may exhibit * asynchronous behavior. This is the case when the caller has two threads calling * proceedTo(), where the second thread is canceling the operation of the first (perhaps * due to timeout of a service's PostConstruct, etc.). In this case, an interrupt will * be sent to the first running thread to cancel the previous operation, and proceedTo * the run level from the second thread's request. This presumes that the first thread * is capable of being interrupted. In such a situation, the second proceedTo() call * returns immediately and the first proceedTo() is interrupted to continue to the * new runLevel requested from the second thread's interrupt. *

* For this reason, it is strongly advised that {@link InterruptedException} is not * swallowed by services that can be driven by the DefaultRunLevelService in * synchronous mode. *

* proceedTo invocations from a {@link PostConstruct} callback are discouraged. Consider * using {@link RunLevelListener} instead. *

* Important Note:
* The proceedTo() method will throw unchecked exceptions of type * {@link DefaultRunLevelService$Interrupt} if it detects that it is being called * reentrantly in synchronous mode. Callers should be careful NOT to swallow exceptions * of this type as shown in the following example: *

* * try {
*  rls.proceedTo(x);
* } catch (Exception e) {
*  // swallow exception
* } *
*

* ~~~ *

* All calls to the {@Link RunLevelListener} happens synchronously on the * same thread that caused the Inhabitant to be activated. Therefore, implementors * of this interface should be careful and avoid calling long operations. *

* ~~~ *

* This service implements contracts {@link InhabitantSorter} as well as * {@link InhabitantActivator}. The implementation will first attempt to find a * habitat resident singleton for each of these contracts respectively, and failing to find an * implementation will handle each itself. This lookup occurs iteratively just prior to a * run level progression event. Note, however, that {@link InhabitantSorter} is only used * as part of activations since the {@link Recorder} is chiefly responsible for ensuring * release of the inhabitants occur in a consistent order. See earlier notes on this subject. * * @see RunLevelService * * @author Jeff Trent * * @since 3.1 */ public class DefaultRunLevelService implements RunLevelService, Enableable, RunLevelState, InhabitantListener, HabitatListener, InhabitantSorter, InhabitantActivator { // the initial run level public static final int INITIAL_RUNLEVEL = -2; // the "kernel" run level public static final int KERNEL_RUNLEVEL = -1; // the default name public static final String NAME = "default"; // the default mode - sync or async. static final boolean ASYNC_ENABLED = false; private static final Logger logger = Logger.getLogger(DefaultRunLevelService.class.getName()); private final Object lock = new Object(); // the async mode for this instance. // if enabled, then all work is performed using a private // executor. If disabled, then almost all of the work // occurs on the calling thread to proceedTo(). Almost all // because in the event a thread calls proceedTo() while // another thread is already executing a proceedTo() operation, // the the interrupting thread will return immediately and // the pre-existing executing thread is interrupted to go to // the new run level. private final boolean asyncMode; // the private executor service if this instance is using // async mode. private final ExecutorService exec; // the name for this instance, defaulting to NAME protected final String name; // the target environment, defaulting to Void.class private final Class targetEnv; // the habitat for this instance of the RunLevelService private final Habitat habitat; // used primarily for testing purposes - can ignore private RunLevelState delegate; // the current run level (the last one successfully achieved) private Integer current; // the set of recorders, one per runlevel (used as necessary, cleared when shutdown) private final HashMap recorders; // the "active" proceedTo worker private Worker worker; // @see Enableable private boolean enabled = true; // used for eventing an {@link RunLevelListener}s private enum ListenerEvent { PROGRESS, CANCEL, ERROR, } public DefaultRunLevelService(Habitat habitat) { this(habitat, ASYNC_ENABLED, null, null, new LinkedHashMap()); } protected DefaultRunLevelService(Habitat habitat, boolean async, String name, Class targetEnv, HashMap recorders) { this.habitat = habitat; assert (null != habitat); this.asyncMode = async; if (asyncMode) { // we can't use a singleThreadExecutor because a thread could become "stuck" exec = Executors.newCachedThreadPool(new ThreadFactory() { @Override public Thread newThread(Runnable runnable) { Thread activeThread = new RunLevelServiceThread(runnable); synchronized (lock) { logger.log(Level.FINE, "new thread: {0}", activeThread); } return activeThread; } }); } else { this.exec = null; } this.name = (null == name) ? NAME : name; this.targetEnv = (null == targetEnv) ? Void.class : targetEnv; this.recorders = recorders; // subscribe to events in the habitat since we cannot rely on PostConstruct. // This is because the complete habitat may not be initialized at the time of // our initialization. habitat.addHabitatListener(this); } @Override public String toString() { return getClass().getSimpleName() + "-" + System.identityHashCode(this) + "(" + getDescription(false) + ", del: " + delegate + ")"; } public String getDescription(boolean extended) { StringBuilder b = new StringBuilder(); b.append("curr=").append(getCurrentRunLevel()).append(", "); b.append("act=").append(getActivatingRunLevel()).append(", "); b.append("plan=").append(getPlannedRunLevel()).append(", "); b.append("env=").append(getEnvironment()).append(", "); if (extended) { b.append("thrd=").append(Thread.currentThread()); } return b.toString(); } @SuppressWarnings("unused") private void setDelegate(RunLevelState stateProvider) { assert (this != stateProvider); assert (getEnvironment() == stateProvider.getEnvironment()); this.delegate = stateProvider; } public String getName() { return name; } @Override public RunLevelState getState() { return (null == delegate) ? this : delegate; } @SuppressWarnings("unchecked") @Override public Class getEnvironment() { return (null == delegate) ? targetEnv : delegate.getEnvironment(); } @Override public Integer getCurrentRunLevel() { return (null == delegate) ? current : delegate.getCurrentRunLevel(); } @Override public Integer getPlannedRunLevel() { if (null == delegate) { synchronized (lock) { return (null == worker) ? null : worker.getPlannedRunLevel(); } } else { return delegate.getPlannedRunLevel(); } } public Integer getActivatingRunLevel() { synchronized (lock) { return (null == worker) ? null : worker.getActivatingRunLevel(); } } @Override public void enable(boolean enabled) throws IllegalStateException { this.enabled = enabled; } @Override public boolean isEnabled() { return enabled; } /** * Returns true if the RunLevel for the given inhabitant in question * should be processed by this RunLevelService instance. * * @param i the inhabitant * @param rl the inhabitan'ts runLevel * @param activeRunLevel the current runLevel * * @return */ protected boolean accept(Inhabitant i, RunLevel rl, int activeRunLevel) { return (rl.value() == activeRunLevel && rl.environment() == targetEnv); } /** * This is needed in the scenario where the habitat initially didn't * have an instance of this RunLevelService (or derivative) and then * later on in time, after all {@link RunLevelInhabitant}'s became defined, * this instance was introduced. In the event that the RunLevelInhabitant's * have not yet been bound, this will latently bind them now. */ private void checkBinding(RunLevelInhabitant rli) { RunLevelState state = rli.getState(); if (state != this && RunLevelServiceStub.class.isInstance(state)) { RunLevelService delegate = ((RunLevelServiceStub)state).getDelegate(); if (null != delegate) { assert(this == delegate); } else { ((RunLevelServiceStub)state).activate(this); } } } private boolean isCancelled(Worker worker) { synchronized (lock) { return (this.worker != worker); } } /** * Called after the proceedTo work is finished. This is * akin to a suicide act. * * @param worker the worker that was performing the work */ private void finished(Worker worker) { synchronized (lock) { // ensure that the worker is the "active" one and not some zombie back to life if (!isCancelled(worker)) { // it was the "trusted" worker this.worker = null; } } synchronized (this) { notifyAll(); } } private void setCurrent(Worker worker, Integer current) { synchronized (lock) { // ensure that the worker is the "active" one and not some zombie back to life if (isCancelled(worker)) { return; } else { this.current = current; } } // notify listeners that we progressed event(worker, ListenerEvent.PROGRESS, null, null); } protected List getRecordersToRelease( HashMap list, int runLevel) { List qualifying = new ArrayList(); synchronized (lock) { for (Entry entry : recorders.entrySet()) { int entryKey = entry.getKey(); if (entryKey >= runLevel) { qualifying.add(entry.getKey()); } } } // return in order of highest to lowest Collections.sort(qualifying); Collections.reverse(qualifying); return qualifying; } protected void event(Worker worker, ListenerEvent event, ServiceContext context, Throwable error) { event(worker, event, context, error, false); } protected void event(Worker worker, ListenerEvent event, ServiceContext context, Throwable error, boolean isHardInterrupt) { logger.log(Level.FINE, "event {0} - " + getDescription(true), event); if (isCancelled(worker)) { logger.log(Level.FINE, "Ignoring this notification!"); } else { Interrupt lastInterrupt = null; Collection activeListeners = habitat.getAllByContract(RunLevelListener.class); for (RunLevelListener listener : activeListeners) { try { if (ListenerEvent.PROGRESS == event) { listener.onProgress(this); } else if (ListenerEvent.CANCEL == event) { listener.onCancelled(this, context, current, isHardInterrupt); } else { listener.onError(this, context, error, true); } } catch (Interrupt interrupt) { lastInterrupt = interrupt; } catch (Exception e) { // don't percolate the exception since it may negatively impact processing logger.log(Level.WARNING, "swallowing exception - " + getDescription(true), new ComponentException(e)); } } if (null != lastInterrupt) { throw lastInterrupt; } else { if (null != error) { logger.log(Level.FINE, "swallowing exception - " + context, new ComponentException(error)); } } } } @Override public boolean inhabitantChanged(InhabitantListener.EventType eventType, Inhabitant inhabitant) { if (InhabitantListener.class.isInstance(delegate)) { return InhabitantListener.class.cast(delegate).inhabitantChanged( eventType, inhabitant); } AbstractInhabitantImpl ai = AbstractInhabitantImpl.class.cast(inhabitant); RunLevel rl = ai.getAnnotation(RunLevel.class); Integer activeRunLevel = (null == rl) ? null : rl.value(); if (null != activeRunLevel) { // forward to the active recorder? if (InhabitantListener.EventType.INHABITANT_ACTIVATED == eventType) { Recorder activeRecorder; synchronized (lock) { activeRecorder = recorders.get(activeRunLevel); if (null == activeRecorder) { activeRecorder = new Recorder(activeRunLevel, getEnvironment()); recorders.put(activeRunLevel, activeRecorder); } } if (null != activeRecorder) { activeRecorder.inhabitantChanged(eventType, inhabitant); } } } // we always want to maintain our subscription return true; } /** * Once habitat is initialized we can proceed to boot through to kernel level (-1) */ @Override public boolean inhabitantChanged(HabitatListener.EventType eventType, Habitat habitat, Inhabitant inhabitant) { if (org.jvnet.hk2.component.HabitatListener.EventType.HABITAT_INITIALIZED == eventType) { if (isEnabled()) { proceedTo(KERNEL_RUNLEVEL); } } return !habitat.isInitialized(); } @Override public boolean inhabitantIndexChanged(HabitatListener.EventType eventType, Habitat habitat, Inhabitant inhabitant, String index, String name, Object service) { return true; } @Override public void proceedTo(int runLevel) { proceedTo(runLevel, false); } @Override public void interrupt() { proceedTo(null, true); } @Override public void interrupt(int runLevel) { proceedTo(runLevel, true); } protected void proceedTo(Integer runLevel, boolean isHardInterrupt) { if (!isEnabled()) { throw new IllegalStateException("disabled state"); } if (null != runLevel && runLevel < KERNEL_RUNLEVEL) { throw new IllegalArgumentException(); } // see if we can interrupt first Worker worker = this.worker; if (null != worker) { if (worker.interrupt(isHardInterrupt, runLevel)) { return; } } if (null != runLevel) { // if we are here then we interrupt isn't enough and we must create a new worker synchronized (lock) { this.worker = worker = (asyncMode) ? new AsyncProceedToOp(runLevel) : new SyncProceedToOp(runLevel); } worker.proceedTo(runLevel); } } /** * Called when we are responsible for handling the {@link InhabitantSorter} work. * * The implementation returns the inhabitants argument as-is . */ @Override public List> sort(List> inhabitants) { return inhabitants; } /** * Called when we are responsible for handling the {@link InhabitantActivator} work. */ @SuppressWarnings("unchecked") @Override public void activate(Inhabitant inhabitant) { inhabitant.get(); } /** * Called when we are responsible for handling the {@link InhabitantActivator} work. */ @SuppressWarnings("unchecked") @Override public void deactivate(Inhabitant inhabitant) { inhabitant.release(); } /** * Obtains the "best" InhabitantSorter, first looking up out of * the habitat any service registered under the same name as ourself, * then defaulting to the first one registered by type alone, followed * by ourself. * * @return an InhabitantActivator, defaulting to ourself */ protected InhabitantSorter getInhabitantSorter() { InhabitantSorter is = habitat.getComponent(InhabitantSorter.class, getName()); if (null == is) { is = habitat.getByContract(InhabitantSorter.class); } return (null == is) ? this : is; } /** * Obtains the "best" InhabitantActivator, first looking up out of * the habitat any service registered under the same name as ourself, * then defaulting to the first one registered by type alone, followed * by ourself. * * @return an InhabitantActivator, defaulting to ourself */ protected InhabitantActivator getInhabitantActivator() { InhabitantActivator ia = habitat.getComponent(InhabitantActivator.class, getName()); if (null == ia) { ia = habitat.getByContract(InhabitantActivator.class); } return (null == ia) ? this : ia; } private abstract class Worker implements Runnable { // the target runLevel we want to achieve protected volatile Integer planned; // the active run level attempting to be activated private Integer activeRunLevel; // tracks the direction of any active proceedTo worker protected Boolean upSide; // records whether a cancel was actually an hard interrupt protected Boolean isHardInterrupt; protected Worker(int runLevel) { this.planned = runLevel; } public Integer getPlannedRunLevel() { return planned; } public Integer getActivatingRunLevel() { return activeRunLevel; } /** * Checks to see if this worker has been interrupted, and will abort * if it finds it has been. * * @param e any error encountered during the nested proceedTo operation; may be null * * @param i the inhabitant that was being activated / released during the operation; may be null * * @param isHard true when this is a "hard" interrupt originating from an interrupt() call, * false when it was from a "soft" interrupt involving a new proceedTo(), null when its * unknown altogether */ protected void checkInterrupt(Exception e, Inhabitant i, Boolean isHard) { if (null != e) { ServiceContext ctx = serviceContext(e, i); boolean isHardInterrupt = isHardInterrupt(isHard, e); if (isHardInterrupt) { event(this, ListenerEvent.CANCEL, ctx, e, isHardInterrupt); } else { event(this, ListenerEvent.ERROR, ctx, e, isHardInterrupt); } } } /** * Attempts to interrupt processing to go to a new runLevel. * * @param isHard if true, this was based on an explicit call to interrupt; * false otherwise. The latter is the case for a new proceedTo() causing * a cancel in order to proceedTo a new runLevel. * * @param runLevel optionally, the revised runLevel to proceedTo * * @return true, if its possible to go to the new runLevel; note that * implementation may handle the interrupt by other means (i.e., * throwing an InterruptException for the synchronous case) */ public abstract boolean interrupt(boolean isHard, Integer runLevel); /** * Called after initialization to run the proceedTo operation * * @param runLevel the runLevel to proceedTo */ public abstract void proceedTo(int runLevel); /** * Core control logic. */ @Override public void run() { logger.log(Level.FINE, "proceedTo({0}) - " + getDescription(true), planned); upSide = null; if (null != planned) { int current = (null == getCurrentRunLevel()) ? INITIAL_RUNLEVEL : getCurrentRunLevel(); if (planned > current) { upSide = true; int rl = current + 1; while (rl <= planned) { upActiveRecorder(rl); rl++; } } else if (planned < current) { upSide = false; // start things off we a notification of the current runLevel setCurrent(this, current); down(current); } else { // planned == current upSide = false; // force closure of any orphaned higher RunLevel services down(current+1); } } finished(this); } private void down(int start) { int rl = start; while (rl > planned) { downActiveRecorder(rl); rl--; } } private void upActiveRecorder(int runLevel) { activeRunLevel = runLevel; // create demand for RunLevel (runLevel) components activateRunLevel(); // don't set current until we've actually reached it setCurrent(this, runLevel); } private void activateRunLevel() { List> activations = new ArrayList>(); // TODO: we could cache this in top-level proceedTo() Collection> runLevelInhabitants = habitat.getAllInhabitantsByContract(RunLevel.class.getName()); for (Inhabitant i : runLevelInhabitants) { AbstractInhabitantImpl ai = AbstractInhabitantImpl.class.cast(i); RunLevel rl = ai.getAnnotation(RunLevel.class); if (accept(ai, rl, activeRunLevel)) { RunLevelInhabitant rli = RunLevelInhabitant.class.cast(ai); checkBinding(rli); activations.add(rli); } } if (!activations.isEmpty()) { InhabitantSorter is = getInhabitantSorter(); if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, "sorting {0}", activations); } activations = is.sort(activations); assert(null != activations); InhabitantActivator ia = getInhabitantActivator(); for (Inhabitant rli : activations) { if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, "activating {0} - " + getDescription(true), rli); } try { ia.activate(rli); // assert(rli.isInstantiated()); // an escape hatch if we've been interrupted in some way checkInterrupt(null, rli, null); } catch (Exception e) { checkInterrupt(e, rli, null); } } } } protected void downActiveRecorder(int runLevel) { activeRunLevel = runLevel; // release stuff deactiveRunLevel(runLevel); // don't set current until we've actually reached it setCurrent(this, activeRunLevel = runLevel-1); } private void deactiveRunLevel(int runLevel) { List downRecorders = getRecordersToRelease(recorders, runLevel); for (int current : downRecorders) { Recorder downRecorder; synchronized (lock) { downRecorder = recorders.get(current); } if (null != downRecorder) { // Causes release of the entire activationSet. Release occurs in the inverse // order of the recordings. So A->B->C will have startUp ordering be (C,B,A) // because of dependencies. The shutdown ordering will b (A,B,C). InhabitantActivator ia = getInhabitantActivator(); Inhabitant i; while (null != (i = downRecorder.pop())) { if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, "releasing {0} - " + getDescription(true), i); } try{ ia.deactivate(i); // assert(!i.isInstantiated()); checkInterrupt(null, i, null); } catch (Exception e) { checkInterrupt(e, i, null); } } } } } protected boolean isHardInterrupt(Boolean isHard, Throwable e) { if (null != isHard) { return isHard; } return (null == isHardInterrupt) ? false : isHardInterrupt; } } /** * Sync worker */ private class SyncProceedToOp extends Worker { // record the thread performing the operation private final Thread activeThread = Thread.currentThread(); // the next planned runLevel (after interrupt) protected Integer nextPlannedAfterInterrupt; // records whether a cancel event was issued private boolean cancelIssued; private SyncProceedToOp(int runLevel) { super(runLevel); } /** * Interrupts are always handled in the synchronous case * either by popping the stack for the reentrant call on * same thread, or by sending a cancel event to the * active thread doing the proceedTo() call. */ @Override public boolean interrupt(boolean isHard, Integer runLevel) { Thread ourThread = Thread.currentThread(); synchronized (lock) { Integer planned = getPlannedRunLevel(); if (!isHard && null != planned && planned == runLevel) { return true; // short circuit } nextPlannedAfterInterrupt = runLevel; if (ourThread == activeThread) { checkInterrupt(null, null, isHard); } else { // must interrupt another thread to do the new proceedTo(). // Note how this thread exhibits async behavior in this case. // The cancel notification will happen on the other thread logger.log(Level.FINE, "Interrupting thread {0} - " + getDescription(true), activeThread); this.isHardInterrupt = isHard; activeThread.interrupt(); } } return true; } @Override public void proceedTo(int runLevel) { synchronized (lock) { planned = runLevel; nextPlannedAfterInterrupt = null; cancelIssued = false; isHardInterrupt = null; } try { run(); } catch (Exception e) { handleInterruptException(e); } } @Override protected void checkInterrupt(Exception e, Inhabitant i, Boolean isHard) { synchronized (lock) { boolean cancelled = isCancelled(this); if (cancelled || null != nextPlannedAfterInterrupt) { if (!cancelled && canUpdateProceedTo(nextPlannedAfterInterrupt)) { planned = nextPlannedAfterInterrupt; nextPlannedAfterInterrupt = null; e = null; } else { // send cancel event, but only one time if (!cancelIssued) { cancelIssued = true; boolean wasHardInterrupt = isHardInterrupt(isHard, e); isHardInterrupt = null; event(this, ListenerEvent.CANCEL, serviceContext(e, i), null, wasHardInterrupt); } // pop stack to last proceedTo() throw new Interrupt(); } } } super.checkInterrupt(e, i, isHard); } private boolean canUpdateProceedTo(Integer proposed) { if (null != upSide) { Integer planned = getPlannedRunLevel(); Integer active = getActivatingRunLevel(); if (null != planned && null != active && null != proposed) { if (upSide && proposed > active) { return true; } else if (!upSide && proposed < active) { return true; } } } return false; } private void handleInterruptException(Exception e) { logger.log(Level.FINE, "Interrupt caught - " + getDescription(true), e); Thread currentThread = Thread.currentThread(); // we want to handle the new proceedTo if interrupted by another thread, // otherwise we fall out since we are not the owning thread. Integer next = null; if (activeThread == currentThread) { next = nextPlannedAfterInterrupt; } if (null != next) { proceedTo(next); } else { // RLS must continue / fall out logger.log(Level.FINE, "swallowing exception - " + getDescription(true), new ComponentException(e)); } } } /** * Async worker */ private class AsyncProceedToOp extends Worker implements Runnable { // record the future for the operation private Future activeFuture; private AsyncProceedToOp(int runLevel) { super(runLevel); } /** * Interrupts are never handled in the asynchronous case. * * Here, we just kill the worker, and expect a new one to form. */ @Override public boolean interrupt(boolean isHard, Integer runLevel) { boolean haveFuture; synchronized (lock) { haveFuture = (null != activeFuture); if (haveFuture) { // cancel previous, but down hit thread with interrupt activeFuture.cancel(false); activeFuture = null; } } if (haveFuture) { event(this, ListenerEvent.CANCEL, null, null, isHard); } return false; } @Override public void run() { super.run(); synchronized (lock) { activeFuture = null; isHardInterrupt = null; } } @Override public void proceedTo(int runLevel) { assert(null == activeFuture); activeFuture = exec.submit(this); } @Override protected void checkInterrupt(Exception e, Inhabitant i, Boolean isHard) { if (isCancelled(this)) { throw new Interrupt(); } super.checkInterrupt(e, i, isHard); } } protected ServiceContext serviceContext(Exception e, final Inhabitant i) { if (null == i) { return null; } ServiceContext ctx = null; if (e instanceof ComponentException) { ctx = ((ComponentException) e).getFailureContext(); } if (null == ctx) { ctx = new ServiceContext() { @Override public ClassLoader getClassLoader() { ClassLoader cl; if (ClassLoaderHolder.class.isInstance(i)) { cl = ((ClassLoaderHolder)i).getClassLoader(); } else { cl = i.getClass().getClassLoader(); } return cl; } @Override public Inhabitant getInhabitant() { return i; } @Override public String getType() { return i.typeName(); } @Override public String toString() { return i.toString(); } }; } return ctx; } private static class RunLevelServiceThread extends Thread { private RunLevelServiceThread(Runnable r) { super(r); setDaemon(true); setName(getClass().getSimpleName() + "-" + System.currentTimeMillis()); } } @SuppressWarnings("serial") public static class Interrupt extends RuntimeException { private Interrupt() {} } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy