zmq.SocketBase 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;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SocketChannel;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;
import zmq.io.IOThread;
import zmq.io.SessionBase;
import zmq.io.net.Address;
import zmq.io.net.Address.IZAddress;
import zmq.io.net.Listener;
import zmq.io.net.NetProtocol;
import zmq.pipe.Pipe;
import zmq.poll.IPollEvents;
import zmq.poll.Poller;
import zmq.socket.Sockets;
import zmq.util.Blob;
import zmq.util.Clock;
import zmq.util.MultiMap;
public abstract class SocketBase extends Own implements IPollEvents, Pipe.IPipeEvents
{
private static class EndpointPipe
{
private final Own endpoint;
private final Pipe pipe;
public EndpointPipe(Own endpoint, Pipe pipe)
{
super();
this.endpoint = endpoint;
this.pipe = pipe;
}
@Override
public String toString()
{
return "EndpointPipe [endpoint=" + endpoint + ", pipe=" + pipe + "]";
}
}
/**
* The old consumer that forward events through a socket
*/
private static class SocketEventHandler implements ZMQ.EventConsummer
{
private final SocketBase monitorSocket;
public SocketEventHandler(SocketBase monitorSocket)
{
this.monitorSocket = monitorSocket;
}
public void consume(ZMQ.Event ev)
{
ev.write(monitorSocket);
}
@Override
public void close()
{
monitorSocket.close();
}
}
// Map of open endpoints.
private final MultiMap endpoints;
// Map of open inproc endpoints.
private final MultiMap inprocs;
// Used to check whether the object is a socket.
private boolean active;
// If true, associated context was already terminated.
private final AtomicBoolean ctxTerminated;
// If processCommand function was called from InEvent function.
private final ThreadLocal isInEventThreadLocal;
// If true, object should have been already destroyed. However,
// destruction is delayed while we unwind the stack to the point
// where it doesn't intersect the object being destroyed.
private final AtomicBoolean destroyed;
// Socket's mailbox object.
private final IMailbox mailbox;
// the attached pipes.
private final Set pipes;
// Reaper's poller and handle of this socket within it.
private Poller poller;
private Poller.Handle handle;
// Timestamp of when commands were processed the last time.
private long lastTsc;
// Number of messages received since last command processing.
private int ticks;
// True if the last message received had MORE flag set.
private boolean rcvmore;
// File descriptor if applicable
private SocketChannel fileDesc;
// Bitmask of events being monitored
private int monitorEvents;
// Next assigned name on a zmq_connect() call used by ROUTER and STREAM socket types
protected String connectRid;
private final AtomicReference monitor;
// Indicate if the socket is thread safe
private final boolean threadSafe;
// Mutex for synchronize access to the socket in thread safe mode
private final ReentrantLock threadSafeSync;
protected SocketBase(Ctx parent, int tid, int sid)
{
this(parent, tid, sid, false);
}
protected SocketBase(Ctx parent, int tid, int sid, boolean threadSafe)
{
super(parent, tid);
active = true;
ctxTerminated = new AtomicBoolean();
isInEventThreadLocal = new ThreadLocal<>();
destroyed = new AtomicBoolean();
lastTsc = 0;
ticks = 0;
rcvmore = false;
monitorEvents = 0;
monitor = new AtomicReference<>(null);
options.socketId = sid;
options.ipv6 = parent.get(ZMQ.ZMQ_IPV6) != 0;
options.linger = parent.get(ZMQ.ZMQ_BLOCKY) != 0 ? -1 : 0;
endpoints = new MultiMap<>();
inprocs = new MultiMap<>();
pipes = new HashSet<>();
this.threadSafe = threadSafe;
this.threadSafeSync = new ReentrantLock();
mailbox = new Mailbox(parent, "socket-" + sid, tid);
}
// Concrete algorithms for the x- methods are to be defined by
// individual socket types.
protected abstract void xattachPipe(Pipe pipe, boolean subscribe2all, boolean isLocallyInitiated);
protected abstract void xpipeTerminated(Pipe pipe);
/**
* @return false if object is not a socket.
*/
boolean isActive()
{
return active;
}
@Override
protected void destroy()
{
synchronized (monitor) {
try {
mailbox.close();
}
catch (IOException ignore) {
}
stopMonitor();
assert (destroyed.get());
}
}
// Returns the mailbox associated with this socket.
final IMailbox getMailbox()
{
return mailbox;
}
// Interrupt blocking call if the socket is stuck in one.
// This function can be called from a different thread!
final void stop()
{
// Called by ctx when it is terminated (zmq_term).
// 'stop' command is sent from the threads that called zmq_term to
// the thread owning the socket. This way, blocking call in the
// owner thread can be interrupted.
sendStop();
}
// Check whether transport protocol, as specified in connect or
// bind, is available and compatible with the socket type.
private NetProtocol checkProtocol(String protocol)
{
try {
// First check out whether the protcol is something we are aware of.
NetProtocol proto = NetProtocol.getProtocol(protocol);
if (!proto.valid) {
errno.set(ZError.EPROTONOSUPPORT);
return proto;
}
// Check whether socket type and transport protocol match.
// Specifically, multicast protocols can't be combined with
// bi-directional messaging patterns (socket types).
if (!proto.compatible(options.type)) {
errno.set(ZError.ENOCOMPATPROTO);
return null;
}
// Protocol is available.
return proto;
}
catch (IllegalArgumentException e) {
errno.set(ZError.EPROTONOSUPPORT);
return null;
}
}
// Register the pipe with this socket.
private void attachPipe(Pipe pipe, boolean isLocallyInitiated)
{
attachPipe(pipe, false, isLocallyInitiated);
}
private void attachPipe(Pipe pipe, boolean subscribe2all, boolean isLocallyInitiated)
{
assert (pipe != null);
// First, register the pipe so that we can terminate it later on.
pipe.setEventSink(this);
pipes.add(pipe);
// Let the derived socket type know about new pipe.
xattachPipe(pipe, subscribe2all, isLocallyInitiated);
// If the socket is already being closed, ask any new pipes to terminate
// straight away.
if (isTerminating()) {
registerTermAcks(1);
pipe.terminate(false);
}
}
public final boolean setSocketOpt(int option, Object optval)
{
lock();
try {
if (ctxTerminated.get()) {
errno.set(ZError.ETERM);
return false;
}
// First, check whether specific socket type overloads the option.
boolean rc = xsetsockopt(option, optval);
if (rc || errno.get() != ZError.EINVAL) {
return rc;
}
// If the socket type doesn't support the option, pass it to
// the generic option parser.
rc = options.setSocketOpt(option, optval);
if (rc) {
errno.set(0);
}
return rc;
}
finally {
unlock();
}
}
public final int getSocketOpt(int option)
{
lock();
try {
if (ctxTerminated.get()) {
errno.set(ZError.ETERM);
return -1;
}
// fast track to avoid boxing
if (option == ZMQ.ZMQ_RCVMORE) {
return rcvmore ? 1 : 0;
}
if (option == ZMQ.ZMQ_EVENTS) {
boolean rc = processCommands(0, false, null);
if (!rc && (errno.get() == ZError.ETERM || errno.get() == ZError.EINTR)) {
return -1;
}
assert (rc);
int val = 0;
if (hasOut()) {
val |= ZMQ.ZMQ_POLLOUT;
}
if (hasIn()) {
val |= ZMQ.ZMQ_POLLIN;
}
return val;
}
Object val = options.getSocketOpt(option);
if (val instanceof Integer) {
return (Integer) val;
}
if (val instanceof Boolean) {
return (Boolean) val ? 1 : 0;
}
throw new IllegalArgumentException(val + " is neither an integer or a boolean for option " + option);
}
finally {
unlock();
}
}
public final Object getSocketOptx(int option)
{
if (ctxTerminated.get()) {
errno.set(ZError.ETERM);
return null;
}
if (option == ZMQ.ZMQ_RCVMORE) {
return rcvmore ? 1 : 0;
}
if (option == ZMQ.ZMQ_FD) {
if (threadSafe) {
// thread safe socket doesn't provide file descriptor
errno.set(ZError.EINVAL);
return null;
}
return ((Mailbox) mailbox).getFd();
}
if (option == ZMQ.ZMQ_EVENTS) {
boolean rc = processCommands(0, false, null);
if (!rc && (errno.get() == ZError.ETERM || errno.get() == ZError.EINTR)) {
return -1;
}
assert (rc);
int val = 0;
if (hasOut()) {
val |= ZMQ.ZMQ_POLLOUT;
}
if (hasIn()) {
val |= ZMQ.ZMQ_POLLIN;
}
return val;
}
// If the socket type doesn't support the option, pass it to
// the generic option parser.
return options.getSocketOpt(option);
}
public final boolean bind(final String addr)
{
lock();
try {
if (ctxTerminated.get()) {
errno.set(ZError.ETERM);
return false;
}
options.mechanism.check(options);
// Process pending commands, if any.
boolean brc = processCommands(0, false, null);
if (!brc) {
return false;
}
SimpleURI uri = SimpleURI.create(addr);
String address = uri.getAddress();
NetProtocol protocol = checkProtocol(uri.getProtocol());
if (protocol == null) {
return false;
}
switch (protocol) {
case inproc: {
Ctx.Endpoint endpoint = new Ctx.Endpoint(this, options);
boolean rc = registerEndpoint(addr, endpoint);
if (rc) {
connectPending(addr, this);
// Save last endpoint URI
options.lastEndpoint = addr;
}
else {
errno.set(ZError.EADDRINUSE);
}
return rc;
}
case pgm:
// continue
case epgm:
// continue
case norm:
// For convenience's sake, bind can be used interchangeable with
// connect for PGM, EPGM and NORM transports.
return connect(addr);
case tcp:
// continue
case ipc:
// continue
case tipc: {
// Remaining transports require to be run in an I/O thread, so at this
// point we'll choose one.
IOThread ioThread = chooseIoThread(options.affinity);
if (ioThread == null) {
errno.set(ZError.EMTHREAD);
return false;
}
Listener listener = protocol.getListener(ioThread, this, options);
boolean rc = listener.setAddress(address);
if (!rc) {
listener.destroy();
eventBindFailed(address, errno.get());
return false;
}
// Save last endpoint URI
options.lastEndpoint = listener.getAddress();
addEndpoint(options.lastEndpoint, listener, null);
return true;
}
default:
throw new IllegalArgumentException(addr);
}
}
finally {
unlock();
}
}
public final boolean connect(String addr)
{
lock();
try {
return connectInternal(addr);
}
finally {
unlock();
}
}
public final int connectPeer(String addr)
{
lock();
try {
if (options.type != ZMQ.ZMQ_PEER && options.type != ZMQ.ZMQ_RAW) {
errno.set(ZError.ENOTSUP);
return 0;
}
boolean rc = connectInternal(addr);
if (!rc) {
return 0;
}
return options.peerLastRoutingId;
}
finally {
unlock();
}
}
private boolean connectInternal(String addr)
{
if (ctxTerminated.get()) {
errno.set(ZError.ETERM);
return false;
}
options.mechanism.check(options);
// Process pending commands, if any.
boolean brc = processCommands(0, false, null);
if (!brc) {
return false;
}
SimpleURI uri = SimpleURI.create(addr);
String address = uri.getAddress();
NetProtocol protocol = checkProtocol(uri.getProtocol());
if (protocol == null || !protocol.valid) {
return false;
}
if (protocol == NetProtocol.inproc) {
// TODO: inproc connect is specific with respect to creating pipes
// as there's no 'reconnect' functionality implemented. Once that
// is in place we should follow generic pipe creation algorithm.
// Find the peer endpoint.
Ctx.Endpoint peer = findEndpoint(addr);
// The total HWM for an inproc connection should be the sum of
// the binder's HWM and the connector's HWM.
int sndhwm = 0;
if (peer.socket == null) {
sndhwm = options.sendHwm;
}
else if (options.sendHwm != 0 && peer.options.recvHwm != 0) {
sndhwm = options.sendHwm + peer.options.recvHwm;
}
int rcvhwm = 0;
if (peer.socket == null) {
rcvhwm = options.recvHwm;
}
else if (options.recvHwm != 0 && peer.options.sendHwm != 0) {
rcvhwm = options.recvHwm + peer.options.sendHwm;
}
// Create a bi-directional pipe to connect the peers.
ZObject[] parents = {this, peer.socket == null ? this : peer.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 : sndhwm, conflate ? -1 : rcvhwm};
boolean[] conflates = {conflate, conflate};
Pipe[] pipes = Pipe.pair(parents, hwms, conflates);
// Attach local end of the pipe to this socket object.
attachPipe(pipes[0], true);
if (peer.socket == null) {
// The peer doesn't exist yet so we don't know whether
// to send the identity message or not. To resolve this,
// we always send our identity and drop it later if
// the peer doesn't expect it.
Msg id = new Msg(options.identitySize);
id.put(options.identity, 0, options.identitySize);
id.setFlags(Msg.IDENTITY);
boolean written = pipes[0].write(id);
assert (written);
pipes[0].flush();
// If set, send the hello msg of the local socket to the peer.
if (options.canSendHelloMsg && options.helloMsg != null) {
written = pipes[0].write(options.helloMsg);
assert (written);
pipes[0].flush();
}
pendConnection(addr, new Ctx.Endpoint(this, options), pipes);
}
else {
// If required, send the identity of the peer to the local socket.
if (peer.options.recvIdentity) {
Msg id = new Msg(options.identitySize);
id.put(options.identity, 0, options.identitySize);
id.setFlags(Msg.IDENTITY);
boolean written = pipes[0].write(id);
assert (written);
pipes[0].flush();
}
// If required, send the identity of the local socket to the peer.
if (options.recvIdentity) {
Msg id = new Msg(peer.options.identitySize);
id.put(peer.options.identity, 0, peer.options.identitySize);
id.setFlags(Msg.IDENTITY);
boolean written = pipes[1].write(id);
assert (written);
pipes[1].flush();
}
// If set, send the hello msg of the local socket to the peer.
if (options.canSendHelloMsg && options.helloMsg != null) {
boolean written = pipes[0].write(options.helloMsg);
assert (written);
pipes[0].flush();
}
// If set, send the hello msg of the peer to the local socket.
if (peer.options.canSendHelloMsg && peer.options.helloMsg != null) {
boolean written = pipes[1].write(peer.options.helloMsg);
assert (written);
pipes[1].flush();
}
if (peer.options.canReceiveDisconnectMsg && peer.options.disconnectMsg != null) {
pipes[0].setDisconnectMsg(peer.options.disconnectMsg);
}
// Attach remote end of the pipe to the peer socket. Note that peer's
// seqnum was incremented in findEndpoint function. We don't need it
// increased here.
sendBind(peer.socket, pipes[1], false);
}
// Save last endpoint URI
options.lastEndpoint = addr;
// remember inproc connections for disconnect
inprocs.insert(addr, pipes[0]);
return true;
}
boolean isSingleConnect = options.type == ZMQ.ZMQ_DEALER || options.type == ZMQ.ZMQ_SUB
|| options.type == ZMQ.ZMQ_REQ;
if (isSingleConnect) {
if (endpoints.hasValues(addr)) {
// There is no valid use for multiple connects for SUB-PUB nor
// DEALER-ROUTER nor REQ-REP. Multiple connects produces
// nonsensical results.
return true;
}
}
// Choose the I/O thread to run the session in.
IOThread ioThread = chooseIoThread(options.affinity);
if (ioThread == null) {
errno.set(ZError.EMTHREAD);
return false;
}
Address paddr = new Address(protocol, address);
// Resolve address (if needed by the protocol)
protocol.resolve(paddr, options.ipv6);
// Create session.
SessionBase session = Sockets.createSession(ioThread, true, this, options, paddr);
assert (session != null);
// PGM does not support subscription forwarding; ask for all data to be
// sent to this pipe. (same for NORM, currently?)
boolean subscribe2all = protocol.subscribe2all;
Pipe newpipe = null;
if (options.immediate || subscribe2all) {
// Create a bi-directional pipe.
ZObject[] parents = {this, session};
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.sendHwm, conflate ? -1 : options.recvHwm};
boolean[] conflates = {conflate, conflate};
Pipe[] pipes = Pipe.pair(parents, hwms, conflates);
// Attach local end of the pipe to the socket object.
attachPipe(pipes[0], subscribe2all, true);
newpipe = pipes[0];
// Attach remote end of the pipe to the session object later on.
session.attachPipe(pipes[1]);
}
// Save last endpoint URI
options.lastEndpoint = paddr.toString();
addEndpoint(addr, session, newpipe);
return true;
}
public boolean disconnectPeer(int routingId)
{
return xdisconnectPeer(routingId);
}
// Creates new endpoint ID and adds the endpoint to the map.
private void addEndpoint(String addr, Own endpoint, Pipe pipe)
{
// Activate the session. Make it a child of this socket.
launchChild(endpoint);
endpoints.insert(addr, new EndpointPipe(endpoint, pipe));
}
public final boolean termEndpoint(String addr)
{
lock();
try {
// Check whether the library haven't been shut down yet.
if (ctxTerminated.get()) {
errno.set(ZError.ETERM);
return false;
}
// Check whether endpoint address passed to the function is valid.
if (addr == null) {
errno.set(ZError.EINVAL);
return false;
}
// Process pending commands, if any, since there could be pending unprocessed processOwn()'s
// (from launchChild() for example) we're asked to terminate now.
boolean rc = processCommands(0, false, null);
if (!rc) {
return false;
}
SimpleURI uri = SimpleURI.create(addr);
NetProtocol protocol = checkProtocol(uri.getProtocol());
if (protocol == null) {
return false;
}
// Disconnect an inproc socket
if (protocol == NetProtocol.inproc) {
if (unregisterEndpoint(addr, this)) {
return true;
}
Collection olds = inprocs.remove(addr);
if (olds == null || olds.isEmpty()) {
errno.set(ZError.ENOENT);
return false;
}
else {
for (Pipe old : olds) {
old.sendDisconnectMsg();
old.terminate(true);
}
}
return true;
}
String resolvedAddress = addr;
// The resolved last_endpoint is used as a key in the endpoints map.
// The address passed by the user might not match in the TCP case due to
// IPv4-in-IPv6 mapping (EG: tcp://[::ffff:127.0.0.1]:9999), so try to
// resolve before giving up. Given at this stage we don't know whether a
// socket is connected or bound, try with both.
if (protocol == NetProtocol.tcp) {
boolean endpoint = endpoints.hasValues(resolvedAddress);
if (!endpoint) {
// TODO V4 resolve TCP address when unbinding
IZAddress address = protocol.zresolve(uri.getAddress(), options.ipv6);
resolvedAddress = address.address().toString();
endpoint = endpoints.hasValues(resolvedAddress);
if (!endpoint) {
// no luck, try with local resolution
InetSocketAddress socketAddress = address.resolve(uri.getAddress(), options.ipv6, true);
resolvedAddress = socketAddress.toString();
}
}
}
// Find the endpoints range (if any) corresponding to the addr_ string.
Collection eps = endpoints.remove(resolvedAddress);
if (eps == null || eps.isEmpty()) {
errno.set(ZError.ENOENT);
return false;
}
else {
// If we have an associated pipe, terminate it.
for (EndpointPipe ep : eps) {
if (ep.pipe != null) {
ep.pipe.terminate(false);
}
termChild(ep.endpoint);
}
}
return true;
}
finally {
unlock();
}
}
public final boolean send(Msg msg, int flags)
{
return send(msg, flags, null);
}
public final boolean send(Msg msg, int flags, AtomicBoolean canceled)
{
lock();
try {
// Check whether the library haven't been shut down yet.
if (ctxTerminated.get()) {
errno.set(ZError.ETERM);
return false;
}
// Check whether message passed to the function is valid.
if (msg == null || !msg.check()) {
errno.set(ZError.EFAULT);
return false;
}
// Process pending commands, if any.
boolean brc = processCommands(0, true, canceled);
if (!brc) {
return false;
}
// Clear any user-visible flags that are set on the message.
msg.resetFlags(Msg.MORE);
// At this point we impose the flags on the message.
if ((flags & ZMQ.ZMQ_SNDMORE) > 0) {
msg.setFlags(Msg.MORE);
}
msg.resetMetadata();
// Try to send the message.
boolean rc = xsend(msg);
if (rc) {
return true;
}
if (errno.get() != ZError.EAGAIN) {
return false;
}
// In case of non-blocking send we'll simply propagate
// the error - including EAGAIN - up the stack.
if ((flags & ZMQ.ZMQ_DONTWAIT) > 0 || options.sendTimeout == 0) {
return false;
}
// Compute the time when the timeout should occur.
// If the timeout is infinite, don't care.
int timeout = options.sendTimeout;
long end = timeout < 0 ? 0 : (Clock.nowMS() + timeout);
// Oops, we couldn't send the message. Wait for the next
// command, process it and try to send the message again.
// If timeout is reached in the meantime, return EAGAIN.
while (true) {
if (!processCommands(timeout, false, canceled)) {
return false;
}
rc = xsend(msg);
if (rc) {
break;
}
if (errno.get() != ZError.EAGAIN) {
return false;
}
if (timeout > 0) {
timeout = (int) (end - Clock.nowMS());
if (timeout <= 0) {
errno.set(ZError.EAGAIN);
return false;
}
}
}
return true;
}
finally {
unlock();
}
}
public final Msg recv(int flags)
{
return recv(flags, null);
}
public final Msg recv(int flags, AtomicBoolean canceled)
{
lock();
try {
// Check whether the library haven't been shut down yet.
if (ctxTerminated.get()) {
errno.set(ZError.ETERM);
return null;
}
// Check whether message passed to the function is valid.: NOT APPLICABLE
// Once every inbound_poll_rate messages check for signals and process
// incoming commands. This happens only if we are not polling altogether
// because there are messages available all the time. If poll occurs,
// ticks is set to zero and thus we avoid this code.
//
// Note that 'recv' uses different command throttling algorithm (the one
// described above) from the one used by 'send'. This is because counting
// ticks is more efficient than doing RDTSC all the time.
if (++ticks == Config.INBOUND_POLL_RATE.getValue()) {
if (!processCommands(0, false, canceled)) {
return null;
}
ticks = 0;
}
// Get the message.
Msg msg = xrecv();
if (msg == null && errno.get() != ZError.EAGAIN) {
return null;
}
// If we have the message, return immediately.
if (msg != null) {
if (fileDesc != null) {
msg.setFd(fileDesc);
}
extractFlags(msg);
return msg;
}
// If the message cannot be fetched immediately, there are two scenarios.
// For non-blocking recv, commands are processed in case there's an
// activate_reader command already waiting in a command pipe.
// If it's not, return EAGAIN.
if ((flags & ZMQ.ZMQ_DONTWAIT) > 0 || options.recvTimeout == 0) {
if (!processCommands(0, false, canceled)) {
return null;
}
ticks = 0;
msg = xrecv();
if (msg == null) {
return null;
}
extractFlags(msg);
return msg;
}
// Compute the time when the timeout should occur.
// If the timeout is infinite, don't care.
int timeout = options.recvTimeout;
long end = timeout < 0 ? 0 : (Clock.nowMS() + timeout);
// In blocking scenario, commands are processed over and over again until
// we are able to fetch a message.
boolean block = (ticks != 0);
while (true) {
if (!processCommands(block ? timeout : 0, false, canceled)) {
return null;
}
msg = xrecv();
if (msg != null) {
ticks = 0;
break;
}
if (errno.get() != ZError.EAGAIN) {
return null;
}
block = true;
if (timeout > 0) {
timeout = (int) (end - Clock.nowMS());
if (timeout <= 0) {
errno.set(ZError.EAGAIN);
return null;
}
}
}
extractFlags(msg);
return msg;
}
finally {
unlock();
}
}
public final boolean join(String group)
{
lock();
try {
return xjoin(group);
}
finally {
unlock();
}
}
public final boolean leave(String group)
{
lock();
try {
return xleave(group);
}
finally {
unlock();
}
}
public final void cancel(AtomicBoolean canceled)
{
canceled.set(true);
sendCancel();
}
public final int poll(int interest, int timeout, AtomicBoolean canceled)
{
lock();
try {
if (ctxTerminated.get()) {
errno.set(ZError.ETERM);
return -1;
}
int ready = 0;
if (hasOut() && (interest & ZMQ.ZMQ_POLLOUT) > 0) {
ready |= ZMQ.ZMQ_POLLOUT;
}
if (hasIn() && (interest & ZMQ.ZMQ_POLLIN) > 0) {
ready |= ZMQ.ZMQ_POLLIN;
}
if (ready != 0) {
return ready;
}
long end = timeout < 0 ? 0 : (Clock.nowMS() + timeout);
while (true) {
if (!processCommands(timeout, false, canceled)) {
return -1;
}
ready = 0;
if (hasOut() && (interest & ZMQ.ZMQ_POLLOUT) > 0) {
ready |= ZMQ.ZMQ_POLLOUT;
}
if (hasIn() && (interest & ZMQ.ZMQ_POLLIN) > 0) {
ready |= ZMQ.ZMQ_POLLIN;
}
if (ready != 0) {
return ready;
}
if (timeout == 0) {
errno.set(ZError.EAGAIN);
return -1;
}
else if (timeout > 0) {
timeout = (int) (end - Clock.nowMS());
if (timeout <= 0) {
errno.set(ZError.EAGAIN);
return -1;
}
}
}
}
finally {
unlock();
}
}
public final void close()
{
lock();
try {
// Mark the socket as dead
active = false;
// Transfer the ownership of the socket from this application thread
// to the reaper thread which will take care of the rest of shutdown
// process.
sendReap(this);
}
finally {
unlock();
}
}
// These functions are used by the polling mechanism to determine
// which events are to be reported from this socket.
final boolean hasIn()
{
return xhasIn();
}
final boolean hasOut()
{
return xhasOut();
}
// Using this function reaper thread ask the socket to register with
// its poller.
final void startReaping(Poller poller)
{
// Plug the socket to the reaper thread.
this.poller = poller;
SelectableChannel fd;
fd = ((Mailbox) mailbox).getFd();
handle = this.poller.addHandle(fd, this);
this.poller.setPollIn(handle);
// Initialize the termination and check whether it can be deallocated
// immediately.
ctxTerminated.set(true);
terminate();
checkDestroy();
}
private boolean isInEvent()
{
Boolean bRes = isInEventThreadLocal.get();
return null != bRes && bRes;
}
// Processes commands sent to this socket (if any). If timeout is -1,
// returns only after at least one command was processed.
// If throttle argument is true, commands are processed at most once
// in a predefined time period.
private boolean processCommands(int timeout, boolean throttle, AtomicBoolean canceled)
{
Command cmd;
if (timeout != 0) {
// If we are asked to wait, simply ask mailbox to wait.
cmd = mailbox.recv(timeout);
}
else {
// If we are asked not to wait, check whether we haven't processed
// commands recently, so that we can throttle the new commands.
// Get the CPU's tick counter. If 0, the counter is not available.
long tsc = 0; // Clock.rdtsc();
// Optimized version of command processing - it doesn't have to check
// for incoming commands each time. It does so only if certain time
// elapsed since last command processing. Command delay varies
// depending on CPU speed: It's ~1ms on 3GHz CPU, ~2ms on 1.5GHz CPU
// etc. The optimization makes sense only on platforms where getting
// a timestamp is a very cheap operation (tens of nanoseconds).
if (tsc != 0 && throttle) {
// Check whether TSC haven't jumped backwards (in case of migration
// between CPU cores) and whether certain time have elapsed since
// last command processing. If it didn't do nothing.
if (tsc >= lastTsc && tsc - lastTsc <= Config.MAX_COMMAND_DELAY.getValue()) {
return true;
}
lastTsc = tsc;
}
// Check whether there are any commands pending for this thread.
cmd = mailbox.recv(0);
}
// Process all the commands available at the moment.
while (cmd != null) {
cmd.process();
cmd = mailbox.recv(0);
}
if (!isInEvent() && destroyed.get()) {
sendReapAck();
}
if (errno.get() == ZError.EINTR) {
return false;
}
if (canceled != null && canceled.get()) {
errno.set(ZError.ECANCELED);
return false;
}
assert (errno.get() == ZError.EAGAIN) : errno;
if (ctxTerminated.get()) {
errno.set(ZError.ETERM); // Do not raise exception at the blocked operation
return false;
}
return true;
}
@Override
protected final void processStop()
{
// Here, someone have called zmq_term while the socket was still alive.
// We'll remember the fact so that any blocking call is interrupted and any
// further attempt to use the socket will return ETERM. The user is still
// responsible for calling zmq_close on the socket though!
synchronized (monitor) {
stopMonitor();
ctxTerminated.set(true);
}
}
@Override
protected final void processBind(Pipe pipe)
{
attachPipe(pipe, false);
}
@Override
protected final void processTerm(int linger)
{
// Unregister all inproc endpoints associated with this socket.
// Doing this we make sure that no new pipes from other sockets (inproc)
// will be initiated.
unregisterEndpoints(this);
// Ask all attached pipes to terminate.
for (Pipe pipe : pipes) {
pipe.sendDisconnectMsg();
pipe.terminate(false);
}
registerTermAcks(pipes.size());
// Continue the termination process immediately.
super.processTerm(linger);
}
// Delay actual destruction of the socket.
@Override
protected final void processDestroy()
{
destroyed.set(true);
}
// The default implementation assumes there are no specific socket
// options for the particular socket type. If not so, overload this
// method.
protected boolean xsetsockopt(int option, Object optval)
{
errno.set(ZError.EINVAL);
return false;
}
protected boolean xhasOut()
{
return false;
}
protected boolean xsend(Msg msg)
{
throw new UnsupportedOperationException("Must Override");
}
protected boolean xhasIn()
{
return false;
}
protected Msg xrecv()
{
throw new UnsupportedOperationException("Must Override");
}
protected Blob getCredential()
{
throw new UnsupportedOperationException("Must Override");
}
protected void xreadActivated(Pipe pipe)
{
throw new UnsupportedOperationException("Must Override");
}
protected void xwriteActivated(Pipe pipe)
{
throw new UnsupportedOperationException("Must Override");
}
protected void xhiccuped(Pipe pipe)
{
throw new UnsupportedOperationException("Must override");
}
protected boolean xjoin(String group)
{
throw new UnsupportedOperationException("Must override");
}
protected boolean xleave(String group)
{
throw new UnsupportedOperationException("Must override");
}
protected boolean xdisconnectPeer(int routingId)
{
throw new UnsupportedOperationException("Must override");
}
private void enterInEvent()
{
isInEventThreadLocal.set(true);
}
private void leaveInEvent()
{
isInEventThreadLocal.remove();
}
@Override
public final void inEvent()
{
// This function is invoked only once the socket is running in the context
// of the reaper thread. Process any commands from other threads/sockets
// that may be available at the moment. Ultimately, the socket will
// be destroyed.
lock();
try {
enterInEvent();
processCommands(0, false, null);
}
finally {
leaveInEvent();
unlock();
}
checkDestroy();
}
// To be called after processing commands or invoking any command
// handlers explicitly. If required, it will deallocate the socket.
private void checkDestroy()
{
// If the object was already marked as destroyed, finish the deallocation.
if (destroyed.get()) {
// Remove the socket from the reaper's poller.
poller.removeHandle(handle);
// Remove the socket from the context.
destroySocket(this);
// Notify the reaper about the fact.
sendReaped();
// Deallocate.
super.processDestroy();
}
}
@Override
public final void readActivated(Pipe pipe)
{
xreadActivated(pipe);
}
@Override
public final void writeActivated(Pipe pipe)
{
xwriteActivated(pipe);
}
@Override
public final void hiccuped(Pipe pipe)
{
if (!options.immediate) {
pipe.terminate(false);
}
else {
// Notify derived sockets of the hiccup
xhiccuped(pipe);
}
}
@Override
public final void pipeTerminated(Pipe pipe)
{
// Notify the specific socket type about the pipe termination.
xpipeTerminated(pipe);
// Remove pipe from inproc pipes
inprocs.remove(pipe);
// Remove the pipe from the list of attached pipes and confirm its
// termination if we are already shutting down.
pipes.remove(pipe);
if (isTerminating()) {
unregisterTermAck();
}
}
// Moves the flags from the message to local variables,
// to be later retrieved by getSocketOpt.
private void extractFlags(Msg msg)
{
// Test whether IDENTITY flag is valid for this socket type.
assert !msg.isIdentity() || (options.recvIdentity);
// Remove MORE flag.
rcvmore = msg.hasMore();
}
/**
* Register the address for a monitor. It must be a inproc PAIR.
* @param addr or null for unregister.
* @param events an event mask to monitor.
* @return true if creation succeeded.
* @throws IllegalStateException if a previous monitor was already
* registered and consumer is not null.
*/
public final boolean monitor(final String addr, int events)
{
synchronized (monitor) {
// To be tested before trying anything
if (ctxTerminated.get()) {
errno.set(ZError.ETERM);
return false;
}
// Support unregistering monitoring endpoints as well
if (addr == null) {
stopMonitor();
return true;
}
SimpleURI uri = SimpleURI.create(addr);
NetProtocol protocol = checkProtocol(uri.getProtocol());
if (protocol == null) {
return false;
}
// Event notification only supported over inproc://
if (protocol != NetProtocol.inproc) {
errno.set(ZError.EPROTONOSUPPORT);
return false;
}
SocketBase monitorSocket = getCtx().createSocket(ZMQ.ZMQ_PAIR);
if (monitorSocket == null) {
return false;
}
// Never block context termination on pending event messages
monitorSocket.setSocketOpt(ZMQ.ZMQ_LINGER, 0);
boolean rc = monitorSocket.bind(addr);
if (rc) {
return setEventHook(new SocketEventHandler(monitorSocket), events);
}
else {
return false;
}
}
}
/**
* Register a custom event consumer.
* @param consumer or null for unregister.
* @param events an event mask to monitor.
* @return true if creation succeeded.
* @throws IllegalStateException if a previous monitor was already
* registered and consumer is not null.
*/
public final boolean setEventHook(ZMQ.EventConsummer consumer, int events)
{
synchronized (monitor) {
if (ctxTerminated.get()) {
errno.set(ZError.ETERM);
return false;
}
// Support unregistering monitoring endpoints as well
if (consumer == null) {
stopMonitor();
}
else {
if (monitor.get() != null) {
throw new IllegalStateException("Monitor registered twice");
}
monitor.set(consumer);
// Register events to monitor
monitorEvents = events;
}
return true;
}
}
public final void eventHandshaken(String addr, int zmtpVersion)
{
event(addr, zmtpVersion, ZMQ.ZMQ_EVENT_HANDSHAKE_PROTOCOL);
}
public final void eventConnected(String addr, SelectableChannel ch)
{
event(addr, ch, ZMQ.ZMQ_EVENT_CONNECTED);
}
public final void eventConnectDelayed(String addr, int errno)
{
event(addr, errno, ZMQ.ZMQ_EVENT_CONNECT_DELAYED);
}
public final void eventConnectRetried(String addr, int interval)
{
event(addr, interval, ZMQ.ZMQ_EVENT_CONNECT_RETRIED);
}
public final void eventListening(String addr, SelectableChannel ch)
{
event(addr, ch, ZMQ.ZMQ_EVENT_LISTENING);
}
public final void eventBindFailed(String addr, int errno)
{
event(addr, errno, ZMQ.ZMQ_EVENT_BIND_FAILED);
}
public final void eventAccepted(String addr, SelectableChannel ch)
{
event(addr, ch, ZMQ.ZMQ_EVENT_ACCEPTED);
}
public final void eventAcceptFailed(String addr, int errno)
{
event(addr, errno, ZMQ.ZMQ_EVENT_ACCEPT_FAILED);
}
public final void eventClosed(String addr, SelectableChannel ch)
{
event(addr, ch, ZMQ.ZMQ_EVENT_CLOSED);
}
public final void eventCloseFailed(String addr, int errno)
{
event(addr, errno, ZMQ.ZMQ_EVENT_CLOSE_FAILED);
}
public final void eventDisconnected(String addr, SelectableChannel ch)
{
event(addr, ch, ZMQ.ZMQ_EVENT_DISCONNECTED);
}
public final void eventHandshakeFailedNoDetail(String addr, int errno)
{
event(addr, errno, ZMQ.ZMQ_EVENT_HANDSHAKE_FAILED_NO_DETAIL);
}
public final void eventHandshakeFailedProtocol(String addr, int errno)
{
event(addr, errno, ZMQ.ZMQ_EVENT_HANDSHAKE_FAILED_PROTOCOL);
}
public final void eventHandshakeFailedAuth(String addr, int errno)
{
event(addr, errno, ZMQ.ZMQ_EVENT_HANDSHAKE_FAILED_AUTH);
}
public final void eventHandshakeSucceeded(String addr, int errno)
{
event(addr, errno, ZMQ.ZMQ_EVENT_HANDSHAKE_SUCCEEDED);
}
private void event(String addr, Object arg, int event)
{
synchronized (monitor) {
if ((monitorEvents & event) == 0 || monitor.get() == null) {
return;
}
monitorEvent(new ZMQ.Event(event, addr, arg));
}
}
// Send a monitor event
protected final void monitorEvent(ZMQ.Event event)
{
if (monitor.get() == null) {
return;
}
monitor.get().consume(event);
}
private void stopMonitor()
{
// this is a private method which is only called from
// contexts where the mutex has been locked before
if (monitor.get() != null) {
if ((monitorEvents & ZMQ.ZMQ_EVENT_MONITOR_STOPPED) != 0) {
monitorEvent(new ZMQ.Event(ZMQ.ZMQ_EVENT_MONITOR_STOPPED, "", 0));
}
monitor.get().close();
monitor.set(null);
monitorEvents = 0;
}
}
@Override
public String toString()
{
return getClass().getSimpleName() + "[" + options.socketId + "]";
}
public final SelectableChannel getFD()
{
if (threadSafe) {
errno.set(ZError.EINVAL);
return null;
}
return ((Mailbox) mailbox).getFd();
}
public String typeString()
{
return Sockets.name(options.type);
}
public final int errno()
{
return errno.get();
}
private void lock()
{
if (threadSafe) {
threadSafeSync.lock();
}
}
private void unlock()
{
if (threadSafe) {
threadSafeSync.unlock();
}
}
private static class SimpleURI
{
private final String protocol;
private final String address;
private SimpleURI(String protocol, String address)
{
this.protocol = protocol;
this.address = address;
}
public static SimpleURI create(String value)
{
int pos = value.indexOf("://");
if (pos < 0) {
throw new IllegalArgumentException("Invalid URI: " + value);
}
String protocol = value.substring(0, pos);
String address = value.substring(pos + 3);
if (protocol.isEmpty() || address.isEmpty()) {
throw new IllegalArgumentException("Invalid URI: " + value);
}
return new SimpleURI(protocol, address);
}
public String getProtocol()
{
return protocol;
}
public String getAddress()
{
return address;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy