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

org.zeromq.ZProxy Maven / Gradle / Ivy

The newest version!
package org.zeromq;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import org.zeromq.ZActor.Actor;
import org.zeromq.ZAgent.SelectorCreator;
import org.zeromq.ZMQ.Socket;
import org.zeromq.ZStar.Exit;

import zmq.Msg;
import zmq.SocketBase;

/**
 * Implementation of a remotely controlled  proxy for 0MQ, using {@link ZActor}.
 * 
* The goals of this implementation are to delegate the creation of sockets * in a background thread via a callback interface to ensure their correct use * and to provide ultimately to end-users the following features. * *

Basic features:

*
    *
  • Remote Control *
      *
    • Start: if was paused, flushes the pending messages *
    • Pause: lets the socket queues accumulate messages according to their types *
    • Stop: Shutdowns the proxy, can be restarted *
    • Status: Retrieves the status of the proxy *
    • Cold Restart: Closes and recreates the connections *
    • {@link #restart(ZMsg) Hot Restart}: User-defined behavior with custom messages *
    • {@link #configure(ZMsg) Configure}: User-defined behavior with custom messages *
    • {@link #command(String, boolean)} ...: Custom commands of your own *
    • Exit: Definitive shutdown of the proxy and its control *
    * All the non-custom commands can be performed in asynchronous or synchronous mode. *
    *
  • Proxy mechanism ensured by pluggable pumps *
      *
    • with built-in low-level {@link org.zeromq.ZProxy.ZmqPump} (zmq.ZMQ): useful for performances *
    • with built-in high-level {@link org.zeromq.ZProxy.ZPump} (ZeroMQ): useful for {@link org.zeromq.ZProxy.ZPump.Transformer message transformation}, lower performances *
    • with your own-custom proxy pump implementing a {@link Pump 1-method interface} *
    *

*
* You can have all the above non-customizable features in about these lines of code: *

 * {@code
        final ZProxy.Proxy provider = new ZProxy.SimpleProxy()
        {
            public Socket create(ZContext ctx, ZProxy.Plug place, Object ... args)
            {
                assert ("TEST".equals(args[0]);
                Socket socket = null;
                if (place == ZProxy.Plug.FRONT) {
                    socket = ctx.createSocket(ZMQ.ROUTER);
                }
                if (place == ZProxy.Plug.BACK) {
                    socket = ctx.createSocket(ZMQ.DEALER);
                }
                return socket;
            }

            public void configure(Socket socket, ZProxy.Plug place, Object ... args)
            {
                assert ("TEST".equals(args[0]);
                int port = -1;
                if (place == ZProxy.Plug.FRONT) {
                    port = socket.bind("tcp://127.0.0.1:6660");
                }
                if (place == ZProxy.Plug.BACK) {
                    port = socket.bind("tcp://127.0.0.1:6661");
                }
                if (place == ZProxy.Plug.CAPTURE && socket != null) {
                    socket.bind("tcp://127.0.0.1:4263");
                }
            }
        };

        ZProxy proxy = ZProxy.newProxy("ProxyOne", provider, "ABRACADABRA", Arrays.asList("TEST"));
}
 * 
* Once created, the proxy is not started. You have to perform first a start command on it. * This choice was made because it is easier for a user to start it with one line of code than for the code to internally handle * different possible starting states (after all, someone may want the proxy started but paused at first or configured in a specific way?) * and because the a/sync stuff was funnier. Life is unfair ... * Or maybe an idea is floating in the air? *
* You can then use it like this: *
 * {@code
        final boolean async = false, sync = true;
        String status = null;
        status = proxy.status();
        status = proxy.pause(sync);
        status = proxy.start(async);
        status = proxy.restart(new ZMsg());
        status = proxy.status(async);
        status = proxy.stop(sync);
        boolean here = proxy.sign();
        ZMsg cfg = new ZMsg();
        msg.add("CONFIG-1");
        ZMsg rcvd = proxy.configure(cfg);
        proxy.exit();
        status = proxy.status(sync);
        assert (!proxy.started());
   }
 * 
* * A {@link #command(Command, boolean) programmatic interface} with enums is also available. * * */ // Proxy for 0MQ. public class ZProxy { /** * Possible places for sockets in the proxy. */ public enum Plug { FRONT, // The position of the frontend socket. BACK, // The position of the backend socket. CAPTURE // The position of the capture socket. } // Contract for socket creation and customizable configuration in proxy threading. public interface Proxy { /** * Creates and initializes (bind, options ...) the socket for the given plug in the proxy. * The proxy will close them afterwards, and the context as well if not provided in the constructor. * There is no need to keep a reference on the created socket or the context given in parameter. * * @param ctx the context used for initialization. * @param place the position for the future created socket in the proxy. * @param args the optional array of arguments that has been passed at the creation of the ZProxy. * @return the created socket. Possibly null only for capture. */ Socket create(ZContext ctx, Plug place, Object... args); /** * Configures the given socket. * * @param socket the socket to configure * @param place the position for the socket in the proxy * @param args the optional array of arguments that has been passed at the creation of the ZProxy. * @return true if successfully configured, otherwise false */ boolean configure(Socket socket, Plug place, Object... args) throws IOException; /** * Performs a hot restart of the given socket. * Usually an unbind/bind but you can use whatever method you like. * * @param cfg the custom configuration message sent by the control. * @param socket the socket to hot restart * @param place the position for the socket in the proxy * @param args the optional array of arguments that has been passed at the creation of the ZProxy. * @return true to perform a cold restart instead, false to do nothing. All the results will be collected from calls for all plugs. * If any of them returns true, the cold restart is performed. */ boolean restart(ZMsg cfg, Socket socket, Plug place, Object... args) throws IOException; /** * Configures the proxy with a custom message. *

* Note: you need to send one (1) mandatory custom response message with the pipe before the end of this call. * * @param pipe the control pipe * @param cfg the custom configuration message sent by the control * @param frontend the frontend socket * @param backend the backend socket * @param capture the optional capture socket * @param args the optional array of arguments that has been passed at the creation of the ZProxy. * @return true to continue the proxy, false to exit */ boolean configure(Socket pipe, ZMsg cfg, Socket frontend, Socket backend, Socket capture, Object... args); /** * Handles a custom command not recognized by the proxy. *

* Note: you need to send the current state at the end of the call. * * @param pipe the control pipe * @param cmd the unrecognized command * @param frontend the frontend socket * @param backend the backend socket * @param capture the optional capture socket * @param args the optional array of arguments that has been passed at the creation of the ZProxy. * * @return true to continue the proxy, false to exit */ boolean custom(Socket pipe, String cmd, Socket frontend, Socket backend, Socket capture, Object... args); // this may be useful abstract class SimpleProxy implements Proxy { @Override public boolean restart(ZMsg cfg, Socket socket, Plug place, Object... args) { return true; } @Override public boolean configure(Socket pipe, ZMsg cfg, Socket frontend, Socket backend, Socket capture, Object... args) { return true; } @Override public boolean custom(Socket pipe, String cmd, Socket frontend, Socket backend, Socket capture, Object... args) { return true; } } } /** * Creates a new proxy in a ZeroMQ way. * This proxy will be less efficient than the * {@link #newZProxy(ZContext, String, org.zeromq.ZProxy.Proxy, String, Object...) low-level one}. * * @param ctx the context used for the proxy. * Possibly null, in this case a new context will be created and automatically destroyed afterwards. * @param name the name of the proxy. Possibly null. * @param selector the creator of the selector used for the internal polling. Not null. * @param sockets the sockets creator of the proxy. Not null. * @param motdelafin the final word used to mark the end of the proxy. Null to disable this mechanism. * @param args an optional array of arguments that will be passed at the creation. * * @return the created proxy. * @deprecated use {@link #newZProxy(ZContext, String, Proxy, String, Object...)} instead. */ @Deprecated public static ZProxy newZProxy(ZContext ctx, String name, SelectorCreator selector, Proxy sockets, String motdelafin, Object... args) { return newZProxy(ctx, name, sockets, motdelafin, args); } /** * Creates a new proxy in a ZeroMQ way. * This proxy will be less efficient than the * {@link #newZProxy(ZContext, String, org.zeromq.ZProxy.Proxy, String, Object...) low-level one}. * * @param ctx the context used for the proxy. * Possibly null, in this case a new context will be created and automatically destroyed afterwards. * @param name the name of the proxy. Possibly null. * @param sockets the sockets creator of the proxy. Not null. * @param motdelafin the final word used to mark the end of the proxy. Null to disable this mechanism. * @param args an optional array of arguments that will be passed at the creation. * * @return the created proxy. */ public static ZProxy newZProxy(ZContext ctx, String name, Proxy sockets, String motdelafin, Object... args) { return new ZProxy(ctx, name, sockets, new ZPump(), motdelafin, args); } /** * Creates a new low-level proxy for better performances. * * @param ctx the context used for the proxy. * Possibly null, in this case a new context will be created and automatically destroyed afterwards. * @param name the name of the proxy. Possibly null. * @param selector the creator of the selector used for the internal polling. Not null. * @param sockets the sockets creator of the proxy. Not null. * @param motdelafin the final word used to mark the end of the proxy. Null to disable this mechanism. * @param args an optional array of arguments that will be passed at the creation. * * @return the created proxy. * @deprecated use {@link #newProxy(ZContext, String, Proxy, String, Object...)} instead. */ // creates a new low-level proxy @Deprecated public static ZProxy newProxy(ZContext ctx, String name, SelectorCreator selector, Proxy sockets, String motdelafin, Object... args) { return newProxy(ctx, name, sockets, motdelafin, args); } /** * Creates a new low-level proxy for better performances. * * @param ctx the context used for the proxy. * Possibly null, in this case a new context will be created and automatically destroyed afterwards. * @param name the name of the proxy. Possibly null. * @param sockets the sockets creator of the proxy. Not null. * @param motdelafin the final word used to mark the end of the proxy. Null to disable this mechanism. * @param args an optional array of arguments that will be passed at the creation. * * @return the created proxy. */ public static ZProxy newProxy(ZContext ctx, String name, Proxy sockets, String motdelafin, Object... args) { return new ZProxy(ctx, name, sockets, new ZmqPump(), motdelafin, args); } /** * Starts the proxy. * * @param sync true to read the status in synchronous way, false for asynchronous mode * @return the read status */ public String start(boolean sync) { return command(START, sync); } /** * Pauses the proxy. * A paused proxy will cease processing messages, causing * them to be queued up and potentially hit the high-water mark on the * frontend or backend socket, causing messages to be dropped, or writing * applications to block. * * @param sync true to read the status in synchronous way, false for asynchronous mode * @return the read status */ public String pause(boolean sync) { return command(PAUSE, sync); } /** * Stops the proxy. * * @param sync true to read the status in synchronous way, false for asynchronous mode * @return the read status */ public String stop(boolean sync) { return command(STOP, sync); } /** * Sends a command message to the proxy actor. * Can be useful for programmatic interfaces. * Does not works with commands {@link #CONFIG CONFIG} and {@link #RESTART RESTART}. * * @param command the command to execute. Not null. * @param sync true to read the status in synchronous way, false for asynchronous mode * @return the read status */ public String command(String command, boolean sync) { Utils.checkArgument( !CONFIG.equals(command), "CONFIG is not useable with that API. Please use configure(ZMsg) method"); Utils.checkArgument( !RESTART.equals(command), "RESTART is not useable with that API. Please use restart(ZMsg) method"); if (STATUS.equals(command)) { return status(sync); } if (EXIT.equals(command)) { return exit(); } // consume the status in the pipe String status = recvStatus(); if (agent.send(command)) { // the pipe is refilled if (sync) { status = status(true); } } return status; } /** * Sends a command message to the proxy actor. * Can be useful for programmatic interfaces. * Does not works with commands {@link Command#CONFIG CONFIG} and {@link Command#RESTART RESTART}. * * @param command the command to execute. * @param sync true to read the status in synchronous way, false for asynchronous mode * @return the read state */ public State command(Command command, boolean sync) { return State.valueOf(command(command.name(), sync)); } /** * Sends a command message to the proxy actor. * Can be useful for programmatic interfaces. * Works only with commands {@link Command#CONFIG CONFIG} and {@link Command#RESTART RESTART}. * * @param command the command to execute. * @param msg the custom message to transmit. * @param sync true to read the status in synchronous way, false for asynchronous mode * @return the response message */ public ZMsg command(Command command, ZMsg msg, boolean sync) { if (command == Command.CONFIG) { return configure(msg); } if (command == Command.RESTART) { String status = restart(msg); msg = new ZMsg(); msg.add(status); return msg; } return null; } /** * Configures the proxy. * The distant side has to send back one (1) mandatory response message. * * @param msg the custom message sent as configuration tip * @return the mandatory response message of the configuration. */ public ZMsg configure(ZMsg msg) { msg.addFirst(CONFIG); if (agent.send(msg)) { // consume the status in the pipe recvStatus(); ZMsg reply = agent.recv(); assert (reply != null); // refill the pipe with status agent.send(STATUS); return reply; } return null; } /** * Restarts the proxy. Stays alive. * * @param hot null to make a cold restart (closing then re-creation of the sockets) * or a configuration message to perform a configurable hot restart, */ public String restart(ZMsg hot) { ZMsg msg = new ZMsg(); msg.add(RESTART); final boolean cold = hot == null; if (cold) { msg.add(Boolean.toString(false)); } else { msg.add(Boolean.toString(true)); msg.append(hot); } String status = EXITED; if (agent.send(msg)) { status = status(false); } return status; } /** * Stops the proxy and exits. * * @param sync forced to true to read the status in synchronous way. * @return the read status. * @deprecated The call is synchronous: the sync parameter is ignored, * as it leads to many mistakes in case of a provided ZContext. */ @Deprecated public String exit(boolean sync) { return exit(); } /** * Stops the proxy and exits. * The call is synchronous. * * @return the read status. * */ public String exit() { agent.send(EXIT); exit.awaitSilent(); agent.close(); return EXITED; } /** * Inquires for the status of the proxy. * This call is synchronous. */ public String status() { return status(true); } /** * Inquires for the status of the proxy. * * @param sync true to read the status in synchronous way, false for asynchronous mode. * If false, you get the last cached status of the proxy */ public String status(boolean sync) { if (exit.isExited()) { return EXITED; } try { String status = recvStatus(); if (agent.send(STATUS) && sync) { // wait for the response to emulate sync status = recvStatus(); // AND refill a status if (EXITED.equals(status) || !agent.send(STATUS)) { return EXITED; } } return status; } catch (ZMQException e) { return EXITED; } } // receives the last known state of the proxy private String recvStatus() { if (!agent.sign()) { return EXITED; } // receive the status response final ZMsg msg = agent.recv(); if (msg == null) { return EXITED; } String status = msg.popString(); msg.destroy(); return status; } /** * Binary inquiry for the status of the proxy. */ public boolean isStarted() { return started(); } /** * Binary inquiry for the status of the proxy. */ public boolean started() { String status = status(true); return STARTED.equals(status); } // to handle commands in a more java-centric way public enum Command { START, PAUSE, STOP, RESTART, EXIT, STATUS, CONFIG } // commands for the control pipe private static final String START = Command.START.name(); private static final String PAUSE = Command.PAUSE.name(); private static final String STOP = Command.STOP.name(); private static final String RESTART = Command.RESTART.name(); private static final String EXIT = Command.EXIT.name(); private static final String STATUS = Command.STATUS.name(); private static final String CONFIG = Command.CONFIG.name(); // to handle states in a more java-centric way public enum State { ALIVE, STARTED, PAUSED, STOPPED, EXITED } // state responses from the control pipe public static final String STARTED = State.STARTED.name(); public static final String PAUSED = State.PAUSED.name(); public static final String STOPPED = State.STOPPED.name(); public static final String EXITED = State.EXITED.name(); // defines the very first time where no command changing the state has been issued public static final String ALIVE = State.ALIVE.name(); private static final AtomicInteger counter = new AtomicInteger(); // the endpoint to the distant proxy actor private final ZAgent agent; // the synchronizer for exiting private final Exit exit; /** * Creates a new unnamed proxy. * * @param selector the creator of the selector used for the proxy. * @param creator the creator of the sockets of the proxy. * @param motdelafin the final word used to mark the end of the proxy. Null to disable this mechanism. * @param args an optional array of arguments that will be passed at the creation. * @deprecated use {@link #ZProxy(Proxy, String, Object...)} instead. */ @Deprecated public ZProxy(SelectorCreator selector, Proxy creator, String motdelafin, Object... args) { this(creator, motdelafin, args); } /** * Creates a new unnamed proxy. * * @param creator the creator of the sockets of the proxy. * @param motdelafin the final word used to mark the end of the proxy. Null to disable this mechanism. * @param args an optional array of arguments that will be passed at the creation. */ public ZProxy(Proxy creator, String motdelafin, Object... args) { this(null, creator, null, motdelafin, args); } /** * Creates a new named proxy. * * @param name the name of the proxy (used in threads naming). * @param selector the creator of the selector used for the proxy. * @param creator the creator of the sockets of the proxy. * @param motdelafin the final word used to mark the end of the proxy. Null to disable this mechanism. * @param args an optional array of arguments that will be passed at the creation. * @deprecated use {@link #ZProxy(String, Proxy, String, Object...)} instead. */ @Deprecated public ZProxy(String name, SelectorCreator selector, Proxy creator, String motdelafin, Object... args) { this(name, creator, motdelafin, args); } /** * Creates a new named proxy. * * @param name the name of the proxy (used in threads naming). * @param creator the creator of the sockets of the proxy. * @param motdelafin the final word used to mark the end of the proxy. Null to disable this mechanism. * @param args an optional array of arguments that will be passed at the creation. */ public ZProxy(String name, Proxy creator, String motdelafin, Object... args) { this(name, creator, null, motdelafin, args); } /** * Creates a new named proxy. * * @param ctx the main context used. * If null, a new context will be created and closed at the stop of the operation. * If not null, it is the responsibility of the call to close it. * @param name the name of the proxy (used in threads naming). * @param selector the creator of the selector used for the proxy. * @param sockets the creator of the sockets of the proxy. * @param pump the pump used for the proxy * @param motdelafin the final word used to mark the end of the proxy. Null to disable this mechanism. * @param args an optional array of arguments that will be passed at the creation. * @deprecated use {@link #ZProxy(ZContext, String, Proxy, Pump, String, Object...)} instead. */ @Deprecated public ZProxy(ZContext ctx, String name, SelectorCreator selector, Proxy sockets, Pump pump, String motdelafin, Object... args) { this(ctx, name, sockets, pump, motdelafin, args); } /** * Creates a new named proxy. A new context will be created and closed at the stop of the operation. * * @param name the name of the proxy (used in threads naming). * @param sockets the creator of the sockets of the proxy. * @param pump the pump used for the proxy * @param motdelafin the final word used to mark the end of the proxy. Null to disable this mechanism. * @param args an optional array of arguments that will be passed at the creation. */ public ZProxy(String name, Proxy sockets, Pump pump, String motdelafin, Object... args) { this(null, name, sockets, pump, motdelafin, args); } /** * Creates a new named proxy. * * @param ctx the main context used. * If null, a new context will be created and closed at the stop of the operation. * If not null, it is the responsibility of the call to close it. * @param name the name of the proxy (used in threads naming). * @param sockets the creator of the sockets of the proxy. * @param pump the pump used for the proxy * @param motdelafin the final word used to mark the end of the proxy. Null to disable this mechanism. * @param args an optional array of arguments that will be passed at the creation. */ public ZProxy(ZContext ctx, String name, Proxy sockets, Pump pump, String motdelafin, Object... args) { super(); // arguments parsing if (pump == null) { pump = new ZmqPump(); } int count = 1; count += args.length; Object[] vars = new Object[count]; vars[0] = sockets; Actor shadow = null; // copy the arguments and retrieve the last optional shadow given in input for (int index = 0; index < args.length; ++index) { Object arg = args[index]; if (arg instanceof Actor) { shadow = (Actor) arg; } vars[index + 1] = arg; } // handle the actor int id = counter.incrementAndGet(); Actor actor = new ProxyActor(name, pump, id); if (shadow != null) { actor = new ZActor.Duo(actor, shadow); } ZActor zactor = new ZActor(ctx, actor, motdelafin, vars); agent = zactor.agent(); // NB: the zactor is also its own agent exit = zactor.exit(); } // defines a pump that will flow messages from one socket to another public interface Pump { /** * Transfers a message from one source to one destination, with an optional capture. * * @param src the plug of the source socket * @param source the socket where to receive the message from. * @param capture the optional sockets where to send the message to. Possibly null. * @param dst the plug of the destination socket * @param destination the socket where to send the message to. * * @return false in case of error or interruption, true if successfully transferred the message */ boolean flow(Plug src, Socket source, Socket capture, Plug dst, Socket destination); } // acts in background to proxy messages private static final class ProxyActor extends ZActor.SimpleActor { // the states container of the proxy private static final class State { @Override public String toString() { return "State [alive=" + alive + ", started=" + started + ", paused=" + paused + ", restart=" + restart + ", hot=" + hot + "]"; } // are we alive ? private boolean alive = false; // are we started ? private boolean started = false; // are we paused ? private boolean paused = false; // controls creation of a new agent if asked of a cold restart private boolean restart = false; // one-shot configuration for hot restart private ZMsg hot; } // the state of the proxy private final State state = new State(); // used to transfer message from one socket to another private final Pump transport; // the nice name of the proxy private final String name; // the provider of the sockets private Proxy provider; // the sockets creator user arguments private Object[] args; private Socket frontend; private Socket backend; private Socket capture; // creates a new Proxy actor. public ProxyActor(String name, Pump transport, int id) { if (name == null) { // default basic name this.name = String.format("ZProxy-%sd", id); } else { this.name = name; } this.transport = transport; } @Override public String premiere(Socket pipe) { ZMsg reply = new ZMsg(); reply.add(ALIVE); reply.send(pipe); return name; } // creates the sockets before the start of the proxy @Override public List createSockets(ZContext ctx, Object... args) { provider = (Proxy) args[0]; this.args = new Object[args.length - 1]; System.arraycopy(args, 1, this.args, 0, this.args.length); frontend = provider.create(ctx, Plug.FRONT, this.args); capture = provider.create(ctx, Plug.CAPTURE, this.args); backend = provider.create(ctx, Plug.BACK, this.args); assert (frontend != null); assert (backend != null); return Arrays.asList(frontend, backend); } @Override public void start(Socket pipe, List sockets, ZPoller poller) { // init the state machine state.alive = true; state.restart = false; } // Process a control message @Override public boolean backstage(Socket pipe, ZPoller poller, int events) { assert (state.hot == null); String cmd = pipe.recvStr(); // a message has been received from the API if (START.equals(cmd)) { if (start(poller)) { return status().send(pipe); } // unable to start the proxy, exit state.restart = false; return false; } else if (STOP.equals(cmd)) { stop(); return status().send(pipe); } else if (PAUSE.equals(cmd)) { pause(poller, true); return status().send(pipe); } else if (RESTART.equals(cmd)) { String val = pipe.recvStr(); boolean hot = Boolean.parseBoolean(val); return restart(pipe, hot); } else if (STATUS.equals(cmd)) { return status().send(pipe); } else if (CONFIG.equals(cmd)) { ZMsg cfg = ZMsg.recvMsg(pipe); boolean rc = provider.configure(pipe, cfg, frontend, backend, capture, args); cfg.destroy(); return rc; } else if (EXIT.equals(cmd)) { // stops the proxy and the agent. // the status will be sent at the end of the loop state.restart = false; } else { return provider.custom(pipe, cmd, frontend, backend, capture, args); } return false; } // returns the status private ZMsg status() { ZMsg reply = new ZMsg(); if (!state.alive) { reply.add(EXITED); } else if (state.paused) { reply.add(PAUSED); } else if (state.started) { reply.add(STARTED); } else { reply.add(STOPPED); } return reply; } // starts the proxy sockets private boolean start(ZPoller poller) { boolean success = true; if (!state.started) { try { success = false; success |= provider.configure(frontend, Plug.FRONT, args); success |= provider.configure(backend, Plug.BACK, args); success |= provider.configure(capture, Plug.CAPTURE, args); state.started = true; } catch (RuntimeException | IOException e) { e.printStackTrace(); // unable to configure proxy, exit state.restart = false; state.started = false; return false; } } pause(poller, false); return success; } // pauses the proxy sockets private boolean pause(ZPoller poller, boolean pause) { state.paused = pause; if (pause) { poller.unregister(frontend); poller.unregister(backend); // TODO why not a mechanism for eventually flushing the sockets during the pause? } else { poller.register(frontend, ZPoller.POLLIN); poller.register(backend, ZPoller.POLLIN); // Now Wait also until there are either requests or replies to process. } return true; } private boolean stop() { // restart the actor in stopped state state.started = false; state.paused = false; // close connections state.restart = true; return true; } // handles the restart command in both modes private boolean restart(Socket pipe, boolean hot) { state.restart = true; if (hot) { assert (provider != null); state.hot = ZMsg.recvMsg(pipe); // continue with the same agent return true; } else { // stop the loop and restart a new agent // with the same started state // the next loop will refill the updated status return false; } } @Override public long looping(Socket pipe, ZPoller poller) { state.hot = null; return super.looping(pipe, poller); } // a message has been received for the proxy to process @Override public boolean stage(Socket socket, Socket pipe, ZPoller poller, int events) { if (socket == frontend) { // Process a request. return transport.flow(Plug.FRONT, frontend, capture, Plug.BACK, backend); } if (socket == backend) { // Process a reply. return transport.flow(Plug.BACK, backend, capture, Plug.FRONT, frontend); } return false; } @Override public boolean looped(Socket pipe, ZPoller poller) { if (state.restart) { if (state.hot == null) { // caught the cold restart return false; } else { // caught the hot restart ZMsg cfg = state.hot; state.hot = null; // we perform a cold restart if the provider says so boolean cold; ZMsg dup = cfg.duplicate(); try { cold = provider.restart(dup, frontend, Plug.FRONT, this.args); dup.destroy(); dup = cfg.duplicate(); cold |= provider.restart(dup, backend, Plug.BACK, this.args); dup.destroy(); dup = cfg.duplicate(); cold |= provider.restart(dup, capture, Plug.CAPTURE, this.args); dup.destroy(); cfg.destroy(); } catch (RuntimeException | IOException e) { e.printStackTrace(); state.restart = false; return false; } // cold restart means the loop has to stop. return !cold; } } return true; } // called in the proxy thread when it stopped. @Override public boolean destroyed(ZContext ctx, Socket pipe, ZPoller poller) { if (capture != null) { capture.close(); } state.alive = false; if (!state.restart) { status().send(pipe); } return state.restart; } } /** * A pump that reads a message as a whole before transmitting it. * It offers a way to transform messages for capture and destination. */ public static class ZPump implements Pump { private static final Identity IDENTITY = new Identity(); // the messages transformer private final Transformer transformer; // transforms one message into another public interface Transformer { /** * Transforms a ZMsg into another ZMsg. * Please note that this will be used during the message transfer, * so lengthy operations will have a cost on performances by definition. * If you return back another message than the one given in input, then this one has to be destroyed by you. * @param msg the message to transform * @param src the source plug * @param dst the destination plug * @return the transformed message */ ZMsg transform(ZMsg msg, Plug src, Plug dst); } private static class Identity implements Transformer { @Override public ZMsg transform(ZMsg msg, Plug src, Plug dst) { return msg; } } public ZPump() { this(null); } public ZPump(Transformer transformer) { super(); this.transformer = transformer == null ? IDENTITY : transformer; } @Override public boolean flow(Plug splug, Socket source, Socket capture, Plug dplug, Socket destination) { boolean success; // we read the whole message ZMsg msg = ZMsg.recvMsg(source); if (msg == null) { return false; } if (capture != null) { // Copy transformed message to capture socket if any message // TODO what if the transformer modifies or destroys the original message ? ZMsg cpt = transformer.transform(msg, splug, Plug.CAPTURE); // boolean destroy = !msg.equals(cpt); // TODO ?? which one boolean destroy = msg != cpt; success = cpt.send(capture, destroy); if (!success) { // not successful, but we can still try to send it to the destination } } ZMsg dst = transformer.transform(msg, splug, dplug); // we send the whole transformed message success = dst.send(destination); // finished msg.destroy(); return success; } } /** * A specialized transport for better transmission purposes * that will send each packets individually instead of the whole message. */ private static final class ZmqPump implements Pump { // transfers each message as a whole by sending each packet received to the capture socket @Override public boolean flow(Plug splug, Socket source, Socket capture, Plug dplug, Socket destination) { boolean rc; SocketBase src = source.base(); SocketBase dst = destination.base(); SocketBase cpt = capture == null ? null : capture.base(); // we transfer the whole message while (true) { // we read the packet Msg msg = src.recv(0); if (msg == null) { return false; } long more = src.getSocketOpt(zmq.ZMQ.ZMQ_RCVMORE); if (more < 0) { return false; } // Copy message to capture socket if any packet if (cpt != null) { Msg ctrl = new Msg(msg); rc = cpt.send(ctrl, more > 0 ? zmq.ZMQ.ZMQ_SNDMORE : 0); if (!rc) { // not successful, but we can still try to send it to the destination } } // we send the packet rc = dst.send(msg, more > 0 ? zmq.ZMQ.ZMQ_SNDMORE : 0); if (!rc) { return false; } if (more == 0) { break; } } return true; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy