zmq.io.SessionBase 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
The newest version!
package zmq.io;
import java.util.HashSet;
import java.util.Set;
import zmq.Ctx;
import zmq.Msg;
import zmq.Options;
import zmq.Own;
import zmq.SocketBase;
import zmq.ZError;
import zmq.ZMQ;
import zmq.ZObject;
import zmq.io.StreamEngine.ErrorReason;
import zmq.io.mechanism.Mechanisms;
import zmq.io.net.Address;
import zmq.io.net.NetProtocol;
import zmq.io.net.ipc.IpcConnecter;
import zmq.io.net.norm.NormEngine;
import zmq.io.net.pgm.PgmReceiver;
import zmq.io.net.pgm.PgmSender;
import zmq.io.net.tcp.SocksConnecter;
import zmq.io.net.tcp.TcpConnecter;
import zmq.io.net.tipc.TipcConnecter;
import zmq.pipe.Pipe;
import zmq.poll.IPollEvents;
public class SessionBase extends Own implements Pipe.IPipeEvents, IPollEvents
{
// If true, this session (re)connects to the peer. Otherwise, it's
// a transient session created by the listener.
private final boolean active;
// Pipe connecting the session to its socket.
private Pipe pipe;
// Pipe used to exchange messages with ZAP socket.
private Pipe zapPipe;
// This set is added to with pipes we are disconnecting, but haven't yet completed
private final Set terminatingPipes;
// This flag is true if the remainder of the message being processed
// is still in the in pipe.
private boolean incompleteIn;
// True if termination have been suspended to push the pending
// messages to the network.
private boolean pending;
// The protocol I/O engine connected to the session.
private IEngine engine;
// The socket the session belongs to.
protected final SocketBase socket;
// I/O thread the session is living in. It will be used to plug in
// the engines into the same thread.
private final IOThread ioThread;
// ID of the linger timer
private static final int LINGER_TIMER_ID = 0x20;
// True is linger timer is running.
private boolean hasLingerTimer;
// Protocol and address to use when connecting.
private final Address addr;
private final IOObject ioObject;
public SessionBase(IOThread ioThread, boolean connect, SocketBase socket, Options options, Address addr)
{
super(ioThread, options);
ioObject = new IOObject(ioThread, this);
this.active = connect;
pipe = null;
zapPipe = null;
incompleteIn = false;
pending = false;
engine = null;
this.socket = socket;
this.ioThread = ioThread;
hasLingerTimer = false;
this.addr = addr;
terminatingPipes = new HashSet<>();
}
@Override
public void destroy()
{
assert (pipe == null);
assert (zapPipe == null);
// If there's still a pending linger timer, remove it.
if (hasLingerTimer) {
ioObject.cancelTimer(LINGER_TIMER_ID);
hasLingerTimer = false;
}
// Close the engine.
if (engine != null) {
engine.terminate();
}
ioObject.unplug();
}
// To be used once only, when creating the session.
public void attachPipe(Pipe pipe)
{
assert (!isTerminating());
assert (this.pipe == null);
assert (pipe != null);
this.pipe = pipe;
this.pipe.setEventSink(this);
}
protected Msg pullMsg()
{
if (pipe == null) {
return null;
}
Msg msg = pipe.read();
if (msg == null) {
return null;
}
incompleteIn = msg.hasMore();
return msg;
}
protected boolean pushMsg(Msg msg)
{
if (msg.isCommand()) {
return true;
}
if (pipe != null && pipe.write(msg)) {
return true;
}
errno.set(ZError.EAGAIN);
return false;
}
public Msg readZapMsg()
{
if (zapPipe == null) {
errno.set(ZError.ENOTCONN);
return null;
}
Msg msg = zapPipe.read();
if (msg == null) {
errno.set(ZError.EAGAIN);
}
return msg;
}
public boolean writeZapMsg(Msg msg)
{
if (zapPipe == null) {
errno.set(ZError.ENOTCONN);
return false;
}
boolean rc = zapPipe.write(msg);
assert (rc);
if (!msg.hasMore()) {
zapPipe.flush();
}
return true;
}
protected void reset()
{
}
public void flush()
{
if (pipe != null) {
pipe.flush();
}
}
// Remove any half processed messages. Flush unflushed messages.
// Call this function when engine disconnect to get rid of leftovers.
private void cleanPipes()
{
assert (pipe != null);
// Get rid of half-processed messages in the out pipe. Flush any
// unflushed messages upstream.
pipe.rollback();
pipe.flush();
// Remove any half-read message from the in pipe.
while (incompleteIn) {
Msg msg = pullMsg();
if (msg == null) {
assert (!incompleteIn);
break;
}
// msg.close ();
}
}
@Override
public void pipeTerminated(Pipe pipe)
{
// Drop the reference to the deallocated pipe.
assert (this.pipe == pipe || this.zapPipe == pipe || terminatingPipes.contains(pipe));
if (this.pipe == pipe) {
// If this is our current pipe, remove it
this.pipe = null;
if (hasLingerTimer) {
ioObject.cancelTimer(LINGER_TIMER_ID);
hasLingerTimer = false;
}
}
else if (zapPipe == pipe) {
zapPipe = null;
}
else {
// Remove the pipe from the detached pipes set
terminatingPipes.remove(pipe);
}
if (!isTerminating() && options.rawSocket) {
if (engine != null) {
engine.terminate();
engine = null;
}
}
// If we are waiting for pending messages to be sent, at this point
// we are sure that there will be no more messages and we can proceed
// with termination safely.
if (pending && this.pipe == null && this.zapPipe == null && terminatingPipes.isEmpty()) {
pending = false;
super.processTerm(0);
}
}
@Override
public void readActivated(Pipe pipe)
{
// Skip activating if we're detaching this pipe
if (this.pipe != pipe && this.zapPipe != pipe) {
assert (terminatingPipes.contains(pipe));
return;
}
if (engine == null) {
this.pipe.checkRead();
return;
}
if (this.pipe == pipe) {
engine.restartOutput();
}
else {
engine.zapMsgAvailable();
}
}
@Override
public void writeActivated(Pipe pipe)
{
// Skip activating if we're detaching this pipe
if (this.pipe != pipe) {
assert (terminatingPipes.contains(pipe));
return;
}
if (engine != null) {
engine.restartInput();
}
}
@Override
public void hiccuped(Pipe pipe)
{
// Hiccups are always sent from session to socket, not the other
// way round.
throw new UnsupportedOperationException("Must Override");
}
public SocketBase getSocket()
{
return socket;
}
@Override
protected void processPlug()
{
ioObject.plug();
if (active) {
startConnecting(false);
}
}
public int zapConnect()
{
// Session might be reused with zap connexion already established, don't panic
if (zapPipe == null) {
Ctx.Endpoint peer = findEndpoint("inproc://zeromq.zap.01");
if (peer.socket == null) {
errno.set(ZError.ECONNREFUSED);
return ZError.ECONNREFUSED;
}
if (peer.options.type != ZMQ.ZMQ_REP && peer.options.type != ZMQ.ZMQ_ROUTER &&
peer.options.type != ZMQ.ZMQ_SERVER) {
errno.set(ZError.ECONNREFUSED);
return ZError.ECONNREFUSED;
}
// Create a bi-directional pipe that will connect
// session with zap socket.
ZObject[] parents = { this, peer.socket };
int[] hwms = { 0, 0 };
boolean[] conflates = { false, false };
Pipe[] pipes = Pipe.pair(parents, hwms, conflates);
// Attach local end of the pipe to this socket object.
zapPipe = pipes[0];
zapPipe.setNoDelay();
zapPipe.setEventSink(this);
sendBind(peer.socket, pipes[1], false);
// Send empty identity if required by the peer.
if (peer.options.recvIdentity) {
Msg id = new Msg();
id.setFlags(Msg.IDENTITY);
zapPipe.write(id);
zapPipe.flush();
}
}
return 0;
}
protected boolean zapEnabled()
{
return options.mechanism != Mechanisms.NULL || (options.zapDomain != null && !options.zapDomain.isEmpty());
}
@Override
protected void processAttach(IEngine engine)
{
assert (engine != null);
// Create the pipe if it does not exist yet.
if (pipe == null && !isTerminating()) {
ZObject[] parents = { this, socket };
boolean conflate = options.conflate && (options.type == ZMQ.ZMQ_DEALER || options.type == ZMQ.ZMQ_PULL
|| options.type == ZMQ.ZMQ_PUSH || options.type == ZMQ.ZMQ_PUB || options.type == ZMQ.ZMQ_SUB);
int[] hwms = { conflate ? -1 : options.recvHwm, conflate ? -1 : options.sendHwm };
boolean[] conflates = { conflate, conflate };
Pipe[] pipes = Pipe.pair(parents, hwms, conflates);
// Plug the local end of the pipe.
pipes[0].setEventSink(this);
// Remember the local end of the pipe.
assert (pipe == null);
pipe = pipes[0];
// Ask socket to plug into the remote end of the pipe.
sendBind(socket, pipes[1]);
}
// Plug in the engine.
assert (this.engine == null);
this.engine = engine;
this.engine.plug(ioThread, this);
}
public void engineError(boolean handshaked, ErrorReason reason)
{
// Engine is dead. Let's forget about it.
engine = null;
// Remove any half-done messages from the pipes.
if (pipe != null) {
cleanPipes();
// Only send disconnect message if socket was accepted and handshake was completed
if (!active && handshaked && options.canReceiveDisconnectMsg && options.disconnectMsg != null) {
pipe.setDisconnectMsg(options.disconnectMsg);
pipe.sendDisconnectMsg();
}
if (active && handshaked && options.canReceiveHiccupMsg && options.hiccupMsg != null) {
pipe.sendHiccupMsg(options.hiccupMsg);
}
}
assert (reason == ErrorReason.CONNECTION || reason == ErrorReason.TIMEOUT || reason == ErrorReason.PROTOCOL);
switch (reason) {
case TIMEOUT:
case CONNECTION:
if (active) {
reconnect();
}
else {
terminate();
}
break;
case PROTOCOL:
terminate();
break;
default:
break;
}
// Just in case there's only a delimiter in the pipe.
if (pipe != null) {
pipe.checkRead();
}
if (zapPipe != null) {
zapPipe.checkRead();
}
}
@Override
protected void processTerm(int linger)
{
assert (!pending);
// If the termination of the pipe happens before the term command is
// delivered there's nothing much to do. We can proceed with the
// standard termination immediately.
if (pipe == null && zapPipe == null && terminatingPipes.isEmpty()) {
super.processTerm(0);
return;
}
pending = true;
if (pipe != null) {
// If there's finite linger value, delay the termination.
// If linger is infinite (negative) we don't even have to set
// the timer.
if (linger > 0) {
assert (!hasLingerTimer);
ioObject.addTimer(linger, LINGER_TIMER_ID);
hasLingerTimer = true;
}
// Start pipe termination process. Delay the termination till all messages
// are processed in case the linger time is non-zero.
pipe.terminate(linger != 0);
// TODO: Should this go into pipe_t::terminate ?
// In case there's no engine and there's only delimiter in the
// pipe it wouldn't be ever read. Thus we check for it explicitly.
if (engine == null) {
pipe.checkRead();
}
}
if (zapPipe != null) {
zapPipe.terminate(false);
}
}
@Override
public void timerEvent(int id)
{
// Linger period expired. We can proceed with termination even though
// there are still pending messages to be sent.
assert (id == LINGER_TIMER_ID);
hasLingerTimer = false;
// Ask pipe to terminate even though there may be pending messages in it.
assert (pipe != null);
pipe.terminate(false);
}
private void reconnect()
{
// TODO V4 - // Transient session self-destructs after peer disconnects. ?
// For delayed connect situations, terminate the pipe
// and reestablish later on
if (pipe != null && !options.immediate && !addr.protocol().isMulticast) {
pipe.hiccup();
pipe.terminate(false);
terminatingPipes.add(pipe);
pipe = null;
}
reset();
// Reconnect.
if (options.reconnectIvl != -1) {
startConnecting(true);
}
// For subscriber sockets we hiccup the inbound pipe, which will cause
// the socket object to resend all the subscriptions.
if (pipe != null && (options.type == ZMQ.ZMQ_SUB || options.type == ZMQ.ZMQ_XSUB)) {
pipe.hiccup();
}
}
private void startConnecting(boolean wait)
{
assert (active);
// Choose I/O thread to run connecter in. Given that we are already
// running in an I/O thread, there must be at least one available.
IOThread ioThread = chooseIoThread(options.affinity);
assert (ioThread != null);
// Create the connecter object.
NetProtocol protocol = addr.protocol();
if (protocol == null) {
errno.set(ZError.EPROTONOSUPPORT);
return;
}
switch (protocol) {
case tcp:
if (options.socksProxyAddress != null) {
Address proxyAddress = new Address(NetProtocol.tcp, options.socksProxyAddress);
SocksConnecter connecter = new SocksConnecter(ioThread, this, options, addr, proxyAddress, wait);
launchChild(connecter);
}
else {
TcpConnecter connecter = new TcpConnecter(ioThread, this, options, addr, wait);
launchChild(connecter);
}
break;
case ipc: {
IpcConnecter connecter = new IpcConnecter(ioThread, this, options, addr, wait);
launchChild(connecter);
}
break;
case tipc: {
TipcConnecter connecter = new TipcConnecter(ioThread, this, options, addr, wait);
launchChild(connecter);
}
break;
case pgm:
case epgm: {
assert (options.type == ZMQ.ZMQ_PUB || options.type == ZMQ.ZMQ_XPUB || options.type == ZMQ.ZMQ_SUB
|| options.type == ZMQ.ZMQ_XSUB);
// For EPGM transport with UDP encapsulation of PGM is used.
boolean udpEncapsulation = protocol == NetProtocol.epgm;
// At this point we'll create message pipes to the session straight
// away. There's no point in delaying it as no concept of 'connect'
// exists with PGM anyway.
if (options.type == ZMQ.ZMQ_PUB || options.type == ZMQ.ZMQ_XPUB) {
// PGM sender.
PgmSender pgmSender = new PgmSender(ioThread, options);
boolean rc = pgmSender.init(udpEncapsulation, addr);
assert (rc);
sendAttach(this, pgmSender);
}
else {
// PGM receiver.
PgmReceiver pgmReceiver = new PgmReceiver(ioThread, options);
boolean rc = pgmReceiver.init(udpEncapsulation, addr);
assert (rc);
sendAttach(this, pgmReceiver);
}
}
break;
case norm: {
// At this point we'll create message pipes to the session straight
// away. There's no point in delaying it as no concept of 'connect'
// exists with NORM anyway.
if (options.type == ZMQ.ZMQ_PUB || options.type == ZMQ.ZMQ_XPUB) {
// NORM sender.
NormEngine normSender = new NormEngine(ioThread, options);
boolean rc = normSender.init(addr, true, false);
assert (rc);
sendAttach(this, normSender);
}
else {
// NORM receiver.
NormEngine normReceiver = new NormEngine(ioThread, options);
boolean rc = normReceiver.init(addr, false, true);
assert (rc);
sendAttach(this, normReceiver);
}
}
break;
default:
assert (false);
break;
}
}
public String getEndpoint()
{
return engine.getEndPoint();
}
@Override
public String toString()
{
return getClass().getSimpleName() + "-" + socket;
}
@Override
public final void incSeqnum()
{
super.incSeqnum();
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy