/* * Copyright 2010 Brian S O'Neill * * Licensed 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 org.cojen.dirmi.io; import java.io.InputStream; import java.io.IOException; import java.io.OutputStream; import java.rmi.Remote; import java.rmi.RemoteException; import org.cojen.dirmi.Asynchronous; import org.cojen.dirmi.Disposer; import org.cojen.dirmi.Ordered; import org.cojen.dirmi.Unreferenced; /** * * * @author Brian S O'Neill */ class RecyclableSocketChannel extends SocketChannel { // Channel states, or'd together. private static final int REMOTE_RECYCLE_READY = 1; private static final int LOCAL_RECYCLE_READY = 2; private static final int OUTPUT_CLOSED = 4; private Recycler mRecycler; private RecycleControl mRemoteControl; private Input mRecycledInput; private Output mRecycledOutput; private int mState; RecyclableSocketChannel(IOExecutor executor, SimpleSocket socket) throws IOException { super(executor, socket); } RecyclableSocketChannel(RecyclableSocketChannel channel, Input in, Output out) { super(channel, in, out); in.setChannel(this); out.setChannel(this); } @Override public synchronized Remote installRecycler(Recycler recycler) { if (mRecycler != null) { throw new IllegalStateException(); } if (recycler == null) { throw new IllegalArgumentException(); } mRecycler = recycler; return new LocalControl(); } @Override public void setRecycleControl(Remote control) { if (!(control instanceof RecycleControl)) { throw new IllegalArgumentException(); } synchronized (this) { mRemoteControl = (RecycleControl) control; } } @Override Input createInputStream(SimpleSocket socket) throws IOException { return new Input(socket.getInputStream(), this); } @Override Output createOutputStream(SimpleSocket socket) throws IOException { return new Output(socket.getOutputStream(), this); } @Override public void close() throws IOException { if (!markClosed()) { return; } RecycleControl remoteControl; int state; check: { synchronized (this) { state = mState; if (mRecycler != null && (remoteControl = mRemoteControl) != null) { state |= OUTPUT_CLOSED; mState = state; break check; } // Ensure stub isn't referenced, breaking remote object cycle. mRemoteControl = null; } // Cannot recycle. super.close(); return; } try { // Instruct remote endpoint to stop writing. if ((state & LOCAL_RECYCLE_READY) != 0) { remoteControl.outputCloseAndDispose(); } else { remoteControl.outputClose(); } // Start draining and unblock remote endpoint's writing. getInputStream().inputClose(); // Close local output and wait for streams to recycle. getOutputStream().outputClose(); } catch (IOException e) { forceDisconnect(); throw e; } } @Override public void disconnect() { if (markClosed()) { forceDisconnect(); } } void forceDisconnect() { synchronized (this) { // Ensure stub isn't referenced, breaking remote object cycle. mRemoteControl = null; } super.disconnect(); } void unreferenced() { synchronized (this) { // Check if unreferenced following a dispose request, in which case // the request should be ignored. if ((mState & REMOTE_RECYCLE_READY) != 0) { return; } } forceDisconnect(); } protected RecyclableSocketChannel newRecycledChannel(Input in, Output out) { return new RecyclableSocketChannel(this, in, out); } void inputRecycled(Input in) { RecycleControl ready; int state; synchronized (this) { state = mState; mRecycledInput = in; if (mRecycledOutput == null) { ready = null; } else { ready = mRemoteControl; state |= LOCAL_RECYCLE_READY; mState = state; } } localRecycled(ready, state); } void outputRecycled(Output out) { RecycleControl ready; int state; synchronized (this) { state = mState; mRecycledOutput = out; if (mRecycledInput == null) { ready = null; } else { ready = mRemoteControl; state |= LOCAL_RECYCLE_READY; mState = state; } } localRecycled(ready, state); } private void localRecycled(RecycleControl ready, int state) { if (ready != null) { try { if ((state & OUTPUT_CLOSED) != 0) { ready.recycleReadyAndDispose(); } else { ready.recycleReady(); } } catch (RemoteException e) { forceDisconnect(); } } handoff(false); } void remoteRecycleReady() { handoff(true); } private void handoff(boolean remoteKnownReady) { Input in; Output out; Recycler recycler; synchronized (this) { if (remoteKnownReady) { mState |= REMOTE_RECYCLE_READY; } if ((mState & REMOTE_RECYCLE_READY) == 0 || ((in = mRecycledInput) == null) || ((out = mRecycledOutput) == null)) { return; } recycler = mRecycler; mRecycledInput = null; mRecycledOutput = null; } recycler.recycled(newRecycledChannel(in, out)); } static class Input extends PacketInputStream<Input> { private volatile RecyclableSocketChannel mChannel; Input(InputStream in, RecyclableSocketChannel channel) { super(in); mChannel = channel; } private Input() { } @Override public void close() throws IOException { RecyclableSocketChannel channel = mChannel; if (channel != null) { channel.close(); } } @Override public void disconnect() { RecyclableSocketChannel channel = mChannel; if (channel != null) { channel.disconnect(); } } @Override protected IOExecutor executor() { return mChannel.executor(); } @Override protected Input newInstance() { return new Input(); } @Override protected void recycled(Input newInstance) { // Not expected to be null. mChannel.inputRecycled(newInstance); } void setChannel(RecyclableSocketChannel channel) { mChannel = channel; } } static class Output extends PacketOutputStream<Output> { private volatile RecyclableSocketChannel mChannel; Output(OutputStream out, RecyclableSocketChannel channel) { super(out); mChannel = channel; } private Output() { } @Override public void close() throws IOException { RecyclableSocketChannel channel = mChannel; if (channel != null) { channel.close(); } } @Override public void disconnect() { RecyclableSocketChannel channel = mChannel; if (channel != null) { channel.disconnect(); } } @Override protected Output newInstance() { return new Output(); } @Override protected void recycled(Output newInstance) { // Not expected to be null. mChannel.outputRecycled(newInstance); } void setChannel(RecyclableSocketChannel channel) { mChannel = channel; } } public static interface RecycleControl extends Remote { @Ordered @Asynchronous void outputClose() throws RemoteException; @Ordered @Asynchronous @Disposer void outputCloseAndDispose() throws RemoteException; @Ordered @Asynchronous void recycleReady() throws RemoteException; @Ordered @Asynchronous @Disposer void recycleReadyAndDispose() throws RemoteException; } private class LocalControl implements RecycleControl, Unreferenced { @Override public void outputClose() { try { getOutputStream().outputClose(); } catch (IOException e) { disconnect(); } } @Override public void outputCloseAndDispose() { outputClose(); } @Override public void recycleReady() { remoteRecycleReady(); } @Override public void recycleReadyAndDispose() { recycleReady(); } @Override public void unreferenced() { RecyclableSocketChannel.this.unreferenced(); } } }