/*
* 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.jeri.internal.runtime.SelectionManager;
import com.sun.jini.logging.Levels;
import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.security.AccessController;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* SocketChannelConnectionIO implements the ConnectionIO abstraction for a
* connection accessible through a java.nio.channels.SocketChannel, and thus
* supports non-blocking I/O.
*
* @author Sun Microsystems, Inc.
**/
final class SocketChannelConnectionIO extends ConnectionIO {
private static final int RECEIVE_BUFFER_SIZE = 4096;
private static final int IOV_MAX = 16; // max writev iovcnt on Solaris...
/** mux logger */
private static final Logger logger =
Logger.getLogger("net.jini.jeri.connection.mux");
/** selection manager used by this implementation */
private static final SelectionManager selectionManager;
static { // REMIND: share more widely?
try {
selectionManager = new SelectionManager();
} catch (IOException e) {
throw new ExceptionInInitializerError(e);
}
}
/*
* Work around 4496906: sun.nio.ch.IOVecWrapper.<clinit> requires
* permission to read the system property "sun.arch.data.model".
*/
static {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() { public Object run() {
try {
Class.forName("sun.nio.ch.IOVecWrapper");
} catch (ClassNotFoundException e) {
}
return null;
} });
}
/** detail message of IOException thrown when 4854354 occurs */
private static final String detailMessage4854354 =
"A non-blocking socket operation could not be completed immediately";
/** socket channel for underlying connection */
private final SocketChannel channel;
private final SelectionManager.Key key;
/**
* queue of buffers of data to be sent over connection
*/
private final LinkedList sendQueue = new LinkedList();
/**
* queue of alternating buffers (that are in sendQueue) and IOFuture
* objects that need to be notified when those buffers are written
*/
private final LinkedList notifyQueue = new LinkedList();
/** buffer for reading incoming data from connection */
private final ByteBuffer inputBuffer =
ByteBuffer.allocateDirect(RECEIVE_BUFFER_SIZE); // ready for reading
private final ByteBuffer[] bufferPair = new ByteBuffer[2];
private final ByteBuffer[] preallocBufferArray = new ByteBuffer[IOV_MAX];
/**
* Creates a new SocketChannelConnectionIO for the connection represented
* by the supplied SocketChannel.
*/
SocketChannelConnectionIO(Mux mux, SocketChannel channel)
throws IOException
{
super(mux);
channel.configureBlocking(false);
this.channel = channel;
key = selectionManager.register(channel, new Handler());
}
/**
* Starts processing connection data.
*/
void start() throws IOException {
key.renewInterestMask(SelectionKey.OP_READ);
}
void asyncSend(ByteBuffer buffer) {
synchronized (mux.muxLock) {
if (mux.muxDown) {
return;
}
try {
if (sendQueue.isEmpty()) {
channel.write(buffer);
}
if (buffer.hasRemaining()) {
sendQueue.addLast(buffer);
key.renewInterestMask(SelectionKey.OP_WRITE); // ###
}
} catch (IOException e) {
mux.setDown("I/O error writing to mux connection: " +
e.toString(), e);
try {
channel.close();
} catch (IOException ignore) {
}
}
}
}
void asyncSend(ByteBuffer first, ByteBuffer second) {
synchronized (mux.muxLock) {
if (mux.muxDown) {
return;
}
try {
if (sendQueue.isEmpty()) {
bufferPair[0] = first;
bufferPair[1] = second;
try {
channel.write(bufferPair);
} catch (IOException e) {
// work around 4854354
String message = e.getMessage();
if (message != null &&
message.indexOf(detailMessage4854354) != -1)
{
logger.log(Levels.HANDLED,
"ignoring to work around 4854354", e);
} else {
throw e;
}
}
}
if (!first.hasRemaining()) {
if (second.hasRemaining()) {
sendQueue.addLast(second);
key.renewInterestMask(SelectionKey.OP_WRITE); // ###
}
} else {
sendQueue.addLast(first);
sendQueue.addLast(second);
key.renewInterestMask(SelectionKey.OP_WRITE); // ###
}
} catch (IOException e) {
mux.setDown("I/O error writing to mux connection: " +
e.toString(), e);
try {
channel.close();
} catch (IOException ignore) {
}
} finally {
bufferPair[0] = null;
bufferPair[1] = null;
}
}
}
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;
}
try {
if (sendQueue.isEmpty()) {
bufferPair[0] = first;
bufferPair[1] = second;
try {
channel.write(bufferPair);
} catch (IOException e) {
// work around 4854354
String message = e.getMessage();
if (message != null &&
message.indexOf(detailMessage4854354) != -1)
{
logger.log(Levels.HANDLED,
"ignoring to work around 4854354", e);
} else {
throw e;
}
}
}
if (!first.hasRemaining()) {
if (second.hasRemaining()) {
sendQueue.addLast(second);
key.renewInterestMask(SelectionKey.OP_WRITE); // ###
notifyQueue.addLast(second);
notifyQueue.addLast(future);
} else {
future.done();
}
} else {
sendQueue.addLast(first);
sendQueue.addLast(second);
key.renewInterestMask(SelectionKey.OP_WRITE); // ###
notifyQueue.addLast(second);
notifyQueue.addLast(future);
}
} catch (IOException e) {
mux.setDown("I/O error writing to mux connection: " +
e.toString(), e);
future.done(e);
try {
channel.close();
} catch (IOException ignore) {
}
} finally {
bufferPair[0] = first;
bufferPair[1] = second;
}
return future;
}
/*
* REMIND: Can/should we implement any sort of
* priority inversion avoidance scheme here?
*/
}
private void handleWriteReady() {
try {
synchronized (mux.muxLock) {
// ByteBuffer[] buffers =
// (ByteBuffer[]) sendQueue.toArray(preallocBufferArray);
// channel.write(buffers);
// while (!sendQueue.isEmpty()) {
// ByteBuffer bb = (ByteBuffer) sendQueue.getFirst();
// if (!bb.hasRemaining()) {
// sendQueue.removeFirst();
// if (!notifyQueue.isEmpty() &&
// bb == notifyQueue.getFirst())
// {
// notifyQueue.removeFirst();
// IOFuture future =
// (IOFuture) notifyQueue.removeFirst();
// future.done();
// }
// } else {
// key.renewInterestMask(SelectionKey.OP_WRITE); // ###
// break;
// }
// }
/*
* Work around 4481573: must manually break sequence of
* buffers to write into chunks no larger than IOV_MAX.
*/
gatherLoop:
while (!sendQueue.isEmpty()) {
/*
* Copy up to the first IOV_MAX buffers of the send queue
* into the preallocated ByteBuffer array.
*/
ByteBuffer[] bufs = preallocBufferArray; // IOV_MAX length
int len = sendQueue.size();
if (len <= bufs.length) { // optimization
bufs = (ByteBuffer[]) sendQueue.toArray(bufs);
} else {
Iterator iter = sendQueue.iterator(); // sufficient
len = 0;
while (iter.hasNext() && len < bufs.length) {
bufs[len++] = (ByteBuffer) iter.next();
}
}
try {
channel.write(bufs, 0, len);
} catch (IOException e) {
// work around 4854354
String message = e.getMessage();
if (message != null &&
message.indexOf(detailMessage4854354) != -1)
{
logger.log(Levels.HANDLED,
"ignoring to work around 4854354", e);
} else {
throw e;
}
}
for (int i = 0; i < len; i++) {
ByteBuffer bb = bufs[i];
assert bb == sendQueue.getFirst();
if (!bb.hasRemaining()) {
sendQueue.removeFirst();
if (!notifyQueue.isEmpty() &&
bb == notifyQueue.getFirst())
{
notifyQueue.removeFirst();
IOFuture future =
(IOFuture) notifyQueue.removeFirst();
future.done();
}
} else {
key.renewInterestMask(SelectionKey.OP_WRITE);// ###
break gatherLoop;
}
}
}
}
} catch (IOException e) {
try {
logger.log(Levels.HANDLED,
"mux write handler, I/O error", e);
} catch (Throwable t) {
}
mux.setDown("I/O error writing to mux connection: " +
e.toString(), e);
drainNotifyQueue();
try {
channel.close();
} catch (IOException ignore) {
}
} catch (Throwable t) {
try {
logger.log(Level.WARNING,
"mux write handler, unexpected exception", t);
} catch (Throwable tt) {
}
mux.setDown("unexpected exception in mux write handler: " +
t.toString(), t);
drainNotifyQueue();
try {
channel.close();
} catch (IOException ignore) {
}
}
}
private void drainNotifyQueue() {
synchronized (mux.muxLock) {
assert mux.muxDown;
while (!notifyQueue.isEmpty()) {
notifyQueue.removeFirst();
IOFuture future = (IOFuture) notifyQueue.removeFirst();
IOException ioe = new IOException(mux.muxDownMessage);
ioe.initCause(mux.muxDownCause);
future.done(ioe);
}
}
}
private void handleReadReady() {
try {
int n = channel.read(inputBuffer);
if (n == -1) {
throw new EOFException();
}
if (n > 0) {
mux.processIncomingData(inputBuffer);
}
assert inputBuffer.hasRemaining();
key.renewInterestMask(SelectionKey.OP_READ);
} 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 read handler, 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 read handler: " + e.getMessage());
} catch (Throwable t) {
}
}
}
if (future != null) {
try {
future.waitUntilDone();
} catch (IOException ignore) {
} catch (InterruptedException interrupt) {
Thread.currentThread().interrupt();
}
}
try {
channel.close();
} catch (IOException ignore) {
}
} catch (IOException e) {
try {
logger.log(Levels.HANDLED,
"mux read handler, I/O error", e);
} catch (Throwable t) {
}
mux.setDown("I/O error reading from mux connection: " +
e.toString(), e);
try {
channel.close();
} catch (IOException ignore) {
}
} catch (Throwable t) {
try {
logger.log(Level.WARNING,
"mux read handler, unexpected exception", t);
} catch (Throwable tt) {
}
mux.setDown("unexpected exception in mux read handler: " +
t.toString(), t);
try {
channel.close();
} catch (IOException ignore) {
}
}
}
private class Handler implements SelectionManager.SelectionHandler {
public void handleSelection(int readyMask, SelectionManager.Key key) {
if ((readyMask & SelectionKey.OP_WRITE) != 0) {
handleWriteReady();
}
if ((readyMask & SelectionKey.OP_READ) != 0) {
handleReadReady();
}
}
}
}