@Test public void workerOrchestrator() throws InterruptedException { Reactor reactor = Reactors.reactor(env, Environment.WORK_QUEUE); CountDownLatch latch = new CountDownLatch(3); reactor.on( Selectors.$("worker"), new Consumer() { @Override public void accept(Object o) { System.out.println(Thread.currentThread().getName() + " worker " + o); reactor.notify("orchestrator", Event.wrap(1000)); latch.countDown(); System.out.println(Thread.currentThread().getName() + " ok"); } }); reactor.on( Selectors.$("orchestrator"), new Consumer<Event<Integer>>() { @Override public void accept(Event<Integer> event) { sendTask(); } void sendTask() { System.out.println(Thread.currentThread().getName() + " sendTask "); reactor.notify("worker", Event.wrap(latch.getCount())); latch.countDown(); } }); reactor.notify("orchestrator", Event.wrap(1000)); Assert.isTrue(latch.await(10, TimeUnit.SECONDS)); }
@Override public <V, X extends Throwable> StateMachine<T> when( Class<X> errorType, final Consumer<Tuple3<V, T, X>> errorConsumer) { observable.on( Selectors.type(errorType), new Consumer<Event<Throwable>>() { @SuppressWarnings("unchecked") @Override public void accept(Event<Throwable> ev) { if (null != errorConsumer && ev.getData() instanceof StateException) { V state = (V) ((StateException) ev.getData()).state; X cause = (X) ev.getData().getCause(); T data = (T) ((StateException) ev.getData()).data; errorConsumer.accept(Tuple.of(state, data, cause)); } } }); return this; }
@Override public <V> StateMachine<T> on(final V state, final Function<T, T> fn) { if (strict) { Assert.isTrue(states.contains(state), "State " + state + " has not been defined"); } observable.on( (state instanceof Selector ? (Selector) state : Selectors.object(state)), new Consumer<Event<T>>() { @SuppressWarnings("unchecked") @Override public void accept(final Event<T> data) { try { data.setData(fn.apply(data.getData())); } catch (Throwable t) { observable.notify( t.getClass(), Event.wrap(new StateException(t, state, data.getData()))); } } }); return this; }
/** * Subclasses should register the given {@link reactor.net.NetChannel} for later use. * * @param ioChannel The channel object. * @param netChannel The {@link NetChannel}. * @param <C> The type of the channel object. * @return {@link reactor.event.registry.Registration} of this channel in the {@link Registry}. */ protected <C> Registration<? extends NetChannel<IN, OUT>> register( @Nonnull C ioChannel, @Nonnull NetChannel<IN, OUT> netChannel) { Assert.notNull(ioChannel, "Channel cannot be null."); Assert.notNull(netChannel, "NetChannel cannot be null."); return netChannels.register(Selectors.$(ioChannel), netChannel); }
/** * Abstract base class that implements common functionality shared by clients and servers. * * @author Jon Brisbin */ public abstract class AbstractNetPeer<IN, OUT> { private final Registry<NetChannel<IN, OUT>> netChannels = new CachingRegistry<NetChannel<IN, OUT>>(); private final Event<AbstractNetPeer<IN, OUT>> selfEvent = Event.wrap(this); private final Selector open = Selectors.$(); private final Selector close = Selectors.$(); private final Selector start = Selectors.$(); private final Selector shutdown = Selectors.$(); private final Environment env; private final Reactor reactor; private final Codec<Buffer, IN, OUT> codec; private final Collection<Consumer<NetChannel<IN, OUT>>> consumers; protected AbstractNetPeer( @Nonnull Environment env, @Nonnull Reactor reactor, @Nullable Codec<Buffer, IN, OUT> codec, @Nonnull Collection<Consumer<NetChannel<IN, OUT>>> consumers) { this.env = env; this.reactor = reactor; this.codec = codec; this.consumers = consumers; for (final Consumer<NetChannel<IN, OUT>> consumer : consumers) { reactor.on( open, new Consumer<Event<NetChannel<IN, OUT>>>() { @Override public void accept(Event<NetChannel<IN, OUT>> ev) { consumer.accept(ev.getData()); } }); } } public Promise<Boolean> close() { Deferred<Boolean, Promise<Boolean>> d = Promises.defer(env, reactor.getDispatcher()); close(d); return d.compose(); } public void close(@Nullable final Consumer<Boolean> onClose) { reactor.schedule( new Consumer<Void>() { @Override public void accept(Void v) { for (Registration<? extends NetChannel<IN, OUT>> reg : getChannels()) { if (null == reg) { continue; } doCloseChannel(reg.getObject()); } getChannels().clear(); doClose(onClose); } }, null); } /** * Subclasses should register the given {@link reactor.net.NetChannel} for later use. * * @param ioChannel The channel object. * @param netChannel The {@link NetChannel}. * @param <C> The type of the channel object. * @return {@link reactor.event.registry.Registration} of this channel in the {@link Registry}. */ protected <C> Registration<? extends NetChannel<IN, OUT>> register( @Nonnull C ioChannel, @Nonnull NetChannel<IN, OUT> netChannel) { Assert.notNull(ioChannel, "Channel cannot be null."); Assert.notNull(netChannel, "NetChannel cannot be null."); return netChannels.register(Selectors.$(ioChannel), netChannel); } /** * Find the {@link NetChannel} for the given IO channel object. * * @param ioChannel The channel object. * @param <C> The type of the channel object. * @return The {@link NetChannel} associated with the given channel. */ protected <C> NetChannel<IN, OUT> select(@Nonnull C ioChannel) { Assert.notNull(ioChannel, "Channel cannot be null."); Iterator<Registration<? extends NetChannel<IN, OUT>>> channs = netChannels.select(ioChannel).iterator(); if (channs.hasNext()) { return channs.next().getObject(); } else { NetChannel<IN, OUT> conn = createChannel(ioChannel); register(ioChannel, conn); notifyOpen(conn); return conn; } } /** * Close the given channel. * * @param channel The channel object. * @param <C> The type of the channel object. */ protected <C> void close(@Nonnull C channel) { Assert.notNull(channel, "Channel cannot be null"); for (Registration<? extends NetChannel<IN, OUT>> reg : netChannels.select(channel)) { NetChannel<IN, OUT> chann = reg.getObject(); reg.cancel(); notifyClose(chann); } } /** * Subclasses should implement this method and provide a {@link NetChannel} object. * * @param ioChannel The IO channel object to associate with this {@link reactor.net.NetChannel}. * @param <C> The type of the channel object. * @return The new {@link NetChannel} object. */ protected abstract <C> NetChannel<IN, OUT> createChannel(C ioChannel); /** Notify this server's consumers that the server has started. */ protected void notifyStart(final Runnable started) { getReactor().notify(start.getObject(), selfEvent); if (null != started) { getReactor() .schedule( new Consumer<Runnable>() { @Override public void accept(Runnable r) { r.run(); } }, started); } } /** * Notify this client's consumers than a global error has occurred. * * @param error The error to notify. */ protected void notifyError(@Nonnull Throwable error) { Assert.notNull(error, "Error cannot be null."); reactor.notify(error.getClass(), Event.wrap(error)); } /** * Notify this peer's consumers that the channel has been opened. * * @param channel The channel that was opened. */ protected void notifyOpen(@Nonnull NetChannel<IN, OUT> channel) { reactor.notify(open.getObject(), Event.wrap(channel)); } /** * Notify this peer's consumers that the given channel has been closed. * * @param channel The channel that was closed. */ protected void notifyClose(@Nonnull NetChannel<IN, OUT> channel) { reactor.notify(close.getObject(), Event.wrap(channel)); } /** Notify this server's consumers that the server has stopped. */ protected void notifyShutdown() { getReactor().notify(shutdown.getObject(), selfEvent); } /** * Get the {@link Codec} in use. * * @return The codec. May be {@literal null}. */ @Nullable protected Codec<Buffer, IN, OUT> getCodec() { return codec; } @Nonnull protected Environment getEnvironment() { return env; } @Nonnull protected Reactor getReactor() { return reactor; } @Nonnull protected Collection<Consumer<NetChannel<IN, OUT>>> getConsumers() { return consumers; } @Nonnull protected Registry<NetChannel<IN, OUT>> getChannels() { return netChannels; } /** * Subclasses should implement this method to perform the actual IO channel close. * * @param onClose */ protected void doClose(@Nullable Consumer<Boolean> onClose) { getReactor().schedule(onClose, null); } /** * Close the given channel. * * @param channel */ protected void doCloseChannel(NetChannel<IN, OUT> channel) { channel.close(); } }
public static void main(String[] args) throws Exception { Environment env = new Environment(); final TradeServer server = new TradeServer(); // Use a Reactor to dispatch events using the high-speed Dispatcher final Reactor serverReactor = Reactors.reactor(env); // Create a single key and Selector for efficiency final Selector tradeExecute = Selectors.object("trade.execute"); // For each Trade event, execute that on the server and notify connected clients // because each client that connects links to the serverReactor serverReactor.on( tradeExecute, (Event<Trade> ev) -> { server.execute(ev.getData()); // Since we're async, for this test, use a latch to tell when we're done latch.countDown(); }); @SuppressWarnings("serial") WebSocketServlet wss = new WebSocketServlet() { @Override public void configure(WebSocketServletFactory factory) { factory.setCreator( (req, resp) -> new WebSocketListener() { AtomicLong counter = new AtomicLong(); @Override public void onWebSocketBinary(byte[] payload, int offset, int len) {} @Override public void onWebSocketClose(int statusCode, String reason) {} @Override public void onWebSocketConnect(final Session session) { LOG.info("Connected a websocket client: {}", session); // Keep track of a rolling average final AtomicReference<Float> avg = new AtomicReference<>(0f); serverReactor.on( tradeExecute, (Event<Trade> ev) -> { Trade t = ev.getData(); avg.set((avg.get() + t.getPrice()) / 2); // Send a message every 1000th trade. // Otherwise, we completely overwhelm the browser and network. if (counter.incrementAndGet() % 1000 == 0) { try { session .getRemote() .sendString(String.format("avg: %s", avg.get())); } catch (IOException e) { if (!"Failed to write bytes".equals(e.getMessage())) { e.printStackTrace(); } } } }); } @Override public void onWebSocketError(Throwable cause) {} @Override public void onWebSocketText(String message) {} }); } }; serve(wss); LOG.info( "Connect websocket clients now (waiting for 10 seconds).\n" + "Open websocket/src/main/webapp/ws.html in a browser..."); Thread.sleep(10000); // Start a throughput timer startTimer(); // Publish one event per trade for (int i = 0; i < totalTrades; i++) { // Pull next randomly-generated Trade from server Trade t = server.nextTrade(); // Notify the Reactor the event is ready to be handled serverReactor.notify(tradeExecute.getObject(), Event.wrap(t)); } // Stop throughput timer and output metrics endTimer(); server.stop(); }