/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.sun.jini.phoenix; import com.sun.jini.proxy.BasicProxyTrustVerifier; import com.sun.jini.proxy.MarshalledWrapper; import com.sun.jini.reliableLog.LogHandler; import com.sun.jini.reliableLog.ReliableLog; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Serializable; import java.lang.Process; import java.net.URL; import java.rmi.*; import java.rmi.activation.*; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.rmi.server.UID; import java.security.CodeSource; import java.security.Permission; import java.security.PrivilegedExceptionAction; import java.security.ProtectionDomain; import java.text.MessageFormat; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; import javax.security.auth.Subject; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; import net.jini.config.Configuration; import net.jini.config.ConfigurationException; import net.jini.config.ConfigurationNotFoundException; import net.jini.config.ConfigurationProvider; import net.jini.config.NoSuchEntryException; import net.jini.core.constraint.MethodConstraints; import net.jini.core.constraint.RemoteMethodControl; import net.jini.export.Exporter; import net.jini.io.MarshalInputStream; import net.jini.io.MarshalOutputStream; import net.jini.io.MarshalledInstance; import net.jini.jeri.BasicILFactory; import net.jini.jeri.BasicJeriExporter; import net.jini.jeri.ServerEndpoint; import net.jini.jeri.tcp.TcpServerEndpoint; import net.jini.security.BasicProxyPreparer; import net.jini.security.ProxyPreparer; import net.jini.security.TrustVerifier; import net.jini.security.proxytrust.ServerProxyTrust; import net.jini.security.proxytrust.TrustEquivalence; /** * Phoenix main class. * * @author Sun Microsystems, Inc. * * @since 2.0 */ class Activation implements Serializable { private static final long serialVersionUID = -6825932652725866242L; private static final String PHOENIX = "com.sun.jini.phoenix"; private static ResourceBundle resources = null; private static Logger logger = Logger.getLogger(PHOENIX); private static final int PHOENIX_PORT = 1198; private static final Object logLock = new Object(); /** maps activation id uid to its respective group id */ private Map idTable = new HashMap(); /** maps group id to its GroupEntry groups */ private Map groupTable = new HashMap(); /** login context */ private transient LoginContext login; /** number of simultaneous group exec's */ private transient int groupSemaphore; /** counter for numbering groups */ private transient int groupCounter = 0; /** persistent store */ private transient ReliableLog log; /** number of log updates since last log snapshot */ private transient int numUpdates = 0; /** take log snapshot after this many updates */ private transient int snapshotInterval; /** the default java command for groups */ private transient String[] command; /** timeout on wait for child process to be created or destroyed */ private transient long groupTimeout; /** timeout on wait for unexport to succeed */ private transient long unexportTimeout; /** timeout on wait between unexport attempts */ private transient long unexportWait; /** ActivatorImpl instance */ private transient Activator activator; /** exporter for activator */ private transient Exporter activatorExporter; /** stub for activator */ private transient Activator activatorStub; /** SystemImpl instance */ private transient ActivationSystem system; /** exporter for system */ private transient Exporter systemExporter; /** stub for system */ private transient ActivationSystem systemStub; /** MonitorImpl instance */ private transient ActivationMonitor monitor; /** exporter for monitor */ private transient Exporter monitorExporter; /** stub for monitor */ private transient ActivationMonitor monitorStub; /** RegistryImpl instance */ private transient Registry registry; /** exporter for registry */ private transient Exporter registryExporter; /** stub for registry */ private transient Registry registryStub; /** MarshalledObject(ActivationGroupData) or null */ private transient MarshalledObject groupData; /** Location of ActivationGroupImpl or null */ private transient String groupLocation; /** preparer for ActivationInstantiators */ private transient ProxyPreparer groupPreparer; private transient GroupOutputHandler outputHandler; /** true if shutdown has been called */ private volatile boolean shuttingDown = false; /** Runtime shutdown hook */ private transient Thread shutdownHook; /** Non-null if phoenix was started by the service starter */ private transient PhoenixStarter starter; /** * Create an uninitialized instance of Activation that can be * populated with log data. This is only called when the initial * snapshot is taken during the first incarnation of phoenix. */ private Activation() {} private void init(ReliableLog log, LoginContext login, Configuration config, String[] configOptions, PhoenixStarter starter) throws Exception { this.log = log; this.login = login; this.starter = starter; groupSemaphore = getInt(config, "groupThrottle", 3); snapshotInterval = getInt(config, "persistenceSnapshotThreshold", 200); groupTimeout = getInt(config, "groupTimeout", 60000); unexportTimeout = getInt(config, "unexportTimeout", 60000); unexportWait = getInt(config, "unexportWait", 10); String[] opts = (String[]) config.getEntry( PHOENIX, "groupOptions", String[].class, new String[0]); command = new String[opts.length + 2]; command[0] = (System.getProperty("java.home") + File.separator + "bin" + File.separator + "java"); System.arraycopy(opts, 0, command, 1, opts.length); command[command.length - 1] = "com.sun.jini.phoenix.ActivationGroupInit"; shutdownHook = new ShutdownHook(); Runtime.getRuntime().addShutdownHook(shutdownHook); groupPreparer = getPreparer(config, "instantiatorPreparer"); groupData = new MarshalledObject(new ActivationGroupData((String[]) config.getEntry(PHOENIX, "groupConfig", String[].class, configOptions))); outputHandler = (GroupOutputHandler) config.getEntry( PHOENIX, "groupOutputHandler", GroupOutputHandler.class, new GroupOutputHandler() { public void handleOutput(ActivationGroupID id, ActivationGroupDesc desc, long incarnation, String groupName, InputStream out, InputStream err) { PipeWriter.plugTogetherPair( groupName, out, System.out, err, System.err); } }); groupLocation = (String) config.getEntry(PHOENIX, "groupLocation", String.class, getDefaultGroupLocation()); ActivationGroupID[] gids = (ActivationGroupID[]) groupTable.keySet().toArray( new ActivationGroupID[groupTable.size()]); activator = new ActivatorImpl(); ServerEndpoint se = TcpServerEndpoint.getInstance(PHOENIX_PORT); activatorExporter = getExporter(config, "activatorExporter", new BasicJeriExporter(se, new BasicILFactory(), false, true, PhoenixConstants.ACTIVATOR_UUID)); system = new SystemImpl(); systemExporter = getExporter(config, "systemExporter", new BasicJeriExporter(se, new SystemAccessILFactory(), false, true, PhoenixConstants.ACTIVATION_SYSTEM_UUID)); monitor = new MonitorImpl(); monitorExporter = getExporter(config, "monitorExporter", new BasicJeriExporter(se, new AccessILFactory())); registry = new RegistryImpl(); registryExporter = getExporter(config, "registryExporter", new RegistrySunExporter()); monitorStub = (ActivationMonitor) monitorExporter.export(monitor); synchronized (activatorExporter) { systemStub = (ActivationSystem) systemExporter.export(system); activatorStub = (Activator) activatorExporter.export(activator); } registryStub = (Registry) registryExporter.export(registry); logger.info(getTextResource("phoenix.daemon.started")); for (int i = gids.length; --i >= 0; ) { try { getGroupEntry(gids[i]).restartServices(); } catch (UnknownGroupException e) { } } } private static String getDefaultGroupLocation() { ProtectionDomain pd = Activation.class.getProtectionDomain(); CodeSource cs = pd.getCodeSource(); URL location = null; if (cs != null) { location = cs.getLocation(); } if (location != null) { String loc = location.toString(); if (loc.endsWith(".jar")) { return loc.substring(0, loc.length() - 4) + "-group.jar"; } } return null; } /** * Return a configuration for the specified options. */ private static Configuration getConfig(String[] configOptions, PhoenixStarter starter) throws ConfigurationException { try { return ConfigurationProvider.getInstance( configOptions, Activation.class.getClassLoader()); } catch (ConfigurationNotFoundException e) { if (starter == null) { bomb("phoenix.missing.config", Arrays.asList(configOptions).toString()); } throw e; } } /** * Return an int configuration entry. */ private static int getInt(Configuration config, String name, int defValue) throws ConfigurationException { return ((Integer) config.getEntry(PHOENIX, name, int.class, new Integer(defValue))).intValue(); } /** * Return the exporter with the specified name from the specified * configuration. */ private static Exporter getExporter(Configuration config, String name, Exporter defaultExporter) throws ConfigurationException { return (Exporter) config.getEntry(PHOENIX, name, Exporter.class, defaultExporter); } /** * Return a ProxyPreparer configuration entry. */ private static ProxyPreparer getPreparer(Configuration config, String name) throws ConfigurationException { return (ProxyPreparer) config.getEntry(PHOENIX, name, ProxyPreparer.class, new BasicProxyPreparer()); } class ActivatorImpl extends AbstractActivator implements ServerProxyTrust { ActivatorImpl() { } public MarshalledWrapper activate(ActivationID id, boolean force) throws ActivationException { UID uid = getUID(id); return getGroupEntry(uid).activate(uid, force); } public TrustVerifier getProxyVerifier() { return new ConstrainableAID.Verifier(activatorStub); } } class MonitorImpl extends AbstractMonitor implements ServerProxyTrust { MonitorImpl() { } public void inactiveObject(ActivationID id) throws UnknownObjectException { UID uid = getUID(id); getGroupEntry(uid).inactiveObject(uid); } public void activeObject(ActivationID id, MarshalledObject mobj) throws UnknownObjectException { UID uid = getUID(id); getGroupEntry(uid).activeObject(uid, mobj); } public void inactiveGroup(ActivationGroupID id, long incarnation) throws UnknownGroupException { getGroupEntry(id).inactiveGroup(incarnation, false); } public TrustVerifier getProxyVerifier() { return new BasicProxyTrustVerifier(monitorStub); } } class SystemImpl extends AbstractSystem implements ServerProxyTrust { SystemImpl() { } /** returns a ConstrainableAID */ public ActivationID registerObject(ActivationDesc desc) throws ActivationException { UID uid = new UID(); ActivationGroupID groupID = desc.getGroupID(); getGroupEntry(groupID).registerObject(uid, desc, true); synchronized (activatorExporter) { return getAID(uid); } } public void unregisterObject(ActivationID id) throws ActivationException { UID uid = getUID(id); getGroupEntry(getGroupID(uid)).unregisterObject(uid, true); } public ActivationGroupID registerGroup(ActivationGroupDesc desc) throws ActivationException { ActivationGroupID id = new ActivationGroupID(systemStub); synchronized (logLock) { addLogRecord(new LogRegisterGroup(id, desc)); GroupEntry entry = new GroupEntry(id, desc); synchronized (groupTable) { groupTable.put(id, entry); } } return id; } public ActivationMonitor activeGroup(ActivationGroupID id, ActivationInstantiator group, long incarnation) throws ActivationException, RemoteException { group = (ActivationInstantiator) groupPreparer.prepareProxy(group); getGroupEntry(id).activeGroup(group, incarnation); return monitorStub; } public void unregisterGroup(ActivationGroupID id) throws ActivationException { GroupEntry groupEntry; synchronized (groupTable) { groupEntry = getGroupEntry(id); groupTable.remove(id); } groupEntry.unregisterGroup(true); } public ActivationDesc setActivationDesc(ActivationID id, ActivationDesc desc) throws ActivationException { UID uid = getUID(id); if (!getGroupID(uid).equals(desc.getGroupID())) { throw new ActivationException( "ActivationDesc contains wrong group"); } return getGroupEntry(uid).setActivationDesc(uid, desc, true); } public ActivationGroupDesc setActivationGroupDesc( ActivationGroupID id, ActivationGroupDesc desc) throws ActivationException { return getGroupEntry(id).setActivationGroupDesc(id, desc, true); } public ActivationDesc getActivationDesc(ActivationID id) throws UnknownObjectException { UID uid = getUID(id); return getGroupEntry(uid).getActivationDesc(uid); } public ActivationGroupDesc getActivationGroupDesc(ActivationGroupID id) throws UnknownGroupException { return getGroupEntry(id).desc; } public void shutdown() { synchronized (Activation.this) { if (!shuttingDown) { shuttingDown = true; new Shutdown().start(); } } } public Map getActivationGroups() { synchronized (groupTable) { Map map = new HashMap(groupTable.size()); for (Iterator iter = groupTable.values().iterator(); iter.hasNext(); ) { GroupEntry entry = (GroupEntry) iter.next(); if (!entry.removed) { map.put(entry.groupID, entry.desc); } } return map; } } public Map getActivatableObjects(ActivationGroupID id) throws UnknownGroupException { synchronized (activatorExporter) { // to wait for it to be exported } return getGroupEntry(id).getActivatableObjects(); } public TrustVerifier getProxyVerifier() { return new BasicProxyTrustVerifier(systemStub); } } /** * A read-only registry containing a single entry for the system. */ class RegistryImpl extends AbstractRegistry { /** The name of the single entry */ private final String NAME = ActivationSystem.class.getName(); RegistryImpl() { } /** * Returns the single object if the specified name matches the single * name, otherwise throws NotBoundException. */ public Remote lookup(String name) throws NotBoundException { if (name.equals(NAME)) { return systemStub; } throw new NotBoundException(name); } /** Always throws SecurityException. */ public void bind(String name, Remote obj) { throw new SecurityException("read-only registry"); } /** Always throws SecurityException. */ public void unbind(String name) { throw new SecurityException("read-only registry"); } /** Always throws SecurityException. */ public void rebind(String name, Remote obj) { throw new SecurityException("read-only registry"); } /** Returns a list containing the single name. */ public String[] list() { return new String[]{NAME}; } } /** * If shutting down, throw an ActivationException. */ private void checkShutdown() throws ActivationException { if (shuttingDown) { throw new ActivationException( "activation system is shutting down"); } } /** * Thread to shutdown phoenix. */ private class Shutdown extends Thread { Shutdown() { super("Shutdown"); } public void run() { try { long stop = System.currentTimeMillis() + unexportTimeout; boolean force = false; while (!registryExporter.unexport(force) || !activatorExporter.unexport(force) || !systemExporter.unexport(force) || !monitorExporter.unexport(force)) { long rem = stop - System.currentTimeMillis(); if (rem <= 0) { force = true; } else { try { Thread.sleep(Math.min(rem, unexportWait)); } catch (InterruptedException e) { } } } // destroy all child processes (groups) GroupEntry[] groupEntries; synchronized (groupTable) { groupEntries = (GroupEntry[]) groupTable.values(). toArray(new GroupEntry[groupTable.size()]); } for (int i = 0; i < groupEntries.length; i++) { groupEntries[i].shutdown(); } Runtime.getRuntime().removeShutdownHook(shutdownHook); try { synchronized (logLock) { log.close(); } } catch (IOException e) { } try { if (login != null) { login.logout(); } } catch (LoginException e) { } } catch (Throwable t) { logger.log(Level.WARNING, "exception during shutdown", t); } finally { logger.info(getTextResource("phoenix.daemon.shutdown")); if (starter == null) { System.exit(0); } else { starter.unregister(); } } } } /** Thread to destroy children in the event of abnormal termination. */ private class ShutdownHook extends Thread { ShutdownHook() { super("Phoenix Shutdown Hook"); } public void run() { synchronized (Activation.this) { shuttingDown = true; } // destroy all child processes (groups) quickly synchronized (groupTable) { for (Iterator iter = groupTable.values().iterator(); iter.hasNext(); ) { ((GroupEntry) iter.next()).shutdownFast(); } } } } private ActivationID getAID(UID uid) { if (activatorStub instanceof RemoteMethodControl) { return new ConstrainableAID(activatorStub, uid); } else { return new AID(activatorStub, uid); } } private static UID getUID(ActivationID id) throws UnknownObjectException { Class c = id.getClass(); if (c == AID.class || c == ConstrainableAID.class) { return ((AID) id).getUID(); } throw new UnknownObjectException("object unknown"); } /** * Returns the groupID for a given id of an object in the group. * Throws UnknownObjectException if the object is not registered. */ private ActivationGroupID getGroupID(UID uid) throws UnknownObjectException { synchronized (idTable) { ActivationGroupID groupID = (ActivationGroupID) idTable.get(uid); if (groupID != null) { return groupID; } } throw new UnknownObjectException("object unknown"); } /** * Returns the group entry for the group id. Throws * UnknownGroupException if the group is not registered. */ private GroupEntry getGroupEntry(ActivationGroupID id) throws UnknownGroupException { if (id.getClass() == ActivationGroupID.class) { synchronized (groupTable) { GroupEntry entry = (GroupEntry) groupTable.get(id); if (entry != null && !entry.removed) { return entry; } } } throw new UnknownGroupException("group unknown"); } /** * Returns the group entry for the object's id. Throws * UnknownObjectException if the object is not registered. */ private GroupEntry getGroupEntry(UID uid) throws UnknownObjectException { ActivationGroupID gid = getGroupID(uid); synchronized (groupTable) { GroupEntry entry = (GroupEntry) groupTable.get(gid); if (entry != null) { return entry; } } throw new UnknownObjectException("object's group removed"); } /** * Container for group information: group's descriptor, group's * instantiator, flag to indicate pending group creation, and * table of the group's active objects. * * WARNING: GroupEntry objects should not be written into log file * updates. GroupEntrys are inner classes of Activation and they * can not be serialized independent of this class. If the * complete Activation system is written out as a log update, the * point of having updates is nullified. */ private class GroupEntry implements Serializable { private static final long serialVersionUID = 7222464070032993304L; private static final int MAX_TRIES = 2; private static final int NORMAL = 0; private static final int CREATING = 1; private static final int TERMINATE = 2; private static final int TERMINATING = 3; ActivationGroupDesc desc; ActivationGroupID groupID; long incarnation = 0; Map objects = new HashMap(11); HashSet restartSet = new HashSet(); transient ActivationInstantiator group = null; transient int status = NORMAL; transient long waitTime = 0; transient String groupName = null; transient Process child = null; transient boolean removed = false; transient Watchdog watchdog = null; GroupEntry(ActivationGroupID groupID, ActivationGroupDesc desc) { this.groupID = groupID; this.desc = desc; } void restartServices() { Iterator iter = null; synchronized (this) { if (restartSet.isEmpty()) { return; } /* * Clone the restartSet so the set does not have to be locked * during iteration. Locking the restartSet could cause * deadlock if an object we are restarting caused another * object in this group to be activated. */ iter = ((Set) restartSet.clone()).iterator(); } while (iter.hasNext()) { UID uid = (UID) iter.next(); try { activate(uid, true); } catch (Exception e) { if (shuttingDown) { return; } logger.log(Level.WARNING, "unable to restart service", e); } } } synchronized void activeGroup(ActivationInstantiator inst, long instIncarnation) throws ActivationException { if (group != null && group.equals(inst) && incarnation == instIncarnation) { return; } else if (child != null && status != CREATING) { throw new ActivationException("group not being created"); } else if (incarnation != instIncarnation) { throw new ActivationException("invalid incarnation"); } else if (group != null) { throw new ActivationException("group already active"); } group = inst; status = NORMAL; notifyAll(); } private void checkRemoved() throws UnknownGroupException { if (removed) { throw new UnknownGroupException("group removed"); } } private ObjectEntry getObjectEntry(UID uid) throws UnknownObjectException { if (removed) { throw new UnknownObjectException("object's group removed"); } ObjectEntry objEntry = (ObjectEntry) objects.get(uid); if (objEntry == null) { throw new UnknownObjectException("object unknown"); } return objEntry; } synchronized void registerObject(UID uid, ActivationDesc desc, boolean addRecord) throws ActivationException { checkRemoved(); synchronized (logLock) { if (addRecord) { addLogRecord(new LogRegisterObject(uid, desc)); } objects.put(uid, new ObjectEntry(desc)); if (desc.getRestartMode()) { restartSet.add(uid); } synchronized (idTable) { idTable.put(uid, groupID); } } } synchronized void unregisterObject(UID uid, boolean addRecord) throws ActivationException { ObjectEntry objEntry = getObjectEntry(uid); synchronized (logLock) { if (addRecord) { addLogRecord(new LogUnregisterObject(uid)); } objEntry.removed(); objects.remove(uid); if (objEntry.desc.getRestartMode()) { restartSet.remove(uid); } synchronized (idTable) { idTable.remove(uid); } } } synchronized Map getActivatableObjects() { Map map = new HashMap(objects.size()); for (Iterator iter = objects.entrySet().iterator(); iter.hasNext(); ) { Map.Entry ent = (Map.Entry) iter.next(); map.put(getAID((UID) ent.getKey()), ((ObjectEntry) ent.getValue()).desc); } return map; } synchronized void unregisterGroup(boolean addRecord) throws ActivationException { checkRemoved(); synchronized (logLock) { if (addRecord) { addLogRecord(new LogUnregisterGroup(groupID)); } removed = true; for (Iterator iter = objects.entrySet().iterator(); iter.hasNext(); ) { Map.Entry ent = (Map.Entry) iter.next(); UID uid = (UID) ent.getKey(); synchronized (idTable) { idTable.remove(uid); } ObjectEntry objEntry = (ObjectEntry) ent.getValue(); objEntry.removed(); } objects.clear(); restartSet.clear(); reset(); childGone(); } } synchronized ActivationDesc setActivationDesc(UID uid, ActivationDesc desc, boolean addRecord) throws ActivationException { ObjectEntry objEntry = getObjectEntry(uid); synchronized (logLock) { if (addRecord) { addLogRecord(new LogUpdateDesc(uid, desc)); } ActivationDesc oldDesc = objEntry.desc; objEntry.desc = desc; if (desc.getRestartMode()) { restartSet.add(uid); } else { restartSet.remove(uid); } return oldDesc; } } synchronized ActivationDesc getActivationDesc(UID uid) throws UnknownObjectException { return getObjectEntry(uid).desc; } synchronized ActivationGroupDesc setActivationGroupDesc( ActivationGroupID id, ActivationGroupDesc desc, boolean addRecord) throws ActivationException { checkRemoved(); synchronized (logLock) { if (addRecord) { addLogRecord(new LogUpdateGroupDesc(id, desc)); } ActivationGroupDesc oldDesc = this.desc; this.desc = desc; return oldDesc; } } synchronized void inactiveGroup(long incarnation, boolean failure) throws UnknownGroupException { checkRemoved(); if (this.incarnation != incarnation) { throw new UnknownGroupException("invalid incarnation"); } reset(); if (failure) { terminate(); } else if (child != null && status == NORMAL) { status = TERMINATE; watchdog.noRestart(); } } synchronized void activeObject(UID uid, MarshalledObject mobj) throws UnknownObjectException { getObjectEntry(uid).stub = new MarshalledWrapper(new MarshalledInstance(mobj)); } synchronized void inactiveObject(UID uid) throws UnknownObjectException { getObjectEntry(uid).reset(); } private void reset() { group = null; for (Iterator iter = objects.values().iterator(); iter.hasNext(); ) { ((ObjectEntry) iter.next()).reset(); } } private void childGone() { if (child != null) { child = null; watchdog.dispose(); watchdog = null; status = NORMAL; notifyAll(); } } private void terminate() { if (child != null && status != TERMINATING) { child.destroy(); status = TERMINATING; waitTime = System.currentTimeMillis() + groupTimeout; notifyAll(); } } private void await() { while (true) { switch (status) { case NORMAL: return; case TERMINATE: terminate(); case TERMINATING: try { child.exitValue(); } catch (IllegalThreadStateException e) { long now = System.currentTimeMillis(); if (waitTime > now) { try { wait(waitTime - now); } catch (InterruptedException ee) { } continue; } logger.log(Level.WARNING, "group did not terminate: {0}", groupName); } childGone(); return; case CREATING: try { wait(); } catch (InterruptedException e) { } } } } // no synchronization to avoid delay wrt getInstantiator void shutdownFast() { Process p = child; if (p != null) { p.destroy(); } } synchronized void shutdown() { reset(); terminate(); await(); } MarshalledWrapper activate(UID uid, boolean force) throws ActivationException { Exception detail = null; /* * Attempt to activate object and reattempt (several times) * if activation fails due to communication problems. */ for (int tries = MAX_TRIES; tries > 0; tries--) { ActivationInstantiator inst; long curIncarnation; // look up object to activate ObjectEntry objEntry; synchronized (this) { objEntry = getObjectEntry(uid); // if not forcing activation, return cached stub if (!force && objEntry.stub != null) { return objEntry.stub; } inst = getInstantiator(groupID); curIncarnation = incarnation; } boolean groupInactive = false; boolean failure = false; // activate object try { return objEntry.activate(uid, force, inst); } catch (NoSuchObjectException e) { groupInactive = true; detail = e; } catch (ConnectException e) { groupInactive = true; failure = true; detail = e; } catch (ConnectIOException e) { groupInactive = true; failure = true; detail = e; } catch (InactiveGroupException e) { groupInactive = true; detail = e; } catch (RemoteException e) { // REMIND: wait some here before continuing? if (detail == null) { detail = e; } } if (groupInactive) { // group has failed; mark inactive try { getGroupEntry(groupID).inactiveGroup(curIncarnation, failure); } catch (UnknownGroupException e) { // not a problem } } } /** * signal that object activation failed, nested exception * specifies what exception occurred when the object did not * activate */ throw new ActivationException("object activation failed after " + MAX_TRIES + " tries", detail); } /** * Returns the instantiator for the group specified by id and * entry. If the group is currently inactive, exec some * bootstrap code to create the group. */ private ActivationInstantiator getInstantiator(ActivationGroupID id) throws ActivationException { await(); if (group != null) { return group; } checkRemoved(); boolean acquired = false; try { groupName = Pstartgroup(); acquired = true; String[] argv = activationArgs(desc); if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "{0} exec {1}", new Object[]{groupName, Arrays.asList(argv)}); } try { child = Runtime.getRuntime().exec(argv); status = CREATING; ++incarnation; watchdog = new Watchdog(); watchdog.start(); synchronized (logLock) { addLogRecord( new LogGroupIncarnation(id, incarnation)); } outputHandler.handleOutput(id, desc, incarnation, groupName, child.getInputStream(), child.getErrorStream()); MarshalOutputStream out = new MarshalOutputStream(child.getOutputStream(), Collections.EMPTY_LIST); out.writeObject(id); ActivationGroupDesc gd = desc; if (gd.getClassName() == null) { MarshalledObject data = gd.getData(); if (data == null) { data = groupData; } String loc = gd.getLocation(); if (loc == null) { loc = groupLocation; } gd = new ActivationGroupDesc( "com.sun.jini.phoenix.ActivationGroupImpl", loc, data, gd.getPropertyOverrides(), gd.getCommandEnvironment()); } out.writeObject(gd); out.writeLong(incarnation); out.flush(); out.close(); } catch (Exception e) { terminate(); if (e instanceof ActivationException) { throw (ActivationException) e; } else { throw new ActivationException( "unable to create activation group", e); } } try { long now = System.currentTimeMillis(); long stop = now + groupTimeout; do { wait(stop - now); if (group != null) { return group; } now = System.currentTimeMillis(); // protect against premature return from wait } while (status == CREATING && now < stop); } catch (InterruptedException e) { } terminate(); throw new ActivationException( "timeout creating child process"); } finally { if (acquired) { Vstartgroup(); } } } /** * Waits for process termination and then restarts services. */ private class Watchdog extends Thread { private Process groupProcess = child; private long groupIncarnation = incarnation; private boolean canInterrupt = true; private boolean shouldQuit = false; private boolean shouldRestart = true; Watchdog() { super("Watchdog-" + groupName + "-" + incarnation); setDaemon(true); } public void run() { if (shouldQuit) { return; } /* * Wait for the group to crash or exit. */ try { groupProcess.waitFor(); } catch (InterruptedException exit) { return; } boolean restart = false; synchronized (GroupEntry.this) { if (shouldQuit) { return; } canInterrupt = false; interrupted(); // clear interrupt bit /* * Since the group crashed, we should * reset the entry before activating objects */ if (groupIncarnation == incarnation) { restart = shouldRestart && !shuttingDown; reset(); childGone(); } } /* * Activate those objects that require restarting * after a crash. */ if (restart) { restartServices(); } } /** * Marks this thread as one that is no longer needed. * If the thread is in a state in which it can be interrupted, * then the thread is interrupted. */ void dispose() { shouldQuit = true; if (canInterrupt) { interrupt(); } } /** * Marks this thread as no longer needing to restart objects. */ void noRestart() { shouldRestart = false; } } } private String[] activationArgs(ActivationGroupDesc desc) { ActivationGroupDesc.CommandEnvironment cmdenv; cmdenv = desc.getCommandEnvironment(); // argv is the literal command to exec List argv = new ArrayList(); // Command name/path argv.add((cmdenv != null && cmdenv.getCommandPath() != null) ? cmdenv.getCommandPath() : command[0]); // Group-specific command options if (cmdenv != null && cmdenv.getCommandOptions() != null) { argv.addAll(Arrays.asList(cmdenv.getCommandOptions())); } // Properties become -D parameters Properties props = desc.getPropertyOverrides(); if (props != null) { for (Enumeration p = props.propertyNames(); p.hasMoreElements(); ) { String name = (String) p.nextElement(); /* Note on quoting: it would be wrong * here, since argv will be passed to * Runtime.exec, which should not parse * arguments or split on whitespace. */ argv.add("-D" + name + "=" + props.getProperty(name)); } } // finally, phoenix-global command options and the classname int i; for (i = 1; i < command.length; i++) { argv.add(command[i]); } String[] realArgv = new String[argv.size()]; System.arraycopy(argv.toArray(), 0, realArgv, 0, realArgv.length); return (realArgv); } private class ObjectEntry implements Serializable { private static final long serialVersionUID = -808474359039620126L; /** descriptor for object */ ActivationDesc desc; /** the stub (if active) */ volatile transient MarshalledWrapper stub = null; volatile transient boolean removed = false; ObjectEntry(ActivationDesc desc) { this.desc = desc; } synchronized MarshalledWrapper activate(UID uid, boolean force, ActivationInstantiator inst) throws RemoteException, ActivationException { /* stub could be set to null by a concurrent group reset */ MarshalledWrapper nstub = stub; if (removed) { throw new UnknownObjectException("object removed"); } else if (!force && nstub != null) { return nstub; } MarshalledInstance marshalledProxy = new MarshalledInstance(inst.newInstance(getAID(uid), desc)); nstub = new MarshalledWrapper(marshalledProxy); stub = nstub; return nstub; } void reset() { stub = null; } void removed() { removed = true; } } /** * Adds a record to the activation log. If the number of updates * passes a predetermined threshold, record a snapshot before * adding the record to the log. */ private void addLogRecord(LogRecord rec) throws ActivationException { assert Thread.holdsLock(logLock); checkShutdown(); if (numUpdates >= snapshotInterval) { snapshot(); } try { log.update(rec, true); numUpdates++; } catch (Exception e) { logger.log(Level.WARNING, "log update throws", e); snapshot(); } } private void snapshot() throws ActivationException { assert Thread.holdsLock(logLock); try { log.snapshot(); numUpdates = 0; } catch (Exception e) { logger.log(Level.SEVERE, "log snapshot throws", e); try { system.shutdown(); } catch (RemoteException ignore) { // can't happen } throw new ActivationException("log snapshot failed", e); } } /** * Handler for the log that knows how to take the initial snapshot * and apply an update (a LogRecord) to the current state. */ private static class ActLogHandler extends LogHandler { private Activation state = null; ActLogHandler() { } public Activation getState() { return state; } public void snapshot(OutputStream out) throws Exception { if (state == null) { state = new Activation(); } MarshalOutputStream s = new MarshalOutputStream(out, Collections.EMPTY_LIST); s.writeObject(state); s.flush(); } public void recover(InputStream in) throws Exception { MarshalInputStream s = new MarshalInputStream(in, ActLogHandler.class.getClassLoader(), false, null, Collections.EMPTY_LIST); s.useCodebaseAnnotations(); state = (Activation) s.readObject(); } public void writeUpdate(OutputStream out, Object value) throws Exception { MarshalOutputStream s = new MarshalOutputStream(out, Collections.EMPTY_LIST); s.writeObject(value); s.flush(); } public void readUpdate(InputStream in) throws Exception { MarshalInputStream s = new MarshalInputStream(in, ActLogHandler.class.getClassLoader(), false, null, Collections.EMPTY_LIST); s.useCodebaseAnnotations(); applyUpdate(s.readObject()); } public void applyUpdate(Object update) throws Exception { ((LogRecord) update).apply(state); } } /** * Abstract class for all log records. The subclass contains * specific update information and implements the apply method * that applies the update information contained in the record * to the current state. */ private static abstract class LogRecord implements Serializable { private static final long serialVersionUID = 8395140512322687529L; abstract Object apply(Object state) throws Exception; } /** * Log record for registering an object. */ private static class LogRegisterObject extends LogRecord { private static final long serialVersionUID = -6280336276146085143L; private UID uid; private ActivationDesc desc; LogRegisterObject(UID uid, ActivationDesc desc) { this.uid = uid; this.desc = desc; } Object apply(Object state) { try { ((Activation) state).getGroupEntry(desc.getGroupID()). registerObject(uid, desc, false); } catch (Exception e) { logger.log(Level.WARNING, "log recovery throws", e); } return state; } } /** * Log record for unregistering an object. */ private static class LogUnregisterObject extends LogRecord { private static final long serialVersionUID = 6269824097396935501L; private UID uid; LogUnregisterObject(UID uid) { this.uid = uid; } Object apply(Object state) { try { ((Activation) state).getGroupEntry(uid).unregisterObject( uid, false); } catch (Exception e) { logger.log(Level.WARNING, "log recovery throws", e); } return state; } } /** * Log record for registering a group. */ private static class LogRegisterGroup extends LogRecord { private static final long serialVersionUID = -1966827458515403625L; private ActivationGroupID id; private ActivationGroupDesc desc; LogRegisterGroup(ActivationGroupID id, ActivationGroupDesc desc) { this.id = id; this.desc = desc; } Object apply(Object state) { // modify state directly // can't ask a nonexistent GroupEntry to register itself ((Activation)state).groupTable.put(id, ((Activation) state).new GroupEntry(id, desc)); return state; } } /** * Log record for updating an activation desc */ private static class LogUpdateDesc extends LogRecord { private static final long serialVersionUID = 545511539051179885L; private UID uid; private ActivationDesc desc; LogUpdateDesc(UID uid, ActivationDesc desc) { this.uid = uid; this.desc = desc; } Object apply(Object state) { try { ((Activation) state).getGroupEntry(uid). setActivationDesc(uid, desc, false); } catch (Exception e) { logger.log(Level.WARNING, "log recovery throws", e); } return state; } } /** * Log record for unregistering a group. */ private static class LogUpdateGroupDesc extends LogRecord { private static final long serialVersionUID = -1271300989218424337L; private ActivationGroupID id; private ActivationGroupDesc desc; LogUpdateGroupDesc(ActivationGroupID id, ActivationGroupDesc desc) { this.id = id; this.desc = desc; } Object apply(Object state) { try { ((Activation) state).getGroupEntry(id). setActivationGroupDesc(id, desc, false); } catch (Exception e) { logger.log(Level.WARNING, "log recovery throws", e); } return state; } } /** * Log record for unregistering a group. */ private static class LogUnregisterGroup extends LogRecord { private static final long serialVersionUID = -3356306586522147344L; private ActivationGroupID id; LogUnregisterGroup(ActivationGroupID id) { this.id = id; } Object apply(Object state) { GroupEntry entry = (GroupEntry) ((Activation) state).groupTable.remove(id); try { entry.unregisterGroup(false); } catch (Exception e) { logger.log(Level.WARNING, "log recovery throws", e); } return state; } } /** * Log record for an active group incarnation */ private static class LogGroupIncarnation extends LogRecord { private static final long serialVersionUID = 4146872747377631897L; private ActivationGroupID id; private long inc; LogGroupIncarnation(ActivationGroupID id, long inc) { this.id = id; this.inc = inc; } Object apply(Object state) { try { GroupEntry entry = ((Activation) state).getGroupEntry(id); entry.incarnation = inc; } catch (Exception e) { logger.log(Level.WARNING, "log recovery throws", e); } return state; } } private static void usage() { System.err.println( MessageFormat.format(getTextResource("phoenix.usage"), new String[] {Activation.class.getName()})); System.exit(1); } private static void bomb(String error) { System.err.println("phoenix: " + error); // $NON-NLS$ usage(); } private static void bomb(String res, String val) { bomb(MessageFormat.format(getTextResource(res), new String[] {val})); } /** * Starts phoenix. See the * <a href="package-summary.html#package_description">package * documentation</a> for details. * * @param args command line options */ public static void main(String[] args) { if (System.getSecurityManager() == null) { System.setSecurityManager(new SecurityManager()); } boolean stop = false; if (args.length > 0 && args[0].equals("-stop")) { stop = true; String[] nargs = new String[args.length - 1]; System.arraycopy(args, 1, nargs, 0, nargs.length); args = nargs; } else if (args.length == 1 && args[0].equals("-help")) { usage(); } try { main(args, stop, null); } catch (Exception e) { System.err.println(MessageFormat.format( getTextResource("phoenix.unexpected.exception"), new Object[]{e.getMessage()})); e.printStackTrace(); System.exit(1); } } /** * Returns the ActivationSystem proxy (used by the PhoenixStarter). */ ActivationSystem getActivationSystemProxy() { return systemStub; } /** * Starts phoenix and returns a reference to the recovered * <code>Activation</code> instance. * * @param configOptions the configuration options for the configuration * @param stop if <code>true</code>, initiates shutdown of the * activation system on the "registryHost" and "registryPort" obtained * from the configuration * @param starter the <code>PhoenixStarter</code> instance, or * <code>null</code> **/ static Activation main(final String[] configOptions, final boolean stop, final PhoenixStarter starter) throws Exception { final Configuration config = getConfig(configOptions, starter); final LoginContext login = (LoginContext) config.getEntry(PHOENIX, "loginContext", LoginContext.class, null); if (login != null) { login.login(); } PrivilegedExceptionAction action = new PrivilegedExceptionAction() { public Object run() throws Exception { if (stop) { assert starter == null; shutdown(config); System.exit(0); } String logName = null; try { logName = (String) config.getEntry( PHOENIX, "persistenceDirectory", String.class); } catch (NoSuchEntryException e) { if (starter == null) { bomb("phoenix.missing.log", null); } else { throw e; } } ActLogHandler handler = new ActLogHandler(); ReliableLog log = new ReliableLog(logName, handler); log.recover(); Activation state = handler.getState(); if (state == null) { log.snapshot(); state = handler.getState(); } state.init(log, login, config, configOptions, starter); if (starter == null) { // prevent exit while (true) { try { Thread.sleep(Long.MAX_VALUE); } catch (InterruptedException e) { } } } else { return state; } } }; if (login != null) { return (Activation) Subject.doAsPrivileged(login.getSubject(), action, null); } else { return (Activation) action.run(); } } /** * Shut down an activation system daemon, using the specified * configuration location to obtain the host and port of the daemon's * registry, the client constraints for the remote call, and the * permissions to grant to the system proxy. */ private static void shutdown(Configuration config) throws Exception { String host = (String) config.getEntry(PHOENIX, "registryHost", String.class, null); int port = getInt(config, "registryPort", ActivationSystem.SYSTEM_PORT); Registry reg = LocateRegistry.getRegistry(host, port); ActivationSystem sys = (ActivationSystem) reg.lookup(ActivationSystem.class.getName()); ProxyPreparer sysPreparer = getPreparer(config, "systemPreparer"); sys = (ActivationSystem) sysPreparer.prepareProxy(sys); sys.shutdown(); } /** * Retrieves text resources from the locale-specific properties file. */ static String getTextResource(String key) { if (resources == null) { try { resources = ResourceBundle.getBundle( "com.sun.jini.phoenix.resources.phoenix"); } catch (MissingResourceException mre) { return "[missing resource file: " + key + "]"; } } try { return resources.getString(key); } catch (MissingResourceException mre) { return "[missing resource: " + key + "]"; } } /* * Dijkstra semaphore operations to limit the number of subprocesses * phoenix attempts to make at once. */ /** * Acquire the group semaphore and return a group name. Each * Pstartgroup must be followed by a Vstartgroup. The calling thread * will wait until there are fewer than <code>N</code> other threads * holding the group semaphore. The calling thread will then acquire * the semaphore and return. */ private synchronized String Pstartgroup() throws ActivationException { while (true) { checkShutdown(); // Wait until positive, then decrement. if (groupSemaphore > 0) { groupSemaphore--; return "Group-" + groupCounter++; } try { wait(); } catch (InterruptedException e) { } } } /** * Release the group semaphore. Every P operation must be * followed by a V operation. This may cause another thread to * wake up and return from its P operation. */ private synchronized void Vstartgroup() { // Increment and notify a waiter (not necessarily FIFO). groupSemaphore++; notifyAll(); } }