/** * Aborts the connection in response to an error. * * @param e The error that caused the connection to be aborted. Never null. */ @java.lang.SuppressWarnings("ToArrayCallWithZeroLengthArrayArgument") @SuppressWarnings( "ITA_INEFFICIENT_TO_ARRAY") // intentionally; race condition on listeners otherwise protected void terminate(IOException e) { try { synchronized (this) { if (e == null) throw new IllegalArgumentException(); outClosed = inClosed = e; try { transport.closeRead(); } catch (IOException x) { logger.log(Level.WARNING, "Failed to close down the reader side of the transport", x); } 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(); } } // JENKINS-14909: leave synch block } finally { if (e instanceof OrderlyShutdown) e = null; for (Listener l : listeners.toArray(new Listener[0])) l.onClosed(this, e); } }
/** * Sends a command to the remote end and executes it there. * * <p>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); transport.write(cmd, cmd instanceof CloseCommand); commandsSent++; }
/** * Creates a new channel. * * @param name See {@link #Channel(String, ExecutorService, Mode, InputStream, OutputStream, * OutputStream, boolean, ClassLoader)} * @param exec See {@link #Channel(String, ExecutorService, Mode, InputStream, OutputStream, * OutputStream, boolean, ClassLoader)} * @param transport The transport that we run {@link Channel} on top of. * @param base See {@link #Channel(String, ExecutorService, Mode, InputStream, OutputStream, * OutputStream, boolean, ClassLoader)} * @param restricted See {@link #Channel(String, ExecutorService, Mode, InputStream, OutputStream, * OutputStream, boolean, ClassLoader)} * @since 2.13 */ public Channel( String name, ExecutorService exec, CommandTransport transport, boolean restricted, ClassLoader base) throws IOException { this.name = name; this.executor = new InterceptingExecutorService(exec); this.isRestricted = restricted; this.underlyingOutput = transport.getUnderlyingStream(); if (base == null) base = getClass().getClassLoader(); this.baseClassLoader = base; if (export(this, false) != 1) throw new AssertionError(); // export number 1 is reserved for the channel itself remoteChannel = RemoteInvocationHandler.wrap(this, 1, IChannel.class, true, false); this.remoteCapability = transport.getRemoteCapability(); this.pipeWriter = new PipeWriter(createPipeWriterExecutor()); this.transport = transport; transport.setup( this, new CommandReceiver() { public void handle(Command cmd) { lastHeard = System.currentTimeMillis(); 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 " + Channel.this.name + ")", t); logger.log(Level.SEVERE, "This command is created here", cmd.createdAt); } } public void terminate(IOException e) { Channel.this.terminate(e); } }); }
/*package*/ Channel(ChannelBuilder settings, CommandTransport transport) throws IOException { this.name = settings.getName(); this.executor = new InterceptingExecutorService(settings.getExecutors()); this.isRestricted = settings.isRestricted(); this.underlyingOutput = transport.getUnderlyingStream(); this.jarCache = settings.getJarCache(); this.baseClassLoader = settings.getBaseLoader(); if (export(this, false) != 1) throw new AssertionError(); // export number 1 is reserved for the channel itself remoteChannel = RemoteInvocationHandler.wrap(this, 1, IChannel.class, true, false); this.remoteCapability = transport.getRemoteCapability(); this.pipeWriter = new PipeWriter(createPipeWriterExecutor()); this.transport = transport; this.jarLoader = new JarLoaderImpl(); // TODO: figure out a mechanism to allow the user to share this across // Channels setProperty(JarLoader.OURS, export(JarLoader.class, jarLoader, false)); transport.setup( this, new CommandReceiver() { public void handle(Command cmd) { lastHeard = System.currentTimeMillis(); 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 " + Channel.this.name + ")", t); logger.log(Level.SEVERE, "This command is created here", cmd.createdAt); } } public void terminate(IOException e) { Channel.this.terminate(e); } }); }
/** * Closes the channel. * * @param diagnosis If someone (either this side or the other side) tries to use a channel that's * already closed, they'll get a stack trace indicating that the channel has already been * closed. This diagnosis, if provided, will further chained to that exception, providing more * contextual information about why the channel was closed. * @since 2.8 */ public synchronized void close(Throwable diagnosis) throws IOException { if (outClosed != null) return; // already closed send(new CloseCommand(this, diagnosis)); outClosed = new IOException() .initCause( diagnosis); // last command sent. no further command allowed. lock guarantees that // no command will slip inbetween try { transport.closeWrite(); } 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. }