
zmq.io.SessionBase Maven / Gradle / Ivy
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 - 2025 Weber Informatics LLC | Privacy Policy