/*
* 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.jeri.internal.mux;
import com.sun.jini.logging.Levels;
import com.sun.jini.thread.Executor;
import com.sun.jini.thread.GetThreadPoolAction;
import java.io.BufferedOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.security.AccessController;
import java.util.LinkedList;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* StreamConnectionIO implements the ConnectionIO abstraction for a
* connection accessible through standard (blocking) I/O streams, i.e.
* java.io.OutputStream and java.io.InputStream.
*
* @author Sun Microsystems, Inc.
**/
final class StreamConnectionIO extends ConnectionIO {
private static final int RECEIVE_BUFFER_SIZE = 2048;
/**
* pool of threads for executing tasks in system thread group:
* used for I/O (reader and writer) threads
*/
private static final Executor systemThreadPool =
(Executor) AccessController.doPrivileged(
new GetThreadPoolAction(false));
/** mux logger */
private static final Logger logger =
Logger.getLogger("net.jini.jeri.connection.mux");
/** I/O streams for underlying connection */
private final OutputStream out;
private final InputStream in;
/** channels wrapped around underlying I/O streams */
private final WritableByteChannel outChannel;
private final ReadableByteChannel inChannel;
/**
* queue of buffers of data to be sent over connection, interspersed
* with IOFuture objects that need to be notified in sequence
*/
private LinkedList sendQueue = new LinkedList();
/** buffer for reading incoming data from connection */
private final ByteBuffer inputBuffer =
ByteBuffer.allocate(RECEIVE_BUFFER_SIZE); // ready for reading
/**
* Creates a new StreamConnectionIO for the connection represented by
* the supplied OutputStream and InputStream pair.
*/
StreamConnectionIO(Mux mux, OutputStream out, InputStream in) {
super(mux);
this.out = out;
// this.out = new BufferedOutputStream(out);
this.in = in;
outChannel = newChannel(out);
inChannel = newChannel(in);
}
/**
* Starts processing connection data. This method starts
* asynchronous actions to read and write from the connection.
*/
void start() throws IOException {
try {
systemThreadPool.execute(new Writer(), "mux writer");
systemThreadPool.execute(new Reader(), "mux reader");
} catch (OutOfMemoryError e) { // assume out of threads
try {
logger.log(Level.WARNING,
"could not create thread for request dispatch", e);
} catch (Throwable t) {
}
// treat as an "expected" I/O error
IOException ioe = new IOException("could not create I/O threads");
ioe.initCause(e);
throw ioe;
}
}
void asyncSend(ByteBuffer buffer) {
synchronized (mux.muxLock) {
if (mux.muxDown) {
return;
}
sendQueue.addLast(buffer);
mux.muxLock.notifyAll();
}
}
void asyncSend(ByteBuffer first, ByteBuffer second) {
synchronized (mux.muxLock) {
if (mux.muxDown) {
return;
}
sendQueue.addLast(first);
sendQueue.addLast(second);
mux.muxLock.notifyAll();
}
}
IOFuture futureSend(ByteBuffer first, ByteBuffer second) {
synchronized (mux.muxLock) {
IOFuture future = new IOFuture();
if (mux.muxDown) {
IOException ioe = new IOException(mux.muxDownMessage);
ioe.initCause(mux.muxDownCause);
future.done(ioe);
return future;
}
sendQueue.addLast(first);
sendQueue.addLast(second);
sendQueue.addLast(future);
mux.muxLock.notifyAll();
return future;
}
/*
* REMIND: Can/should we implement any sort of
* priority inversion avoidance scheme here?
*/
}
private class Writer implements Runnable {
Writer() { }
public void run() {
LinkedList localQueue = null;
try {
while (true) {
synchronized (mux.muxLock) {
while (!mux.muxDown && sendQueue.size() == 0) {
/*
* REMIND: Should we use a timeout here, to send
* occasional PING messages during periods of
* inactivity, to make sure connection is alive?
*/
mux.muxLock.wait();
/*
* Let an interrupt during the wait just kill this
* thread, because an interrupt during an I/O write
* would leave it in an unrecoverable state anyway.
*/
}
if (mux.muxDown && sendQueue.size() == 0) {
logger.log(Level.FINEST,
"mux writer thread dying, connection " +
"down and nothing more to send");
break;
}
localQueue = sendQueue;
sendQueue = new LinkedList();
}
boolean needToFlush = false;
while (!localQueue.isEmpty()) {
Object next = localQueue.getFirst();
if (next instanceof ByteBuffer) {
outChannel.write((ByteBuffer) next);
needToFlush = true;
} else {
assert next instanceof IOFuture;
if (needToFlush) {
out.flush();
needToFlush = false;
}
((IOFuture) next).done();
}
localQueue.removeFirst();
}
if (needToFlush) {
out.flush();
}
}
} catch (InterruptedException e) {
try {
logger.log(Level.WARNING,
"mux writer thread dying, interrupted", e);
} catch (Throwable t) {
}
mux.setDown("mux writer thread interrupted", e);
} catch (IOException e) {
try {
logger.log(Levels.HANDLED,
"mux writer thread dying, I/O error", e);
} catch (Throwable t) {
}
mux.setDown("I/O error writing to mux connection: " +
e.toString(), e);
} catch (Throwable t) {
try {
logger.log(Level.WARNING,
"mux writer thread dying, unexpected exception", t);
} catch (Throwable tt) {
}
mux.setDown("unexpected exception in mux writer thread: " +
t.toString(), t);
} finally {
synchronized (mux.muxLock) {
assert mux.muxDown;
if (localQueue != null) {
drainQueue(localQueue);
}
drainQueue(sendQueue);
}
try {
outChannel.close();
} catch (IOException e) {
}
}
}
}
private void drainQueue(LinkedList queue) {
while (!queue.isEmpty()) {
Object next = queue.removeFirst();
if (next instanceof IOFuture) {
IOException ioe = new IOException(mux.muxDownMessage);
ioe.initCause(mux.muxDownCause);
((IOFuture) next).done(ioe);
}
}
}
private class Reader implements Runnable {
Reader() { }
public void run() {
try {
while (true) {
int n = inChannel.read(inputBuffer);
if (n == -1) {
throw new EOFException();
}
assert n > 0; // channel is assumed to be blocking
mux.processIncomingData(inputBuffer);
assert inputBuffer.hasRemaining();
}
} catch (ProtocolException e) {
IOFuture future = null;
synchronized (mux.muxLock) {
/*
* If mux connection is already down, then we probably got
* here because of the receipt of a normal protocol-ending
* message, like Shutdown or Error, or else something else
* went wrong anyway. Otherwise, a real protocol violation
* was detected, so respond with an Error message before
* taking down the whole mux connection.
*/
if (!mux.muxDown) {
try {
logger.log(Levels.HANDLED,
"mux reader thread dying, protocol error", e);
} catch (Throwable t) {
}
future = mux.futureSendError(e.getMessage());
mux.setDown("protocol violation detected: " +
e.getMessage(), null);
} else {
try {
logger.log(Level.FINEST,
"mux reader thread dying: " + e.getMessage());
} catch (Throwable t) {
}
}
}
if (future != null) {
try {
future.waitUntilDone();
} catch (IOException ignore) {
} catch (InterruptedException interrupt) {
Thread.currentThread().interrupt();
}
}
} catch (IOException e) {
try {
logger.log(Levels.HANDLED,
"mux reader thread dying, I/O error", e);
} catch (Throwable t) {
}
mux.setDown("I/O error reading from mux connection: " +
e.toString(), e);
} catch (Throwable t) {
try {
logger.log(Level.WARNING,
"mux reader thread dying, unexpected exception", t);
} catch (Throwable tt) {
}
mux.setDown("unexpected exception in mux reader thread: " +
t.toString(), t);
} finally {
try {
inChannel.close();
} catch (IOException e) {
}
}
}
}
/**
* The following two methods are modifications of their
* equivalents in java.nio.channels.Channels with the assumption
* that the supplied byte buffers are backed by arrays, so no
* additional copying is required.
*/
public static ReadableByteChannel newChannel(final InputStream in) {
return new ReadableByteChannel() {
private boolean open = true;
public int read(ByteBuffer dst) throws IOException {
assert dst.hasArray();
byte[] array = dst.array();
int arrayOffset = dst.arrayOffset();
int totalRead = 0;
int bytesRead = 0;
int bytesToRead;
while ((bytesToRead = dst.remaining()) > 0) {
if ((totalRead > 0) && !(in.available() > 0)) {
break; // block at most once
}
int pos = dst.position();
bytesRead = in.read(array, arrayOffset + pos, bytesToRead);
if (bytesRead < 0) {
break;
} else {
dst.position(pos + bytesRead);
totalRead += bytesRead;
}
}
if ((bytesRead < 0) && (totalRead == 0)) {
return -1;
}
return totalRead;
}
public boolean isOpen() {
return open;
}
public void close() throws IOException {
in.close();
open = false;
}
};
}
public static WritableByteChannel newChannel(final OutputStream out) {
return new WritableByteChannel() {
private boolean open = true;
public int write(ByteBuffer src) throws IOException {
assert src.hasArray();
int len = src.remaining();
if (len > 0) {
int pos = src.position();
out.write(src.array(), src.arrayOffset() + pos, len);
src.position(pos + len);
}
return len;
}
public boolean isOpen() {
return open;
}
public void close() throws IOException {
out.close();
open = false;
}
};
}
}