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

hudson.remoting.Channel Maven / Gradle / Ivy

Go to download

Contains the bootstrap code to bridge separate JVMs into a single semi-shared space. Reusable outside Hudson.

There is a newer version: 3.0.3
Show newest version
/*******************************************************************************
 *
 * Copyright (c) 2004-2009 Oracle Corporation.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors: 
*
*    Kohsuke Kawaguchi
 *     
 *
 *******************************************************************************/ 

package hudson.remoting;

import hudson.remoting.ExportTable.ExportList;
import hudson.remoting.PipeWindow.Key;
import hudson.remoting.PipeWindow.Real;
import hudson.remoting.forward.ForwarderFactory;
import hudson.remoting.forward.ListeningPort;
import hudson.remoting.forward.PortForwarder;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.lang.ref.WeakReference;
import java.net.URL;
import java.util.Collections;
import java.util.Hashtable;
import java.util.Map;
import java.util.Vector;
import java.util.WeakHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Represents a communication channel to the remote peer.
 * 

*

* A {@link Channel} is a mechanism for two JVMs to communicate over * bi-directional {@link InputStream}/{@link OutputStream} pair. * {@link Channel} represents an endpoint of the stream, and thus * two {@link Channel}s are always used in a pair. *

*

* Communication is established as soon as two {@link Channel} instances * are created at the end fo the stream pair * until the stream is terminated via {@link #close()}. *

*

* The basic unit of remoting is an executable {@link Callable} object. * An application can create a {@link Callable} object, and execute it remotely * by using the {@link #call(Callable)} method or {@link #callAsync(Callable)} method. *

*

* In this sense, {@link Channel} is a mechanism to delegate/offload computation * to other JVMs and somewhat like an agent system. This is bit different from * remoting technologies like CORBA or web services, where the server exposes a * certain functionality that clients invoke. *

*

* {@link Callable} object, as well as the return value / exceptions, * are transported by using Java serialization. All the necessary class files * are also shipped over {@link Channel} on-demand, so there's no need to * pre-deploy such classes on both JVMs. *

*

*

Implementor's Note

*

* {@link Channel} builds its features in a layered model. Its higher-layer * features are built on top of its lower-layer features, and they * are called layer-0, layer-1, etc. *

*

    *
  • * Layer 0: * See {@link Command} for more details. This is for higher-level features, * and not likely useful for applications directly. *
  • * Layer 1: * See {@link Request} for more details. This is for higher-level features, * and not likely useful for applications directly. *
* * @author Kohsuke Kawaguchi, Winston Prakash (bug fixes) */ public class Channel implements VirtualChannel, IChannel { private final ObjectInputStream ois; private final ObjectOutputStream oos; /** * Human readable description of where this channel is connected to. Used during diagnostic output * and error reports. */ private final String name; /*package*/ final boolean isRestricted; /*package*/ final ExecutorService executor; /** * If non-null, the incoming link is already shut down, * and reader is already terminated. The {@link Throwable} object indicates why the outgoing channel * was closed. */ private volatile Throwable inClosed = null; /** * If non-null, the outgoing link is already shut down, * and no command can be sent. The {@link Throwable} object indicates why the outgoing channel * was closed. */ private volatile Throwable outClosed = null; /*package*/ final Map> pendingCalls = new Hashtable>(); /** * Records the {@link Request}s being executed on this channel, sent by the remote peer. */ /*package*/ final Map> executingCalls = Collections.synchronizedMap(new Hashtable>()); /** * {@link ClassLoader}s that are proxies of the remote classloaders. */ /*package*/ final ImportedClassLoaderTable importedClassLoaders = new ImportedClassLoaderTable(this); /** * Objects exported via {@link #export(Class, Object)}. */ private final ExportTable exportedObjects = new ExportTable(); /** * {@link PipeWindow}s keyed by their OIDs (of the OutputStream exported by the other side.) *

*

* To make the GC of {@link PipeWindow} automatic, the use of weak references here are tricky. * A strong reference to {@link PipeWindow} is kept from {@link ProxyOutputStream}, and * this is the only strong reference. Thus while {@link ProxyOutputStream} is alive, * it keeps {@link PipeWindow} referenced, which in turn keeps its {@link PipeWindow.Real#key} * referenced, hence this map can be looked up by the OID. When the {@link ProxyOutputStream} * will be gone, the key is no longer strongly referenced, so it'll get cleaned up. *

*

* In some race condition situation, it might be possible for us to lose the tracking of the collect * window size. But as long as we can be sure that there's only one {@link PipeWindow} instance * per OID, it will only result in a temporary spike in the effective window size, * and therefore should be OK. */ private final WeakHashMap> pipeWindows = new WeakHashMap>(); /** * Registered listeners. */ private final Vector listeners = new Vector(); private int gcCounter; /** * Total number of nanoseconds spent for remote class loading. *

* Remote code execution often results in classloading activity * (more precisely, when the remote peer requests some computation * on this channel, this channel often has to load necessary * classes from the remote peer.) *

* This counter represents the total amount of time this channel * had to spend loading classes from the remote peer. The time * measurement doesn't include the time locally spent to actually * define the class (as the local classloading would have incurred * the same cost.) */ public final AtomicLong classLoadingTime = new AtomicLong(); /** * Total counts of remote classloading activities. Used in a pair * with {@link #classLoadingTime}. */ public final AtomicInteger classLoadingCount = new AtomicInteger(); /** * Total number of nanoseconds spent for remote resource loading. * * @see #classLoadingTime */ public final AtomicLong resourceLoadingTime = new AtomicLong(); /** * Total count of remote resource loading. * * @see #classLoadingCount */ public final AtomicInteger resourceLoadingCount = new AtomicInteger(); /** * Property bag that contains application-specific stuff. */ private final Hashtable properties = new Hashtable(); /** * Proxy to the remote {@link Channel} object. */ private IChannel remoteChannel; /** * Capability of the remote {@link Channel}. */ public final Capability remoteCapability; /** * When did we receive any data from this slave the last time? * This can be used as a basis for detecting dead connections. *

* Note that this doesn't include our sender side of the operation, * as successfully returning from {@link #send(Command)} doesn't mean * anything in terms of whether the underlying network was able to send * the data (for example, if the other end of a socket connection goes down * without telling us anything, the {@link SocketOutputStream#write(int)} will * return right away, and the socket only really times out after 10s of minutes. */ private volatile long lastHeard; /*package*/ final ExecutorService pipeWriter; /** * Communication mode. * * @since 1.161 */ public enum Mode { /** * Send binary data over the stream. Most efficient. */ BINARY(new byte[]{0, 0, 0, 0}), /** * Send ASCII over the stream. Uses base64, so the efficiency goes down by 33%, * but this is useful where stream is binary-unsafe, such as telnet. */ TEXT("<===[HUDSON TRANSMISSION BEGINS]===>") { @Override protected OutputStream wrap(OutputStream os) { return BinarySafeStream.wrap(os); } @Override protected InputStream wrap(InputStream is) { return BinarySafeStream.wrap(is); } }, /** * Let the remote peer decide the transmission mode and follow that. * Note that if both ends use NEGOTIATE, it will dead lock. */ NEGOTIATE(new byte[0]); /** * Preamble used to indicate the tranmission mode. * Because of the algorithm we use to detect the preamble, * the string cannot be any random string. For example, * if the preamble is "AAB", we'll fail to find a preamble * in "AAAB". */ private final byte[] preamble; Mode(String preamble) { try { this.preamble = preamble.getBytes("US-ASCII"); } catch (UnsupportedEncodingException e) { throw new Error(e); } } Mode(byte[] preamble) { this.preamble = preamble; } protected OutputStream wrap(OutputStream os) { return os; } protected InputStream wrap(InputStream is) { return is; } } public Channel(String name, ExecutorService exec, InputStream is, OutputStream os) throws IOException { this(name, exec, Mode.BINARY, is, os, null); } public Channel(String name, ExecutorService exec, Mode mode, InputStream is, OutputStream os) throws IOException { this(name, exec, mode, is, os, null); } public Channel(String name, ExecutorService exec, InputStream is, OutputStream os, OutputStream header) throws IOException { this(name, exec, Mode.BINARY, is, os, header); } public Channel(String name, ExecutorService exec, Mode mode, InputStream is, OutputStream os, OutputStream header) throws IOException { this(name, exec, mode, is, os, header, false); } /** * Creates a new channel. * * @param name Human readable name of this channel. Used for debug/logging. Can be anything. * @param exec Commands sent from the remote peer will be executed by using this {@link Executor}. * @param mode The encoding to be used over the stream. * @param is Stream connected to the remote peer. It's the caller's responsibility to do * buffering on this stream, if that's necessary. * @param os Stream connected to the remote peer. It's the caller's responsibility to do * buffering on this stream, if that's necessary. * @param header If non-null, receive the portion of data in is before * the data goes into the "binary mode". This is useful * when the established communication channel might include some data that might * be useful for debugging/trouble-shooting. * @param restricted If true, this channel won't accept {@link Command}s that allow the remote end to execute arbitrary closures * --- instead they can only call methods on objects that are exported by this channel. * This also prevents the remote end from loading classes into JVM. *

* Note that it still allows the remote end to deserialize arbitrary object graph * (provided that all the classes are already available in this JVM), so exactly how * safe the resulting behavior is is up to discussion. */ public Channel(String name, ExecutorService exec, Mode mode, InputStream is, OutputStream os, OutputStream header, boolean restricted) throws IOException { this(name, exec, mode, is, os, header, restricted, new Capability()); } /*package*/ Channel(String name, ExecutorService exec, Mode mode, InputStream is, OutputStream os, OutputStream header, boolean restricted, Capability capability) throws IOException { this.name = name; this.executor = exec; this.isRestricted = restricted; if (export(this, false) != 1) { throw new AssertionError(); // export number 1 is reserved for the channel itself } remoteChannel = RemoteInvocationHandler.wrap(this, 1, IChannel.class, false, false); // write the magic preamble. // certain communication channel, such as forking JVM via ssh, // may produce some garbage at the beginning (for example a remote machine // might print some warning before the program starts outputting its own data.) // // so use magic preamble and discard all the data up to that to improve robustness. capability.writePreamble(os); ObjectOutputStream oos = null; if (mode != Mode.NEGOTIATE) { os.write(mode.preamble); oos = new ObjectOutputStream(mode.wrap(os)); oos.flush(); // make sure that stream preamble is sent to the other end. avoids dead-lock } {// read the input until we hit preamble Mode[] modes = {Mode.BINARY, Mode.TEXT}; byte[][] preambles = new byte[][]{Mode.BINARY.preamble, Mode.TEXT.preamble, Capability.PREAMBLE}; int[] ptr = new int[3]; Capability cap = new Capability( 0); // remote capacity that we obtained. If we don't hear from remote, assume no capability while (true) { int ch = is.read(); if (ch == -1) { throw new EOFException("unexpected stream termination"); } for (int i = 0; i < preambles.length; i++) { byte[] preamble = preambles[i]; if (preamble[ptr[i]] == ch) { if (++ptr[i] == preamble.length) { switch (i) { case 0: case 1: // transmission mode negotiation if (mode == Mode.NEGOTIATE) { // now we know what the other side wants, so send the consistent preamble mode = modes[i]; os.write(mode.preamble); oos = new ObjectOutputStream(mode.wrap(os)); oos.flush(); } else { if (modes[i] != mode) { throw new IOException("Protocol negotiation failure"); } } this.oos = oos; this.remoteCapability = cap; this.pipeWriter = createPipeWriter(); this.ois = new ObjectInputStream(mode.wrap(is)); new ReaderThread(name).start(); return; case 2: cap = Capability.read(is); break; } ptr[i] = 0; // reset } } else { // didn't match. ptr[i] = 0; } } if (header != null) { header.write(ch); } } } } /** * Callback "interface" for changes in the state of {@link Channel}. */ public static abstract class Listener { /** * When the channel was closed normally or abnormally due to an error. * * @param cause if the channel is closed abnormally, this parameter * represents an exception that has triggered it. * Otherwise null. */ public void onClosed(Channel channel, IOException cause) { } } /*package*/ boolean isOutClosed() { return outClosed != null; } /** * Creates the {@link ExecutorService} for writing to pipes. *

*

* If the throttling is supported, use a separate thread to free up the main channel * reader thread (thus prevent blockage.) Otherwise let the channel reader thread do it, * which is the historical behaviour. */ private ExecutorService createPipeWriter() { if (remoteCapability.supportsPipeThrottling()) { return Executors.newSingleThreadExecutor(new ThreadFactory() { public Thread newThread(Runnable r) { return new Thread(r, "Pipe writer thread: " + name); } }); } return new SynchronousExecutorService(); } /** * Sends a command to the remote end and executes it there. *

*

* This is the lowest layer of abstraction in {@link Channel}. * {@link Command}s are executed on a remote system in the order they are sent. */ /*package*/ synchronized void send(Command cmd) throws IOException { if (outClosed != null) { throw new ChannelClosedException(outClosed); } if (logger.isLoggable(Level.FINE)) { logger.fine("Send " + cmd); } Channel old = Channel.setCurrent(this); try { oos.writeObject(cmd); oos.flush(); // make sure the command reaches the other end. } finally { Channel.setCurrent(old); } // unless this is the last command, have OOS and remote OIS forget all the objects we sent // in this command. Otherwise it'll keep objects in memory unnecessarily. // However, this may fail if the command was the close, because that's supposed to be the last command // ever sent. See the comment from jglick on HUDSON-3077 about what happens if we do oos.reset(). if (!(cmd instanceof CloseCommand)) { oos.reset(); } } /** * {@inheritDoc} */ public T export(Class type, T instance) { return export(type, instance, true); } /** * @param userProxy If true, the returned proxy will be capable to handle classes * defined in the user classloader as parameters and return values. * Such proxy relies on {@link RemoteClassLoader} and related mechanism, * so it's not usable for implementing lower-layer services that are * used by {@link RemoteClassLoader}. *

* To create proxies for objects inside remoting, pass in false. */ /*package*/ T export(Class type, T instance, boolean userProxy) { if (instance == null) { return null; } // every so often perform GC on the remote system so that // unused RemoteInvocationHandler get released, which triggers // unexport operation. if ((++gcCounter) % 10000 == 0) { try { send(new GCCommand()); } catch (IOException e) { // for compatibility reason we can't change the export method signature logger.log(Level.WARNING, "Unable to send GC command", e); } } // proxy will unexport this instance when it's GC-ed on the remote machine. final int id = export(instance); return RemoteInvocationHandler.wrap(null, id, type, userProxy, exportedObjects.isRecording()); } /*package*/ int export(Object instance) { return exportedObjects.export(instance); } /*package*/ int export(Object instance, boolean automaticUnexport) { return exportedObjects.export(instance, automaticUnexport); } /*package*/ Object getExportedObject(int oid) { return exportedObjects.get(oid); } /*package*/ void unexport(int id) { exportedObjects.unexportByOid(id); } /** * Preloads jar files on the remote side. *

*

* This is a performance improvement method that can be safely * ignored if your goal is just to make things working. *

*

* Normally, classes are transferred over the network one at a time, * on-demand. This design is mainly driven by how Java classloading works * — we can't predict what classes will be necessarily upfront very easily. *

*

* Classes are loaded only once, so for long-running {@link Channel}, * this is normally an acceptable overhead. But sometimes, for example * when a channel is short-lived, or when you know that you'll need * a majority of classes in certain jar files, then it is more efficient * to send a whole jar file over the network upfront and thereby * avoiding individual class transfer over the network. *

*

* That is what this method does. It ensures that a series of jar files * are copied to the remote side (AKA "preloading.") * Classloading will consult the preloaded jars before performing * network transfer of class files. * * @param classLoaderRef This parameter is used to identify the remote classloader * that will prefetch the specified jar files. That is, prefetching * will ensure that prefetched jars will kick in * when this {@link Callable} object is actually executed remote side. *

*

* {@link RemoteClassLoader}s are created wisely, one per local {@link ClassLoader}, * so this parameter doesn't have to be exactly the same {@link Callable} * to be executed later — it just has to be of the same class. * @param classesInJar {@link Class} objects that identify jar files to be preloaded. * Jar files that contain the specified classes will be preloaded into the remote peer. * You just need to specify one class per one jar. * @return true if the preloading actually happened. false if all the jars * are already preloaded. This method is implemented in such a way that * unnecessary jar file transfer will be avoided, and the return value * will tell you if this optimization kicked in. Under normal circumstances * your program shouldn't depend on this return value. It's just a hint. * @throws IOException if the preloading fails. */ public boolean preloadJar(Callable classLoaderRef, Class... classesInJar) throws IOException, InterruptedException { return preloadJar(UserRequest.getClassLoader(classLoaderRef), classesInJar); } public boolean preloadJar(ClassLoader local, Class... classesInJar) throws IOException, InterruptedException { URL[] jars = new URL[classesInJar.length]; for (int i = 0; i < classesInJar.length; i++) { jars[i] = Which.jarFile(classesInJar[i]).toURI().toURL(); } return call(new PreloadJarTask(jars, local)); } public boolean preloadJar(ClassLoader local, URL... jars) throws IOException, InterruptedException { return call(new PreloadJarTask(jars, local)); } PipeWindow getPipeWindow(int oid) { synchronized (pipeWindows) { Key k = new Key(oid); WeakReference v = pipeWindows.get(k); if (v != null) { PipeWindow w = v.get(); if (w != null) { return w; } } PipeWindow w; if (remoteCapability.supportsPipeThrottling()) { w = new Real(k, PIPE_WINDOW_SIZE); } else { w = new PipeWindow.Fake(); } pipeWindows.put(k, new WeakReference(w)); return w; } } /** * {@inheritDoc} */ public V call(Callable callable) throws IOException, T, InterruptedException { UserRequest request = null; try { request = new UserRequest(this, callable); UserResponse r = request.call(this); return r.retrieve(this, UserRequest.getClassLoader(callable)); // re-wrap the exception so that we can capture the stack trace of the caller. } catch (ClassNotFoundException e) { IOException x = new IOException("Remote call on " + name + " failed"); x.initCause(e); throw x; } catch (Error e) { IOException x = new IOException("Remote call on " + name + " failed"); x.initCause(e); throw x; } finally { // since this is synchronous operation, when the round trip is over // we assume all the exported objects are out of scope. // (that is, the operation shouldn't spawn a new thread or altter // global state in the remote system. if (request != null) { request.releaseExports(); } } } /** * {@inheritDoc} */ public Future callAsync(final Callable callable) throws IOException { final Future> f = new UserRequest(this, callable).callAsync(this); return new FutureAdapter>(f) { protected V adapt(UserResponse r) throws ExecutionException { try { return r.retrieve(Channel.this, UserRequest.getClassLoader(callable)); } catch (Throwable t) {// really means catch(T t) throw new ExecutionException(t); } } }; } /* * This method provides a mean to flush the I/O pipe associated with this * channel. Useful when the process associated with the channel is terminating * but the pipe might still transmitting data. * See http://issues.hudson-ci.org/browse/HUDSON-7809 */ public void flushPipe() throws IOException, InterruptedException { // The solution is to create no-op dummy RemotePipeWriter callable and submit // to the channel synchronously. try { pipeWriter.submit(new Runnable() { public void run() { // Do nothing, just a dummy runnable just to flush // this side of the Pipe } }).get(); } catch (ExecutionException exc) { throw new AssertionError(exc); } // Do not use anonymous class, other wise whole class gets marshalled over pipe and // the channel class is not serializable. call(new DummyRemotePipeWriterCallable()); } public static class DummyRemotePipeWriterCallable implements Callable, Serializable { public Object call() throws InterruptedException { try { return Channel.current().pipeWriter.submit(new Runnable() { public void run() { // Do nothing, just a dummy runnable just to flush // other side of the Pipe } }).get(); } catch (ExecutionException exc) { throw new AssertionError(exc); } } } ; /** * Aborts the connection in response to an error. * * @param e The error that caused the connection to be aborted. Never null. */ protected synchronized void terminate(IOException e) { if (e == null) { throw new IllegalArgumentException(); } outClosed = inClosed = e; try { synchronized (pendingCalls) { for (Request req : pendingCalls.values()) { req.abort(e); } pendingCalls.clear(); } synchronized (executingCalls) { for (Request r : executingCalls.values()) { java.util.concurrent.Future f = r.future; if (f != null) { f.cancel(true); } } executingCalls.clear(); } } finally { notifyAll(); if (e instanceof OrderlyShutdown) { e = null; } for (Listener l : listeners.toArray(new Listener[listeners.size()])) { l.onClosed(this, e); } } } /** * Registers a new {@link Listener}. * * @see #removeListener(Listener) */ public void addListener(Listener l) { listeners.add(l); } /** * Removes a listener. * * @return false if the given listener has not been registered to begin with. */ public boolean removeListener(Listener l) { return listeners.remove(l); } /** * Waits for this {@link Channel} to be closed down. *

* The close-down of a {@link Channel} might be initiated locally or remotely. * * @throws InterruptedException If the current thread is interrupted while waiting for the completion. */ public synchronized void join() throws InterruptedException { while (inClosed == null || outClosed == null) { wait(); } } /** * If the receiving end of the channel is closed (that is, if we are guaranteed to receive nothing further), * this method returns true. */ /*package*/ boolean isInClosed() { return inClosed != null; } /** * Waits for this {@link Channel} to be closed down, but only up the given milliseconds. * * @throws InterruptedException If the current thread is interrupted while waiting for the completion. * @since 1.299 */ public synchronized void join(long timeout) throws InterruptedException { long start = System.currentTimeMillis(); while (System.currentTimeMillis() - start < timeout && (inClosed == null || outClosed == null)) { wait(timeout + start - System.currentTimeMillis()); } } /** * Notifies the remote peer that we are closing down. *

* Execution of this command also triggers the {@link ReaderThread} to shut down * and quit. The {@link CloseCommand} is always the last command to be sent on * {@link ObjectOutputStream}, and it's the last command to be read. */ private static final class CloseCommand extends Command { protected void execute(Channel channel) { try { channel.close(); channel.terminate(new OrderlyShutdown(createdAt)); } catch (IOException e) { logger.log(Level.SEVERE, "close command failed on " + channel.name, e); logger.log(Level.INFO, "close command created at", createdAt); } } @Override public String toString() { return "close"; } } /** * Signals the orderly shutdown of the channel, but captures * where the termination was initiated as a nested exception. */ private static final class OrderlyShutdown extends IOException { private OrderlyShutdown(Throwable cause) { super(cause.getMessage()); initCause(cause); } private static final long serialVersionUID = 1L; } /** * Resets all the performance counters. */ public void resetPerformanceCounters() { classLoadingCount.set(0); classLoadingTime.set(0); resourceLoadingCount.set(0); resourceLoadingTime.set(0); } /** * {@inheritDoc} */ public synchronized void close() throws IOException { if (outClosed != null) { return; // already closed } send(new CloseCommand()); outClosed = new IOException(); // last command sent. no further command allowed. lock guarantees that no command will slip inbetween try { oos.close(); } catch (IOException e) { // there's a race condition here. // the remote peer might have already responded to the close command // and closed the connection, in which case our close invocation // could fail with errors like // "java.io.IOException: The pipe is being closed" // so let's ignore this error. } // termination is done by CloseCommand when we received it. } /** * Gets the application specific property set by {@link #setProperty(Object, Object)}. * These properties are also accessible from the remote channel via {@link #getRemoteProperty(Object)}. *

*

* This mechanism can be used for one side to discover contextual objects created by the other JVM * (as opposed to executing {@link Callable}, which cannot have any reference to the context * of the remote {@link Channel}. */ public Object getProperty(Object key) { return properties.get(key); } public T getProperty(ChannelProperty key) { return key.type.cast(properties.get(key)); } /** * Works like {@link #getProperty(Object)} but wait until some value is set by someone. */ public Object waitForProperty(Object key) throws InterruptedException { synchronized (properties) { while (true) { Object v = properties.get(key); if (v != null) { return v; } properties.wait(); } } } /** * Sets the property value on this side of the channel. * * @see #getProperty(Object) */ public Object setProperty(Object key, Object value) { synchronized (properties) { Object old = value != null ? properties.put(key, value) : properties.remove(key); properties.notifyAll(); return old; } } public Object getRemoteProperty(Object key) { return remoteChannel.getProperty(key); } public Object waitForRemoteProperty(Object key) throws InterruptedException { return remoteChannel.waitForProperty(key); } /** * Starts a local to remote port forwarding (the equivalent of "ssh -L"). * * @param recvPort The port on this local machine that we'll listen to. 0 to let * OS pick a random available port. If you specify 0, use * {@link ListeningPort#getPort()} to figure out the actual assigned port. * @param forwardHost The remote host that the connection will be forwarded to. * Connection to this host will be made from the other JVM that * this {@link Channel} represents. * @param forwardPort The remote port that the connection will be forwarded to. * @return */ public ListeningPort createLocalToRemotePortForwarding(int recvPort, String forwardHost, int forwardPort) throws IOException, InterruptedException { return new PortForwarder(recvPort, ForwarderFactory.create(this, forwardHost, forwardPort)); } /** * Starts a remote to local port forwarding (the equivalent of "ssh -R"). * * @param recvPort The port on the remote JVM (represented by this {@link Channel}) * that we'll listen to. 0 to let * OS pick a random available port. If you specify 0, use * {@link ListeningPort#getPort()} to figure out the actual assigned port. * @param forwardHost The remote host that the connection will be forwarded to. * Connection to this host will be made from this JVM. * @param forwardPort The remote port that the connection will be forwarded to. * @return */ public ListeningPort createRemoteToLocalPortForwarding(int recvPort, String forwardHost, int forwardPort) throws IOException, InterruptedException { return PortForwarder.create(this, recvPort, ForwarderFactory.create(forwardHost, forwardPort)); } @Override public String toString() { return super.toString() + ":" + name; } /** * Dumps the list of exported objects and their allocation traces to the given output. */ public void dumpExportTable(PrintWriter w) throws IOException { exportedObjects.dump(w); } public ExportList startExportRecording() { return exportedObjects.startRecording(); } /** * @see #lastHeard */ public long getLastHeard() { return lastHeard; } private final class ReaderThread extends Thread { public ReaderThread(String name) { super("Channel reader thread: " + name); } @Override public void run() { Command cmd = null; try { while (inClosed == null) { try { Channel old = Channel.setCurrent(Channel.this); try { cmd = (Command) ois.readObject(); lastHeard = System.currentTimeMillis(); } finally { Channel.setCurrent(old); } } catch (EOFException e) { IOException ioe = new IOException("Unexpected termination of the channel"); ioe.initCause(e); throw ioe; } catch (ClassNotFoundException e) { logger.log(Level.SEVERE, "Unable to read a command (channel " + name + ")", e); } if (logger.isLoggable(Level.FINE)) { logger.fine("Received " + cmd); } try { cmd.execute(Channel.this); } catch (Throwable t) { logger.log(Level.SEVERE, "Failed to execute command " + cmd + " (channel " + name + ")", t); logger.log(Level.SEVERE, "This command is created here", cmd.createdAt); } } ois.close(); } catch (IOException e) { logger.log(Level.SEVERE, "I/O error in channel " + name, e); terminate(e); } finally { pipeWriter.shutdown(); } } } /*package*/ static Channel setCurrent(Channel channel) { Channel old = CURRENT.get(); CURRENT.set(channel); return old; } /** * This method can be invoked during the serialization/deserialization of * objects when they are transferred to the remote {@link Channel}, * as well as during {@link Callable#call()} is invoked. * * @return null * if the calling thread is not performing serialization. */ public static Channel current() { return CURRENT.get(); } /** * Remembers the current "channel" associated for this thread. */ private static final ThreadLocal CURRENT = new ThreadLocal(); private static final Logger logger = Logger.getLogger(Channel.class.getName()); public static final int PIPE_WINDOW_SIZE = Integer.getInteger(Channel.class + ".pipeWindowSize", 128 * 1024); // static { // ConsoleHandler h = new ConsoleHandler(); // h.setFormatter(new Formatter(){ // public synchronized String format(LogRecord record) { // StringBuilder sb = new StringBuilder(); // sb.append((record.getMillis()%100000)+100000); // sb.append(" "); // if (record.getSourceClassName() != null) { // sb.append(record.getSourceClassName()); // } else { // sb.append(record.getLoggerName()); // } // if (record.getSourceMethodName() != null) { // sb.append(" "); // sb.append(record.getSourceMethodName()); // } // sb.append('\n'); // String message = formatMessage(record); // sb.append(record.getLevel().getLocalizedName()); // sb.append(": "); // sb.append(message); // sb.append('\n'); // if (record.getThrown() != null) { // try { // StringWriter sw = new StringWriter(); // PrintWriter pw = new PrintWriter(sw); // record.getThrown().printStackTrace(pw); // pw.close(); // sb.append(sw.toString()); // } catch (Exception ex) { // } // } // return sb.toString(); // } // }); // h.setLevel(Level.FINE); // logger.addHandler(h); // logger.setLevel(Level.FINE); // } }