org.jboss.remoting.transport.multiplex.MultiplexingInputStream Maven / Gradle / Ivy
/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt 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.remoting.transport.multiplex;
import java.io.EOFException;
import java.io.IOException;
import java.net.SocketException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.jboss.logging.Logger;
import org.jboss.remoting.transport.multiplex.utility.GrowablePipedInputStream;
import org.jboss.remoting.transport.multiplex.utility.GrowablePipedOutputStream;
import org.jboss.remoting.transport.multiplex.utility.VirtualSelector;
/**
* MultiplexingInputStream
is the class returned by
* VirtualSocket.getInputStream()
.
* It supports the methods and behavior implemented by the InputStream
returned by
* java.net.Socket.getInputStream()
. For more information about the behavior
* of the methods, see the javadoc for java.io.InputStream
.
*
* Copyright (c) 2005
*
* @author Ron Sigal
*/
public class MultiplexingInputStream extends GrowablePipedInputStream
{
protected static final Logger log = Logger.getLogger(MultiplexingInputStream.class);
private VirtualSocket socket;
private boolean eof = false;
private boolean closed = false;
private boolean remoteShutDownPending = false;
private Set readingThreads = new HashSet();
private IOException readException;
private long skipCount = 0;
private boolean tracing;
/**
* @param sourceStream
* @param manager
*/
public MultiplexingInputStream(GrowablePipedOutputStream sourceStream, MultiplexingManager manager)
throws IOException
{
this(sourceStream, manager, null, null);
}
/**
* @param sourceStream
* @param manager
* @param socket
*
*/
public MultiplexingInputStream(GrowablePipedOutputStream sourceStream,
MultiplexingManager manager,
VirtualSocket socket)
throws IOException
{
this(sourceStream, manager, socket, null);
}
/**
* @param sourceStream
* @param manager
* @param socket
* @param virtualSelector
*
*/
public MultiplexingInputStream(GrowablePipedOutputStream sourceStream,
MultiplexingManager manager,
VirtualSocket socket,
VirtualSelector virtualSelector)
throws IOException
{
super(sourceStream, virtualSelector);
this.socket = socket;
tracing = log.isTraceEnabled();
}
//////////////////////////////////////////////////////////////////////////////////////////////////
/// The following methods are required of all InputStreams '///
//////////////////////////////////////////////////////////////////////////////////////////////////
/*************************************************************************************************
ok: public int read() throws IOException;
ok: public int read(byte b[]) throws IOException;
ok: public int read(byte b[], int off, int len) throws IOException;
ok: public long skip(long n) throws IOException;
ok: public int available() throws IOException;
ok: public void close() throws IOException;
ok: public void mark(int readlimit);
ok: public void reset() throws IOException;
ok: public boolean markSupported();
*************************************************************************************************/
/**
* See superclass javadoc.
*/
public void close() throws IOException
{
if (closed)
return;
log.debug("MultiplexingInputStream closing");
closed = true;
super.close();
if (socket != null)
socket.close();
// If a thread is currently in read(), interrupt it.
interruptReadingThreads();
}
/**
* See superclass javadoc.
*/
public synchronized int read() throws IOException
{
if (eof)
return -1;
if (closed)
throw new SocketException("Socket closed");
if (readException != null)
throw readException;
if (skipCount > 0)
skip(skipCount);
try
{
// We leave a reference to the current thread so that close() and handleRemoteShutdown()
// can interrupt it if necessary.
readingThreads.add(Thread.currentThread());
int b = super.read();
readingThreads.remove(Thread.currentThread());
if (tracing)
log.trace("read(): super.read() returned: " + b);
if (remoteShutDownPending && available() == 0)
setEOF();
return b & 0xff;
}
catch (IOException e)
{
readingThreads.remove(Thread.currentThread());
if (closed)
throw new SocketException("Socket closed");
if (eof)
return -1;
if (readException != null)
throw readException;
throw e;
}
}
/**
* See superclass javadoc.
*/
public int read(byte[] bytes) throws IOException
{
return read(bytes, 0, bytes.length);
}
/**
* See superclass javadoc.
*/
public synchronized int read(byte[] bytes, int off, int len) throws IOException
{
log.trace("entering read()");
if (eof)
return -1;
if (closed)
throw new SocketException("Socket closed");
if (readException != null)
throw readException;
if (skipCount > 0)
skip(skipCount);
try
{
// We leave a reference to the current thread so that handleRemoteShutdown() can
// interrupt it if necessary.
readingThreads.add(Thread.currentThread());
int n = super.read(bytes, off, len);
readingThreads.remove(Thread.currentThread());
if (tracing)
log.trace("super.read() returned " + n + " bytes: "
+ "[" + (0xff & bytes[off]) + ".." + (0xff & bytes[off+n-1]) + "]");
if (remoteShutDownPending && available() == 0)
setEOF();
return n;
}
catch (IOException e)
{
readingThreads.remove(Thread.currentThread());
if (eof)
return -1;
if (closed)
throw new SocketException("Socket closed");
throw e;
}
}
/**
* See superclass javadoc.
*/
public synchronized long skip(long n) throws IOException
{
if (eof)
return 0;
if (closed)
throw new SocketException("Socket closed");
if (readException != null)
throw readException;
if (n <= 0)
return 0;
int skipped = 0;
try
{
readingThreads.add(Thread.currentThread());
while (skipped < n && (skipped == 0 || available() > 0))
{
if (read() == -1)
break;
skipped++;
}
readingThreads.remove(Thread.currentThread());
if (remoteShutDownPending && available() == 0)
setEOF();
return skipped;
}
catch (IOException e)
{
readingThreads.remove(Thread.currentThread());
if (eof)
return -1;
if (closed)
throw new SocketException("Socket closed");
throw e;
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////
/// The following methods are specific to MultiplexingInputStream '///
//////////////////////////////////////////////////////////////////////////////////////////////////
/**
*
*/
protected VirtualSocket getSocket()
{
return socket;
}
/**
* handleRemoteShutdown()
is responsible for informing the MultiplexingInputStream
* that no more bytes will be coming from the remote MultiplexingOutputStream
to which
* it is connected, because shutdownOutput()
or close()
has been called on the
* remote VirtualSocket
. The result is that once all bytes sent by the remote socket have
* been consumed, all subsequent calls to read() will return -1 and all subsequent calls to skip() will
* return 0, indicating end of file has been reached.
*/
protected synchronized void handleRemoteShutdown() throws IOException
{
/*
* handleRemoteShutdown() needs to handle two cases correctly:
*
* Case 1. all bytes transmitted by the remote MultiplexingOutputStream have been consumed by the time
* handleRemoteShutdown() executes, and
*
* Case 2. not all bytes transmitted by the remote MultiplexingOutputStream have been consumed
* by the time handleRemoteShutdown() executes..
*
* Correctness argument:
*
* Case 1.
*
* The bracketing facility implemented by OutputMultiplexor guarantees that all bytes
* transmitted by the remote MultiplexingOutputStream will arrive and be stored in this
* MultiplexingInputStream before the protocol message arrives that leads to
* handleRemoteShutdown() being called. Therefore, if available() == 0 is true upon entering
* handleRemoteShutdown(), all transmitted bytes have been consumed and it is correct to indicate
* that this MultiplexingInputStream is at end of file.
*
* Case 1a. No threads are currently in read() or skip():
*
* Calling setEOF() will guarantee that all subsequent calls to read() will return -1 and all
* subsequent calls to skip() will return 0.
*
* Case 1b. One or more threads are currently in read() or skip():
*
* Since all read() methods, skip(), and handleRemoteShutdown() are synchronized, the only way
* handleRemoteShutdown() can be executing is if all of the threads in read() and skip() are
* blocked on the wait() call in super.read(). Then all of the blocked threads are referenced in
* the Set readingThreads, and calling interruptReadingThreads will guarantee that they are
* interrupted, which will lead to their throwing an InterruptedIOException. Moreover, calling
* setEOF() will guarantee that all such threads will see eof == true in the exception and
* will return -1. If any of the threads made the call to read() by way of skip(), then the
* condition (skipped == 0 || available() > 0) was true when read() was called, and since we
* are assumeing available() == 0, then skipped == 0 must have been true. Therefore, when
* it gets -1 from the call to read(), skip() will return 0. Finally, calling setEOF() in
* handleRemoteShutdown() will guarantee that all subsequent calls to read() will return -1
* and all subsequent calls to skip() will return 0.
*
* Case 2.
*
* Suppose, on the other hand, that available() == 0 is false. Then the only action taken by
* handleRemoteShutdown() is to set remoteShutdownPending to true, and as long as bytes are
* available, there is no obstacle to their being read or skipped.
*
* Fact. The last transmitted byte has been consumed if and only if available() == 0.
*
* (If):
* The fact that handleRemoteShutdown() has been called implies that all bytes transmitted from the remote
* socket have already arrived and been stored in this MultiplexingInputStream, so the value returned
* by available() will decrease monotonically, and once available() == 0, the last transmitted byte has
* been consumed.
*
* (Only if):
* This direction is obvious.
*
* Now, if no thread ever requests the last available byte to be read or skipped, then all calls to read()
* or skip() following the call to handleRemoteShutdown() will execute with available() > 0, and there
* will be no impediment to their successful completion.
*
* Suppose, then, that some thread T makes a call to read() that retrieves the last available byte.
* Upon returning from super.read(), T will find (remoteShutdownPending && available() == 0) is true,
* and it will call setEOF(), which, by the Fact argued above, is a correct action. Since available()
* was > 0 when the T entered read(), T will never call wait() in super.read(), so no other thread will
* enter read() or skip() until T leaves read(). At that point any threads entering read() will find
* eof == true and will return -1, and any threads entering skip() will find eof == true and return 0.
*
* Finally, suppose that some thread T makes a call to skip() to skip the last available byte.
* T will eventually leave the while loop in skip() with available() == 0, and when it reaches
* the test for (remoteShutDownPending && available() == 0), it will call setEOF().
* Since available() was > 0 when the T entered skip(), T will never call wait() in super.read(),
* so no other thread will enter read() or skip() until T leaves skip(). At that point any threads
* entering read() will find eof == true and will return -1, and any threads entering skip() will
* find eof == true and return 0.
*/
log.debug("entering handleRemoteShutdown()");
if (eof)
return;
remoteShutDownPending = true;
if (available() == 0)
{
setEOF();
interruptReadingThreads();
}
log.debug("leaving handleRemoteShutdown()");
}
/**
*
*/
protected synchronized void interruptReadingThreads()
{
// If we obtained the lock, then either there are no threads in read() or skip(),
// or any such threads are blocked in super.read(), having executed wait().
Iterator it = readingThreads.iterator();
while (it.hasNext())
{
Thread t = (Thread) it.next();
it.remove();
t.interrupt();
}
}
/**
* readInt()
is borrowed from DataInputStream. It saves the extra expense of
* creating a DataInputStream
*/
public final int readInt() throws IOException
{
int b1 = read();
int b2 = read();
int b3 = read();
int b4 = read();
if ((b1 | b2 | b3 | b4) < 0)
throw new EOFException();
return ((b1 << 24) + (b2 << 16) + (b3 << 8) + (b4 << 0));
}
/**
*/
protected void setEOF()
{
eof = true;
}
protected void setReadException(IOException e)
{
readException = e;
interruptReadingThreads();
}
/**
* @param n
*/
protected synchronized void setSkip(long n)
{
skipCount += n;
}
/**
* A MultiplexingInputStream may be created without reference to a VirtualSocket.
* (See MultiplexingManager.getAnOutputStream().) setSocket() allows the socket to
* be set afterwards.
*
* @param socket
*/
protected void setSocket(VirtualSocket socket)
{
this.socket = socket;
}
}