All Downloads are FREE. Search and download functionalities are using the official Maven repository.

zmq.Ctx Maven / Gradle / Ivy

The newest version!
package zmq;

import java.io.IOException;
import java.lang.Thread.UncaughtExceptionHandler;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.nio.channels.SelectableChannel;
import java.nio.channels.Selector;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.zeromq.ZMQException;

import zmq.io.IOThread;
import zmq.pipe.Pipe;
import zmq.socket.Sockets;
import zmq.util.Errno;
import zmq.util.MultiMap;
import zmq.util.function.BiFunction;

/**
 * Context object encapsulates all the global state associated with
 * the library.
* It creates a reaper thread and some IO threads as defined by {@link ZMQ#ZMQ_IO_THREADS}. The thread are created * using a thread factory that defined the UncaughtExceptionHandler as defined by {@link Ctx#setUncaughtExceptionHandler(UncaughtExceptionHandler)} * and defined the thread as a daemon. If a custom thread factory is defined with {@link Ctx#setThreadFactory(BiFunction)}, * all that steps must be handled manually. */ public class Ctx { private static final int WAIT_FOREVER = -1; // Information associated with inproc endpoint. Note that endpoint options // are registered as well so that the peer can access them without a need // for synchronization, handshaking or similar. public static class Endpoint { public final SocketBase socket; public final Options options; public Endpoint(SocketBase socket, Options options) { this.socket = socket; this.options = options; } } private static class PendingConnection { private final Endpoint endpoint; private final Pipe connectPipe; private final Pipe bindPipe; public PendingConnection(Endpoint endpoint, Pipe connectPipe, Pipe bindPipe) { super(); this.endpoint = endpoint; this.connectPipe = connectPipe; this.bindPipe = bindPipe; } } private enum Side { CONNECT, BIND } // Used to check whether the object is a context. private boolean active; // Sockets belonging to this context. We need the list so that // we can notify the sockets when zmq_term() is called. The sockets // will return ETERM then. private final List sockets; // List of unused thread slots. private final Deque emptySlots; // If true, init has been called but no socket has been created // yet. Launching of I/O threads is delayed. private final AtomicBoolean starting = new AtomicBoolean(true); // If true, zmq_term was already called. private volatile boolean terminating; // Synchronization of accesses to global slot-related data: // sockets, emptySlots, terminating. It also synchronizes // access to zombie sockets as such (as opposed to slots) and provides // a memory barrier to ensure that all CPU cores see the same data. private final Lock slotSync; // A list of poll selectors opened under this context. When the context is // destroyed, each of the selectors is closed to ensure resource // deallocation. private final List selectors = new ArrayList<>(); // The reaper thread. private Reaper reaper; // I/O threads. private final List ioThreads; // Array of pointers to mailboxes for both application and I/O threads. private int slotCount; private IMailbox[] slots; // Mailbox for zmq_term thread. private final Mailbox termMailbox; // List of inproc endpoints within this context. private final Map endpoints; // Synchronization of access to the list of inproc endpoints. private final Lock endpointsSync; // Maximum socket ID. private static final AtomicInteger maxSocketId = new AtomicInteger(0); // Maximum number of sockets that can be opened at the same time. private int maxSockets; // Number of I/O threads to launch. private int ioThreadCount; // The thread factory used by the poller private BiFunction threadFactory; // Does context wait (possibly forever) on termination? private boolean blocky; // Synchronization of access to context options. private final Lock optSync; // Synchronization of access to selectors. private final Lock selectorSync = new ReentrantLock(); static final int TERM_TID = 0; private static final int REAPER_TID = 1; private final MultiMap pendingConnections = new MultiMap<>(); private boolean ipv6; private final Errno errno = new Errno(); // Exception handlers to receive notifications of critical exceptions in zmq.poll.Poller and handle uncaught exceptions private UncaughtExceptionHandler exhandler = Thread.getDefaultUncaughtExceptionHandler(); // Exception handlers to receive notifications of exception in zmq.poll.Poller, and can be used for logging private UncaughtExceptionHandler exnotification = (t, e) -> e.printStackTrace(); /** * A class that holds the informations needed to forward channel in monitor sockets. * Of course, it only works with inproc sockets. *

* It uses WeakReference to avoid holding references to channel if the monitor event is * lost. *

* A class is used as a lock in lazy allocation of the needed objects. */ private static class ChannelForwardHolder { private final AtomicInteger handleSource = new AtomicInteger(0); private final Map> map = new ConcurrentHashMap<>(); // The WeakReference is empty when the reference is empty, so keep a reverse empty to clean the direct map. private final Map, Integer> reversemap = new ConcurrentHashMap<>(); private final ReferenceQueue queue = new ReferenceQueue<>(); } private ChannelForwardHolder forwardHolder = null; public Ctx() { active = true; terminating = false; reaper = null; slotCount = 0; slots = null; maxSockets = ZMQ.ZMQ_MAX_SOCKETS_DFLT; ioThreadCount = ZMQ.ZMQ_IO_THREADS_DFLT; threadFactory = this::createThread; ipv6 = false; blocky = true; slotSync = new ReentrantLock(); endpointsSync = new ReentrantLock(); optSync = new ReentrantLock(); termMailbox = new Mailbox(this, "terminater", -1); emptySlots = new ArrayDeque<>(); ioThreads = new ArrayList<>(); sockets = new ArrayList<>(); endpoints = new HashMap<>(); } private void destroy() throws IOException { assert (sockets.isEmpty()); for (IOThread it : ioThreads) { it.stop(); } for (IOThread it : ioThreads) { it.close(); } ioThreads.clear(); selectorSync.lock(); try { for (Selector selector : selectors) { if (selector != null) { selector.close(); } } selectors.clear(); } finally { selectorSync.unlock(); } // Deallocate the reaper thread object. if (reaper != null) { reaper.close(); } // Deallocate the array of mailboxes. No special work is // needed as mailboxes themselves were deallocated with their // corresponding io_thread/socket objects. termMailbox.close(); active = false; } /** * @return false if {@link #terminate()}terminate() has been called. */ public boolean isActive() { return active; } /** * @return false if {@link #terminate()}terminate() has been called. * @deprecated use {@link #isActive()} instead */ @Deprecated public boolean checkTag() { return active; } // This function is called when user invokes zmq_term. If there are // no more sockets open it'll cause all the infrastructure to be shut // down. If there are open sockets still, the deallocation happens // after the last one is closed. public void terminate() { slotSync.lock(); try { // Connect up any pending inproc connections, otherwise we will hang for (Entry pending : pendingConnections.entries()) { SocketBase s = createSocket(ZMQ.ZMQ_PAIR); // create_socket might fail eg: out of memory/sockets limit reached assert (s != null); s.bind(pending.getValue()); s.close(); } if (!starting.get()) { // Check whether termination was already underway, but interrupted and now // restarted. boolean restarted = terminating; terminating = true; // First attempt to terminate the context. if (!restarted) { // First send stop command to sockets so that any blocking calls // can be interrupted. If there are no sockets we can ask reaper // thread to stop. for (SocketBase socket : sockets) { socket.stop(); } if (sockets.isEmpty()) { reaper.stop(); } } } } finally { slotSync.unlock(); } if (!starting.get()) { // Wait till reaper thread closes all the sockets. Command cmd = termMailbox.recv(WAIT_FOREVER); if (cmd == null) { throw new ZMQException(errno.get()); } assert (cmd.type == Command.Type.DONE) : cmd; slotSync.lock(); try { assert (sockets.isEmpty()); } finally { slotSync.unlock(); } } // Deallocate the resources. try { destroy(); } catch (IOException e) { throw new ZError.IOException(e); } } final void shutdown() { slotSync.lock(); try { if (!starting.get() && !terminating) { terminating = true; // Send stop command to sockets so that any blocking calls // can be interrupted. If there are no sockets we can ask reaper // thread to stop. for (SocketBase socket : sockets) { socket.stop(); } if (sockets.isEmpty()) { reaper.stop(); } } } finally { slotSync.unlock(); } } private void chechStarted() { if (!starting.get()) { throw new IllegalStateException("Already started"); } } /** * Set the handler invoked when a {@link zmq.poll.Poller} abruptly terminates due to an uncaught exception.
* It defaults to the value of {@link Thread#getDefaultUncaughtExceptionHandler()} * @param handler The object to use as this thread's uncaught exception handler. If null then this thread has no * explicit handler and will use the one defined for the {@link ThreadGroup}. * @throws IllegalStateException If context was already initialized by the creation of a socket */ public void setUncaughtExceptionHandler(UncaughtExceptionHandler handler) { chechStarted(); exhandler = handler; } /** * @return The handler invoked when a {@link zmq.poll.Poller} abruptly terminates due to an uncaught exception. */ public UncaughtExceptionHandler getUncaughtExceptionHandler() { return exhandler; } /** * In {@link zmq.poll.Poller#run()}, some non-fatal exceptions can be thrown. This handler will be notified, so they can * be logged.

* Default to {@link Throwable#printStackTrace()} * @param handler The object to use as this thread's handler for recoverable exceptions notifications. * @throws IllegalStateException If context was already initialized by the creation of a socket */ public void setNotificationExceptionHandler(UncaughtExceptionHandler handler) { chechStarted(); exnotification = handler; } /** * @return The handler invoked when a non-fatal exceptions is thrown in zmq.poll.Poller#run() */ public UncaughtExceptionHandler getNotificationExceptionHandler() { return exnotification; } /** * Used to define a custom thread factory. It can be used to create thread that will be bounded to a CPU for * performance or tweaks the created thread. It the UncaughtExceptionHandler is not set, the created thread UncaughtExceptionHandler * will not be changed, so the factory can also be used to set it. * * @param threadFactory the thread factory used by {@link zmq.poll.Poller} * @throws IllegalStateException If context was already initialized by the creation of a socket */ public void setThreadFactory(BiFunction threadFactory) { chechStarted(); this.threadFactory = threadFactory; } /** * @return the current thread factory */ public BiFunction getThreadFactory() { return threadFactory; } /** * Set an option * @param option the option to set * @param optval the option value * @return true is the option is allowed for a context and the value is valid for the option * @throws IllegalStateException If context was already initialized by the creation of a socket, and the * option can't be changed. */ public boolean set(int option, int optval) { if (option == ZMQ.ZMQ_MAX_SOCKETS && optval >= 1) { chechStarted(); optSync.lock(); try { maxSockets = optval; } finally { optSync.unlock(); } } else if (option == ZMQ.ZMQ_IO_THREADS && optval >= 0) { chechStarted(); optSync.lock(); try { ioThreadCount = optval; } finally { optSync.unlock(); } } else if (option == ZMQ.ZMQ_BLOCKY && optval >= 0) { optSync.lock(); try { blocky = (optval != 0); } finally { optSync.unlock(); } } else if (option == ZMQ.ZMQ_IPV6 && optval >= 0) { optSync.lock(); try { ipv6 = (optval != 0); } finally { optSync.unlock(); } } else { return false; } return true; } public int get(int option) { int rc; if (option == ZMQ.ZMQ_MAX_SOCKETS) { rc = maxSockets; } else if (option == ZMQ.ZMQ_IO_THREADS) { rc = ioThreadCount; } else if (option == ZMQ.ZMQ_BLOCKY) { rc = blocky ? 1 : 0; } else if (option == ZMQ.ZMQ_IPV6) { rc = ipv6 ? 1 : 0; } else { throw new IllegalArgumentException("option = " + option); } return rc; } public SocketBase createSocket(int type) { SocketBase s; slotSync.lock(); try { if (starting.compareAndSet(true, false)) { initSlots(); } // Once zmq_term() was called, we can't create new sockets. if (terminating) { throw new ZError.CtxTerminatedException(); } // If maxSockets limit was reached, return error. if (emptySlots.isEmpty()) { throw new ZMQException(ZError.EMFILE); } // Choose a slot for the socket. int slot = emptySlots.pollLast(); // Generate new unique socket ID. int sid = maxSocketId.incrementAndGet(); // Create the socket and register its mailbox. s = Sockets.create(type, this, slot, sid); if (s == null) { emptySlots.addLast(slot); return null; } sockets.add(s); slots[slot] = s.getMailbox(); } finally { slotSync.unlock(); } return s; } private void initSlots() { slotSync.lock(); try { // Initialize the array of mailboxes. Additional two slots are for // zmq_term thread and reaper thread. int ios; optSync.lock(); try { ios = ioThreadCount; slotCount = maxSockets + ioThreadCount + 2; } finally { optSync.unlock(); } slots = new IMailbox[slotCount]; // Initialize the infrastructure for zmq_term thread. slots[TERM_TID] = termMailbox; // Create the reaper thread. reaper = new Reaper(this, REAPER_TID); slots[REAPER_TID] = reaper.getMailbox(); reaper.start(); // Create I/O thread objects and launch them. for (int i = 2; i != ios + 2; i++) { IOThread ioThread = new IOThread(this, i); //alloc_assert (io_thread); ioThreads.add(ioThread); slots[i] = ioThread.getMailbox(); ioThread.start(); } // In the unused part of the slot array, create a list of empty slots. for (int i = slotCount - 1; i >= ios + 2; i--) { emptySlots.add(i); slots[i] = null; } } finally { slotSync.unlock(); } } void destroySocket(SocketBase socket) { slotSync.lock(); // Free the associated thread slot. try { int tid = socket.getTid(); emptySlots.add(tid); slots[tid] = null; // Remove the socket from the list of sockets. sockets.remove(socket); // If zmq_term() was already called and there are no more socket // we can ask reaper thread to terminate. if (terminating && sockets.isEmpty()) { reaper.stop(); } } finally { slotSync.unlock(); } } // Creates a Selector that will be closed when the context is destroyed. public Selector createSelector() { selectorSync.lock(); try { Selector selector = Selector.open(); assert (selector != null); selectors.add(selector); return selector; } catch (IOException e) { throw new ZError.IOException(e); } finally { selectorSync.unlock(); } } public boolean closeSelector(Selector selector) { selectorSync.lock(); try { boolean rc = selectors.remove(selector); if (rc) { try { selector.close(); } catch (IOException e) { throw new ZError.IOException(e); } } return rc; } finally { selectorSync.unlock(); } } // Returns reaper thread object. ZObject getReaper() { return reaper; } // Send command to the destination thread. void sendCommand(int tid, final Command command) { // System.out.println(Thread.currentThread().getName() + ": Sending command " + command); slots[tid].send(command); } // Returns the I/O thread that is the least busy at the moment. // Affinity specifies which I/O threads are eligible (0 = all). // Returns NULL if no I/O thread is available. IOThread chooseIoThread(long affinity) { if (ioThreads.isEmpty()) { return null; } // Find the I/O thread with minimum load. int minLoad = -1; IOThread selectedIoThread = null; for (int i = 0; i != ioThreads.size(); i++) { if (affinity == 0 || (affinity & (1L << i)) > 0) { int load = ioThreads.get(i).getLoad(); if (selectedIoThread == null || load < minLoad) { minLoad = load; selectedIoThread = ioThreads.get(i); } } } return selectedIoThread; } // Management of inproc endpoints. boolean registerEndpoint(String addr, Endpoint endpoint) { endpointsSync.lock(); Endpoint inserted; try { inserted = endpoints.put(addr, endpoint); } finally { endpointsSync.unlock(); } return inserted == null; } boolean unregisterEndpoint(String addr, SocketBase socket) { endpointsSync.lock(); try { Endpoint endpoint = endpoints.get(addr); if (endpoint != null && socket == endpoint.socket) { endpoints.remove(addr); return true; } } finally { endpointsSync.unlock(); } return false; } void unregisterEndpoints(SocketBase socket) { endpointsSync.lock(); try { endpoints.entrySet().removeIf(e -> e.getValue().socket == socket); } finally { endpointsSync.unlock(); } } Endpoint findEndpoint(String addr) { Endpoint endpoint; endpointsSync.lock(); try { endpoint = endpoints.get(addr); if (endpoint == null) { return new Endpoint(null, new Options()); } // Increment the command sequence number of the peer so that it won't // get deallocated until "bind" command is issued by the caller. // The subsequent 'bind' has to be called with inc_seqnum parameter // set to false, so that the seqnum isn't incremented twice. endpoint.socket.incSeqnum(); } finally { endpointsSync.unlock(); } return endpoint; } void pendConnection(String addr, Endpoint endpoint, Pipe[] pipes) { PendingConnection pendingConnection = new PendingConnection(endpoint, pipes[0], pipes[1]); endpointsSync.lock(); try { Endpoint existing = endpoints.get(addr); if (existing == null) { // Still no bind. endpoint.socket.incSeqnum(); pendingConnections.insert(addr, pendingConnection); } else { // Bind has happened in the mean time, connect directly connectInprocSockets(existing.socket, existing.options, pendingConnection, Side.CONNECT); } } finally { endpointsSync.unlock(); } } void connectPending(String addr, SocketBase bindSocket) { endpointsSync.lock(); try { Collection pendings = pendingConnections.remove(addr); if (pendings != null) { for (PendingConnection pending : pendings) { connectInprocSockets(bindSocket, endpoints.get(addr).options, pending, Side.BIND); } } } finally { endpointsSync.unlock(); } } private void connectInprocSockets(SocketBase bindSocket, Options bindOptions, PendingConnection pendingConnection, Side side) { bindSocket.incSeqnum(); pendingConnection.bindPipe.setTid(bindSocket.getTid()); if (!bindOptions.recvIdentity) { Msg msg = pendingConnection.bindPipe.read(); assert (msg != null); } int sndhwm = 0; if (pendingConnection.endpoint.options.sendHwm != 0 && bindOptions.recvHwm != 0) { sndhwm = pendingConnection.endpoint.options.sendHwm + bindOptions.recvHwm; } int rcvhwm = 0; if (pendingConnection.endpoint.options.recvHwm != 0 && bindOptions.sendHwm != 0) { rcvhwm = pendingConnection.endpoint.options.recvHwm + bindOptions.sendHwm; } boolean conflate = pendingConnection.endpoint.options.conflate && (pendingConnection.endpoint.options.type == ZMQ.ZMQ_DEALER || pendingConnection.endpoint.options.type == ZMQ.ZMQ_PULL || pendingConnection.endpoint.options.type == ZMQ.ZMQ_PUSH || pendingConnection.endpoint.options.type == ZMQ.ZMQ_PUB || pendingConnection.endpoint.options.type == ZMQ.ZMQ_SUB); int[] hwms = { conflate ? -1 : sndhwm, conflate ? -1 : rcvhwm }; pendingConnection.connectPipe.setHwms(hwms[1], hwms[0]); pendingConnection.bindPipe.setHwms(hwms[0], hwms[1]); if (bindOptions.canReceiveDisconnectMsg && bindOptions.disconnectMsg != null) { pendingConnection.connectPipe.setDisconnectMsg(bindOptions.disconnectMsg); } if (side == Side.BIND) { Command cmd = new Command(null, Command.Type.BIND, pendingConnection.bindPipe); bindSocket.processCommand(cmd); bindSocket.sendInprocConnected(pendingConnection.endpoint.socket); } else { pendingConnection.connectPipe.sendBind(bindSocket, pendingConnection.bindPipe, false); } // When a ctx is terminated all pending inproc connection will be // connected, but the socket will already be closed and the pipe will be // in waiting_for_delimiter state, which means no more writes can be done // and the identity write fails and causes an assert. Check if the socket // is open before sending. if (pendingConnection.endpoint.options.recvIdentity && pendingConnection.endpoint.socket.isActive()) { Msg id = new Msg(bindOptions.identitySize); id.put(bindOptions.identity, 0, bindOptions.identitySize); id.setFlags(Msg.IDENTITY); boolean written = pendingConnection.bindPipe.write(id); assert (written); pendingConnection.bindPipe.flush(); } // If set, send the hello msg of the peer to the local socket. if (bindOptions.canSendHelloMsg && bindOptions.helloMsg != null) { boolean written = pendingConnection.bindPipe.write(bindOptions.helloMsg); assert (written); pendingConnection.bindPipe.flush(); } } public Errno errno() { return errno; } /** * Forward a channel in a monitor socket. * @param channel a channel to forward * @return the handle of the channel to be forwarded, used to retrieve it in {@link #getForwardedChannel(Integer)} */ int forwardChannel(SelectableChannel channel) { synchronized (ChannelForwardHolder.class) { if (forwardHolder == null) { forwardHolder = new ChannelForwardHolder(); } } WeakReference ref = new WeakReference<>(channel, forwardHolder.queue); int handle = forwardHolder.handleSource.getAndIncrement(); forwardHolder.map.put(handle, ref); forwardHolder.reversemap.put(ref, handle); cleanForwarded(); return handle; } /** * Retrieve a channel, using the handle returned by {@link #forwardChannel(SelectableChannel)}. As WeakReference are used, if the channel was discarded * and a GC ran, it will not be found and this method will return null. * @param handle * @return */ SelectableChannel getForwardedChannel(Integer handle) { cleanForwarded(); WeakReference ref = forwardHolder.map.remove(handle); if (ref != null) { return ref.get(); } else { return null; } } /** * Clean all empty references */ private void cleanForwarded() { Reference ref; while ((ref = forwardHolder.queue.poll()) != null) { Integer handle = forwardHolder.reversemap.remove(ref); forwardHolder.map.remove(handle); } } private Thread createThread(Runnable target, String name) { Thread t = new Thread(target, name); t.setDaemon(true); t.setUncaughtExceptionHandler(getUncaughtExceptionHandler()); return t; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy