zmq.pipe.Pipe Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jeromq Show documentation
Show all versions of jeromq Show documentation
Pure Java implementation of libzmq
package zmq.pipe;
import zmq.Config;
import zmq.Msg;
import zmq.ZObject;
import zmq.util.Blob;
// Note that pipe can be stored in three different arrays.
// The array of inbound pipes (1), the array of outbound pipes (2) and
// the generic array of pipes to deallocate (3).
public class Pipe extends ZObject
{
public interface IPipeEvents
{
void readActivated(Pipe pipe);
void writeActivated(Pipe pipe);
void hiccuped(Pipe pipe);
void pipeTerminated(Pipe pipe);
}
// Underlying pipes for both directions.
private YPipeBase inpipe;
private YPipeBase outpipe;
// Can the pipe be read from / written to?
private boolean inActive;
private boolean outActive;
// High watermark for the outbound pipe.
private int hwm;
// Low watermark for the inbound pipe.
private int lwm;
// Number of messages read and written so far.
private long msgsRead;
private long msgsWritten;
// Last received peer's msgsRead. The actual number in the peer
// can be higher at the moment.
private long peersMsgsRead;
// The pipe object on the other side of the pipepair.
private Pipe peer;
// Sink to send events to.
private IPipeEvents sink;
// States of the pipe endpoint:
// active: common state before any termination begins,
// delimiter_received: delimiter was read from pipe before
// term command was received,
// waiting_fo_delimiter: term command was already received
// from the peer but there are still pending messages to read,
// term_ack_sent: all pending messages were already read and
// all we are waiting for is ack from the peer,
// term_req_sent1: 'terminate' was explicitly called by the user,
// term_req_sent2: user called 'terminate' and then we've got
// term command from the peer as well.
enum State
{
ACTIVE,
DELIMITER_RECEIVED,
WAITING_FOR_DELIMITER,
TERM_ACK_SENT,
TERM_REQ_SENT_1,
TERM_REQ_SENT_2
}
private State state;
// If true, we receive all the pending inbound messages before
// terminating. If false, we terminate immediately when the peer
// asks us to.
private boolean delay;
// Identity of the writer. Used uniquely by the reader side.
private Blob identity;
// Pipe's credential.
private Blob credential;
private final boolean conflate;
// JeroMQ only
private final ZObject parent;
// Constructor is private. Pipe can only be created using
// pipepair function.
private Pipe(ZObject parent, YPipeBase inpipe, YPipeBase outpipe, int inhwm, int outhwm, boolean conflate)
{
super(parent);
this.inpipe = inpipe;
this.outpipe = outpipe;
inActive = true;
outActive = true;
hwm = outhwm;
lwm = computeLwm(inhwm);
msgsRead = 0;
msgsWritten = 0;
peersMsgsRead = 0;
peer = null;
sink = null;
state = State.ACTIVE;
this.delay = true;
this.conflate = conflate;
this.parent = parent;
}
// This allows pipepair to create pipe objects.
// Create a pipepair for bi-directional transfer of messages.
// First HWM is for messages passed from first pipe to the second pipe.
// Second HWM is for messages passed from second pipe to the first pipe.
// Conflate specifies how the pipe behaves when the peer terminates. If true
// pipe receives all the pending messages before terminating, otherwise it
// terminates straight away.
public static Pipe[] pair(ZObject[] parents, int[] hwms, boolean[] conflates)
{
Pipe[] pipes = new Pipe[2];
// Creates two pipe objects. These objects are connected by two ypipes,
// each to pass messages in one direction.
YPipeBase upipe1 = conflates[0] ? new YPipeConflate<>()
: new YPipe(Config.MESSAGE_PIPE_GRANULARITY.getValue());
YPipeBase upipe2 = conflates[1] ? new YPipeConflate<>()
: new YPipe(Config.MESSAGE_PIPE_GRANULARITY.getValue());
pipes[0] = new Pipe(parents[0], upipe1, upipe2, hwms[1], hwms[0], conflates[0]);
pipes[1] = new Pipe(parents[1], upipe2, upipe1, hwms[0], hwms[1], conflates[1]);
pipes[0].setPeer(pipes[1]);
pipes[1].setPeer(pipes[0]);
return pipes;
}
// Pipepair uses this function to let us know about
// the peer pipe object.
private void setPeer(Pipe peer)
{
// Peer can be set once only.
assert (this.peer == null);
assert (peer != null);
this.peer = peer;
}
// Specifies the object to send events to.
public void setEventSink(IPipeEvents sink)
{
assert (this.sink == null);
this.sink = sink;
}
// Pipe endpoint can store an opaque ID to be used by its clients.
public void setIdentity(Blob identity)
{
this.identity = identity;
}
public Blob getIdentity()
{
return identity;
}
public Blob getCredential()
{
return credential;
}
// Returns true if there is at least one message to read in the pipe.
public boolean checkRead()
{
if (!inActive) {
return false;
}
if (state != State.ACTIVE && state != State.WAITING_FOR_DELIMITER) {
return false;
}
// Check if there's an item in the pipe.
if (!inpipe.checkRead()) {
inActive = false;
return false;
}
// If the next item in the pipe is message delimiter,
// initiate termination process.
if (isDelimiter(inpipe.probe())) {
Msg msg = inpipe.read();
assert (msg != null);
processDelimiter();
return false;
}
return true;
}
// Reads a message to the underlying pipe.
public Msg read()
{
if (!inActive) {
return null;
}
if (state != State.ACTIVE && state != State.WAITING_FOR_DELIMITER) {
return null;
}
while (true) {
Msg msg = inpipe.read();
if (msg == null) {
inActive = false;
return null;
}
// If this is a credential, save a copy and receive next message.
if (msg.isCredential()) {
credential = Blob.createBlob(msg);
continue;
}
// If delimiter was read, start termination process of the pipe.
if (msg.isDelimiter()) {
processDelimiter();
return null;
}
if (!msg.hasMore() && !msg.isIdentity()) {
msgsRead++;
}
if (lwm > 0 && msgsRead % lwm == 0) {
sendActivateWrite(peer, msgsRead);
}
return msg;
}
}
// Checks whether messages can be written to the pipe. If writing
// the message would cause high watermark the function returns false.
public boolean checkWrite()
{
if (!outActive || state != State.ACTIVE) {
return false;
}
// TODO DIFF V4 small change, it is done like this in 4.2.2
boolean full = !checkHwm();
if (full) {
outActive = false;
return false;
}
return true;
}
// Writes a message to the underlying pipe. Returns false if the
// message cannot be written because high watermark was reached.
public boolean write(Msg msg)
{
if (!checkWrite()) {
return false;
}
boolean more = msg.hasMore();
boolean identity = msg.isIdentity();
outpipe.write(msg, more);
if (!more && !identity) {
msgsWritten++;
}
return true;
}
// Remove unfinished parts of the outbound message from the pipe.
public void rollback()
{
// Remove incomplete message from the outbound pipe.
Msg msg;
if (outpipe != null) {
while ((msg = outpipe.unwrite()) != null) {
assert (msg.hasMore());
}
}
}
// Flush the messages downstream.
public void flush()
{
// The peer does not exist anymore at this point.
if (state == State.TERM_ACK_SENT) {
return;
}
if (outpipe != null && !outpipe.flush()) {
sendActivateRead(peer);
}
}
@Override
protected void processActivateRead()
{
if (!inActive && (state == State.ACTIVE || state == State.WAITING_FOR_DELIMITER)) {
inActive = true;
sink.readActivated(this);
}
}
@Override
protected void processActivateWrite(long msgsRead)
{
// Remember the peers's message sequence number.
peersMsgsRead = msgsRead;
if (!outActive && state == State.ACTIVE) {
outActive = true;
sink.writeActivated(this);
}
}
@Override
protected void processHiccup(YPipeBase pipe)
{
// Destroy old outpipe. Note that the read end of the pipe was already
// migrated to this thread.
assert (outpipe != null);
outpipe.flush();
Msg msg;
while ((msg = outpipe.read()) != null) {
if (!msg.hasMore()) {
msgsWritten--;
}
}
// Plug in the new outpipe.
assert (pipe != null);
outpipe = pipe;
outActive = true;
// If appropriate, notify the user about the hiccup.
if (state == State.ACTIVE) {
sink.hiccuped(this);
}
}
@Override
protected void processPipeTerm()
{
assert (state == State.ACTIVE || state == State.DELIMITER_RECEIVED || state == State.TERM_REQ_SENT_1);
// This is the simple case of peer-induced termination. If there are no
// more pending messages to read, or if the pipe was configured to drop
// pending messages, we can move directly to the term_ack_sent state.
// Otherwise we'll hang up in waiting_for_delimiter state till all
// pending messages are read.
if (state == State.ACTIVE) {
if (delay) {
state = State.WAITING_FOR_DELIMITER;
}
else {
state = State.TERM_ACK_SENT;
outpipe = null;
sendPipeTermAck(peer);
}
}
else
// Delimiter happened to arrive before the term command. Now we have the
// term command as well, so we can move straight to term_ack_sent state.
if (state == State.DELIMITER_RECEIVED) {
state = State.TERM_ACK_SENT;
outpipe = null;
sendPipeTermAck(peer);
}
else
// This is the case where both ends of the pipe are closed in parallel.
// We simply reply to the request by ack and continue waiting for our
// own ack.
if (state == State.TERM_REQ_SENT_1) {
state = State.TERM_REQ_SENT_2;
outpipe = null;
sendPipeTermAck(peer);
}
}
@Override
protected void processPipeTermAck()
{
// Notify the user that all the references to the pipe should be dropped.
assert (sink != null);
sink.pipeTerminated(this);
// In term_ack_sent and term_req_sent2 states there's nothing to do.
// Simply deallocate the pipe. In term_req_sent1 state we have to ack
// the peer before deallocating this side of the pipe.
// All the other states are invalid.
if (state == State.TERM_REQ_SENT_1) {
outpipe = null;
sendPipeTermAck(peer);
}
else {
assert (state == State.TERM_ACK_SENT || state == State.TERM_REQ_SENT_2);
}
// TODO V4 not in zeromq, but no harm. Remove it?
// If the inbound pipe has already been deallocated, then we're done.
if (inpipe == null) {
return;
}
// We'll deallocate the inbound pipe, the peer will deallocate the outbound
// pipe (which is an inbound pipe from its point of view).
// First, delete all the unread messages in the pipe. We have to do it by
// hand because msg_t doesn't have automatic destructor. Then deallocate
// the ypipe itself.
if (!conflate) {
while (inpipe.read() != null) {
// do nothing
}
}
// Deallocate the pipe object
inpipe = null;
}
public void setNoDelay()
{
this.delay = false;
}
// Ask pipe to terminate. The termination will happen asynchronously
// and user will be notified about actual deallocation by 'terminated'
// event. If delay is true, the pending messages will be processed
// before actual shutdown.
public void terminate(boolean delay)
{
// Overload the value specified at pipe creation.
this.delay = delay;
// If terminate was already called, we can ignore the duplicit invocation.
if (state == State.TERM_REQ_SENT_1 || state == State.TERM_REQ_SENT_2) {
return;
}
// If the pipe is in the final phase of async termination, it's going to
// closed anyway. No need to do anything special here.
else if (state == State.TERM_ACK_SENT) {
return;
}
// The simple sync termination case. Ask the peer to terminate and wait
// for the ack.
else if (state == State.ACTIVE) {
sendPipeTerm(peer);
state = State.TERM_REQ_SENT_1;
}
// There are still pending messages available, but the user calls
// 'terminate'. We can act as if all the pending messages were read.
else if (state == State.WAITING_FOR_DELIMITER && !this.delay) {
outpipe = null;
sendPipeTermAck(peer);
state = State.TERM_ACK_SENT;
}
// If there are pending messages still available, do nothing.
else if (state == State.WAITING_FOR_DELIMITER) {
// do nothing
}
// We've already got delimiter, but not term command yet. We can ignore
// the delimiter and ack synchronously terminate as if we were in
// active state.
else if (state == State.DELIMITER_RECEIVED) {
sendPipeTerm(peer);
state = State.TERM_REQ_SENT_1;
}
// There are no other states.
else {
assert (false);
}
// Stop outbound flow of messages.
outActive = false;
if (outpipe != null) {
// Drop any unfinished outbound messages.
rollback();
// Write the delimiter into the pipe. Note that watermarks are not
// checked; thus the delimiter can be written even when the pipe is full.
Msg msg = new Msg();
msg.initDelimiter();
outpipe.write(msg, false);
flush();
}
}
// Returns true if the message is delimiter; false otherwise.
private static boolean isDelimiter(Msg msg)
{
return msg.isDelimiter();
}
// Computes appropriate low watermark from the given high watermark.
private static int computeLwm(int hwm)
{
// Compute the low water mark. Following point should be taken
// into consideration:
//
// 1. LWM has to be less than HWM.
// 2. LWM cannot be set to very low value (such as zero) as after filling
// the queue it would start to refill only after all the messages are
// read from it and thus unnecessarily hold the progress back.
// 3. LWM cannot be set to very high value (such as HWM-1) as it would
// result in lock-step filling of the queue - if a single message is
// read from a full queue, writer thread is resumed to write exactly one
// message to the queue and go back to sleep immediately. This would
// result in low performance.
//
// Given the 3. it would be good to keep HWM and LWM as far apart as
// possible to reduce the thread switching overhead to almost zero,
// say HWM-LWM should be max_wm_delta.
//
// That done, we still we have to account for the cases where
// HWM < max_wm_delta thus driving LWM to negative numbers.
// Let's make LWM 1/2 of HWM in such cases.
// return (hwm +1) /2;
return (hwm + 1) / 2;
}
// Handler for delimiter read from the pipe.
private void processDelimiter()
{
assert (state == State.ACTIVE || state == State.WAITING_FOR_DELIMITER);
if (state == State.ACTIVE) {
state = State.DELIMITER_RECEIVED;
}
else {
outpipe = null;
sendPipeTermAck(peer);
state = State.TERM_ACK_SENT;
}
}
// Temporarily disconnects the inbound message stream and drops
// all the messages on the fly. Causes 'hiccuped' event to be generated
// in the peer.
public void hiccup()
{
// If termination is already under way do nothing.
if (state != State.ACTIVE) {
return;
}
// We'll drop the pointer to the inpipe. From now on, the peer is
// responsible for deallocating it.
inpipe = null;
// Create new inpipe.
if (conflate) {
inpipe = new YPipeConflate<>();
}
else {
inpipe = new YPipe<>(Config.MESSAGE_PIPE_GRANULARITY.getValue());
}
inActive = true;
// Notify the peer about the hiccup.
sendHiccup(peer, inpipe);
}
public void setHwms(int inhwm, int outhwm)
{
lwm = computeLwm(inhwm);
hwm = outhwm;
}
public boolean checkHwm()
{
// TODO DIFF V4 small change, it is done like this in 4.2.2
boolean full = hwm > 0 && (msgsWritten - peersMsgsRead) >= hwm;
return !full;
}
@Override
public String toString()
{
return super.toString() + "(" + parent.getClass().getSimpleName() + "[" + parent.getTid() + "]->"
+ peer.parent.getClass().getSimpleName() + "[" + peer.parent.getTid() + "])";
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy