
org.dellroad.muxable.MuxableChannel Maven / Gradle / Ivy
Show all versions of muxable-api Show documentation
/*
* Copyright (C) 2021 Archie L. Cobbs. All rights reserved.
*/
package org.dellroad.muxable;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SelectableChannel;
import java.nio.channels.Selector;
import java.nio.channels.WritableByteChannel;
import java.util.concurrent.BlockingQueue;
/**
* A single channel supporting parallel nested byte-oriented channels operating independently.
*
*
* The nested "child" channels are byte-oriented and operate independently, and all scoped to the parent {@link MuxableChannel}.
* In other words, if a parent is closed, all of its nested channels are also implicitly closed; on the other hand,
* if a nested channel is closed, only that nested channel is affected, and its parent and siblings are unaffected.
*
*
* More precisely, a nested channel is represented by a {@link NestedChannel}, which provides access to the
* input and output channels. When a {@link MuxableChannel} is closed, these two channels are explicitly closed
* for all of its existing nested channels (on the other hand, whether the channel(s) that connect to the peer,
* if any, are explicitly closed depends on the {@link MuxableChannel} implementation).
*
*
* Nested channels are bidirectional. Unlike TCP sockets, they do not support shutting down only one direction.
* Closing either the input or output channel results in both channels being closed.
*
*
* Any I/O exception implies brokenness: if a {@link MuxableChannel} or any nested channel throws {@link IOException},
* one should assume it (the channel that threw the exception) is no longer usable and should be closed.
*
*
* Deadlock Requirements
*
*
* Implementations may use varying strategies for multiplexing the nested channels over the underlying "real" channel, and
* this may have subtle effects on behavior. For example, with implementations that multiplex over a single underlying TCP
* stream, there may be situations where, after certain internal buffer limits are reached, further attempts to read from
* nested channel A will block unless and until more data is read from a sibling nested channel B, etc. Nested channels
* might also block if the {@link BlockingQueue} returned by {@link #getNestedChannelRequests} reaches its internal buffer
* capacity, etc. In any case, such restrictions or limitations should be clearly documented by the implementation.
*
*
* However, all implementations must avoid "senseless deadlock"; more precisely: if, for every nested input channel
* along with the {@link BlockingQueue} returned by {@link #getNestedChannelRequests}, there exists some {@link Thread}
* or {@link Selector} currently polling for data, and there is any data is available, then at least one must become
* readable and provide new data.
*
*
* Recursive Nesting
*
*
* An inner {@link MuxableChannel} may be created from a nested channel that itself was created from an outer
* {@link MuxableChannel}. This process may be repeated with arbitrary levels of nesting. As always, the closure
* of any channel forces the closure of all of its nested channels, and ultimately closing all of its descendents.
*
* @param input channel type
* @param output channel type
*/
public interface MuxableChannel<
I extends SelectableChannel & ReadableByteChannel,
O extends SelectableChannel & WritableByteChannel>
extends Channel {
/**
* Create a new nested channel, or pair of nested channels, scoped to this instance.
*
*
* The remote side will be notified by the appearance of a corresponding {@link NestedChannel} in the queue
* returned by {@link #getNestedChannelRequests} (with the input and output channels reversed, of course).
* The delivery of requests on the remote side is guaranteed to preserve order (but only to the extent order
* is well-defined on the sending side, i.e., there is a "happens before" relationship).
*
* @param requestData data to supply to the remote side (via {@link NestedChannel#getRequestData})
* @param directions which of input and/or output to create
* @return the newly created nested channel(s)
* @throws IOException if an I/O error occurs
* @throws IllegalArgumentException if either parameter is null
*/
NestedChannel newNestedChannel(ByteBuffer requestData, Directions directions) throws IOException;
/**
* Create a pair of nested input and output channels scoped to this instance.
*
*
* Equivalent to: {@link #newNestedChannel(ByteBuffer, Directions)
* newNestedChannel(requestData, Directions.BIDIRECTIONAL)}.
*
* @param requestData data to supply to the remote side (via {@link NestedChannel#getRequestData})
* @return the newly created nested channel(s)
* @throws IOException if an I/O error occurs
* @throws IllegalArgumentException if {@code requestData} is null
*/
default NestedChannel newNestedChannel(ByteBuffer requestData) throws IOException {
return this.newNestedChannel(requestData, Directions.BIDIRECTIONAL);
}
/**
* Access the queue of incoming {@link NestedChannel}s initiated from the remote side.
*
*
* Each {@link NestedChannel} corresponds to a remote invocation of
* {@link #newNestedChannel newNestedChannel()}. Moreover, the order of requests is preserved.
*
*
* Because the remote side can close a nested channel at any time, it is possible that the new channel associated
* with an incoming {@link NestedChannel} has already been closed in a subsequent message from the remote peer
* by the time the original {@link NestedChannel} is pulled from this queue. In such a case, the nested channel's
* input and output streams will throw {@link IOException} on first access.
*
*
* Note: no special change happens to the returned {@link BlockingQueue} once this instance is closed;
* instead, new requests simply stop appearing. Therefore, after closing this instance, any thread(s)
* that are blocked polling for new data may need to be woken up via {@link Thread#interrupt}.
*
* @return queue of {@link NestedChannel} initiated from the remote side
*/
BlockingQueue extends NestedChannel> getNestedChannelRequests();
/**
* Close this instance.
*
*
* All outstanding nested channels are also implicitly closed.
*
* @throws IOException if an I/O error occurs
*/
@Override
void close() throws IOException;
}