/* * JBoss, Home of Professional Open Source. * Copyright 2010, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.as.process.protocol; import java.io.BufferedOutputStream; import java.io.FilterInputStream; import java.io.FilterOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.io.OutputStream; import java.net.InetAddress; import java.net.Socket; import java.util.concurrent.Executor; import org.jboss.as.process.logging.ProcessLogger; import org.wildfly.common.Assert; /** * @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a> */ final class ConnectionImpl implements Connection { private final Socket socket; private final Object lock = new Object(); // protected by {@link #lock} private OutputStream sender; // protected by {@link #lock} private boolean readDone; // protected by {@link #lock} private boolean writeDone; private volatile MessageHandler messageHandler; private final Executor readExecutor; private volatile Object attachment; private volatile MessageHandler backupHandler; private final ClosedCallback callback; ConnectionImpl(final Socket socket, final MessageHandler handler, final Executor readExecutor, final ClosedCallback callback) { this.socket = socket; messageHandler = handler; this.readExecutor = readExecutor; this.callback = callback; } @Override public OutputStream writeMessage() throws IOException { final OutputStream os; synchronized (lock) { if (writeDone) { throw ProcessLogger.ROOT_LOGGER.writesAlreadyShutdown(); } while (sender != null) { try { lock.wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new InterruptedIOException(); } } boolean ok = false; try { sender = new MessageOutputStream(); os = new BufferedOutputStream(sender); ok = true; } finally { if (! ok) { // let someone else try lock.notify(); } } } return os; } @Override public void shutdownWrites() throws IOException { synchronized (lock) { if (writeDone) return; while (sender != null) { try { lock.wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new InterruptedIOException(); } } writeDone = true; if (readDone) { socket.close(); } else { socket.shutdownOutput(); } lock.notifyAll(); } } @Override public void close() throws IOException { synchronized (lock) { lock.notifyAll(); sender = null; readDone = true; writeDone = true; socket.close(); lock.notifyAll(); } } @Override public void setMessageHandler(final MessageHandler messageHandler) { Assert.checkNotNullParam("messageHandler", messageHandler); this.messageHandler = messageHandler; } @Override public InetAddress getPeerAddress() { synchronized (lock) { final Socket socket = this.socket; if (socket != null) { return socket.getInetAddress(); } else { return null; } } } @Override public void attach(final Object attachment) { this.attachment = attachment; } @Override public Object getAttachment() { return attachment; } @Override public void backupMessageHandler() { backupHandler = messageHandler; } @Override public void restoreMessageHandler() { MessageHandler handler = backupHandler; setMessageHandler(handler == null ? MessageHandler.NULL : handler); } Runnable getReadTask() { return new Runnable() { @Override public void run() { boolean closed = false; OutputStream mos = null; try { Pipe pipe = null; final InputStream is = socket.getInputStream(); final int bufferSize = 8192; final byte[] buffer = new byte[bufferSize]; for (;;) { int cmd = is.read(); switch (cmd) { case -1: { ProcessLogger.PROTOCOL_CONNECTION_LOGGER.trace("Received end of stream"); // end of stream safeHandleShutdown(); boolean done; if (mos != null) { mos.close(); pipe.await(); } synchronized (lock) { readDone = true; done = writeDone; } if (done) { StreamUtils.safeClose(socket); safeHandleFinished(); } closed = true; closed(); return; } case ProtocolConstants.CHUNK_START: { if (mos == null) { pipe = new Pipe(8192); // new message! final InputStream pis = pipe.getIn(); mos = pipe.getOut(); readExecutor.execute(new Runnable() { @Override public void run() { safeHandleMessage(new MessageInputStream(pis)); } }); } int cnt = StreamUtils.readInt(is); ProcessLogger.PROTOCOL_CONNECTION_LOGGER.tracef("Received data chunk of size %d", Integer.valueOf(cnt)); while (cnt > 0) { int sc = is.read(buffer, 0, Math.min(cnt, bufferSize)); if (sc == -1) { throw ProcessLogger.ROOT_LOGGER.unexpectedEndOfStream(); } mos.write(buffer, 0, sc); cnt -= sc; } break; } case ProtocolConstants.CHUNK_END: { ProcessLogger.PROTOCOL_CONNECTION_LOGGER.trace("Received end data marker"); if (mos != null) { // end message mos.close(); pipe.await(); mos = null; pipe = null; } break; } default: { throw ProcessLogger.ROOT_LOGGER.invalidCommandByte(cmd); } } } } catch (IOException e) { safeHandlerFailure(e); } finally { StreamUtils.safeClose(mos); if (!closed) { closed(); } } } }; } void safeHandleMessage(final InputStream pis) { try { messageHandler.handleMessage(this, pis); } catch (RuntimeException e) { ProcessLogger.PROTOCOL_CONNECTION_LOGGER.failedToReadMessage(e); } catch (IOException e) { ProcessLogger.PROTOCOL_CONNECTION_LOGGER.failedToReadMessage(e); } catch (NoClassDefFoundError e) { ProcessLogger.PROTOCOL_CONNECTION_LOGGER.failedToReadMessage(e); } catch (Error e) { ProcessLogger.PROTOCOL_CONNECTION_LOGGER.failedToReadMessage(e); throw e; } finally { StreamUtils.safeClose(pis); } } void safeHandleShutdown() { try { messageHandler.handleShutdown(this); } catch (IOException e) { ProcessLogger.PROTOCOL_CONNECTION_LOGGER.failedToHandleSocketShutdown(e); } } void safeHandleFinished() { try { messageHandler.handleFinished(this); } catch (IOException e) { ProcessLogger.PROTOCOL_CONNECTION_LOGGER.failedToHandleSocketFinished(e); } } void safeHandlerFailure(IOException e) { try { messageHandler.handleFailure(this, e); } catch (IOException e1) { ProcessLogger.PROTOCOL_CONNECTION_LOGGER.failedToHandleSocketFailure(e); } } final class MessageInputStream extends FilterInputStream { protected MessageInputStream(InputStream in) { super(in); } @Override public void close() throws IOException { try { while (in.read() != -1) {} } finally { super.close(); } } } final class MessageOutputStream extends FilterOutputStream { private final byte[] hdr = new byte[5]; MessageOutputStream() throws IOException { super(socket.getOutputStream()); } @Override public void write(final int b) throws IOException { throw new IllegalStateException(); } @Override public void write(final byte[] b, final int off, final int len) throws IOException { if (len == 0) { return; } final byte[] hdr = this.hdr; hdr[0] = (byte) ProtocolConstants.CHUNK_START; hdr[1] = (byte) (len >> 24); hdr[2] = (byte) (len >> 16); hdr[3] = (byte) (len >> 8); hdr[4] = (byte) (len >> 0); synchronized (lock) { if (sender != this || writeDone) { if (sender == this) sender = null; lock.notifyAll(); throw ProcessLogger.ROOT_LOGGER.writeChannelClosed(); } ProcessLogger.PROTOCOL_CONNECTION_LOGGER.tracef("Sending data chunk of size %d", Integer.valueOf(len)); out.write(hdr); out.write(b, off, len); } } @Override public void close() throws IOException { synchronized (lock) { if (sender != this) { return; } sender = null; // wake up waiters lock.notify(); if (writeDone) throw ProcessLogger.ROOT_LOGGER.writeChannelClosed(); if (readDone) { readExecutor.execute(new Runnable() { @Override public void run() { safeHandleFinished(); } }); } ProcessLogger.PROTOCOL_CONNECTION_LOGGER.tracef("Sending end of message"); out.write(ProtocolConstants.CHUNK_END); } } @Override protected void finalize() throws Throwable { super.finalize(); synchronized (lock) { if (sender == this) { ProcessLogger.PROTOCOL_CONNECTION_LOGGER.leakedMessageOutputStream(); close(); } } } } private void closed() { ClosedCallback callback = this.callback; if (callback != null) { callback.connectionClosed(); } } }