@Override @HasPermission(action = UGC_UPDATE, type = SocialPermission.class) public UGC update( @SecuredObject final String ugcId, final String body, final String subject, final String contextId, final Map attributes) throws SocialException, UGCNotFound { log.debug("logging.ugc.updateUgc", ugcId); try { final Profile currentProfile = SocialSecurityUtils.getCurrentProfile(); boolean moderateByMail = Boolean.parseBoolean( tenantConfigurationService.getProperty(contextId, "moderateByMailEnable").toString()); if (!ObjectId.isValid(ugcId)) { throw new IllegalArgumentException("Given UGC Id is not valid"); } T toUpdate = (T) ugcRepository.findUGC(contextId, ugcId); if (toUpdate == null) { throw new IllegalArgumentException("UGC with Id " + ugcId + " does not exists"); } if (StringUtils.isNotBlank(body)) { toUpdate.setBody(body); } if (StringUtils.isNotBlank(subject)) { toUpdate.setBody(subject); } pipeline.processUgc(toUpdate); if (moderateByMail && !SocialSecurityUtils.isProfileModeratorOrAdmin(currentProfile, contextId)) { if (toUpdate instanceof SocialUgc) { ((SocialUgc) toUpdate).setModerationStatus(ModerationStatus.UNMODERATED); } } ugcRepository.update(ugcId, toUpdate, false, false); final SocialEvent<T> event = new SocialEvent<>( toUpdate, SocialSecurityUtils.getCurrentProfile().getId().toString(), UGCEvent.UPDATE); event.setAttribute("baseUrl", calculateBaseUrl()); reactor.notify(UGCEvent.UPDATE.getName(), Event.wrap(event)); if (attributes != null && !attributes.isEmpty()) { toUpdate.getAttributes().putAll(attributes); // ToDo This should be one query, problem is with deep attributes !! setAttributes(toUpdate.getId().toString(), contextId, toUpdate.getAttributes()); reactor.notify(UGCEvent.UPDATE_ATTRIBUTES, Event.wrap(attributes)); } log.info("logging.ugc.updatedUgc", ugcId); return toUpdate; } catch (MongoDataException ex) { log.error("logging.ugc.unableToUpdateUgc", ex); throw new UGCException("Unable to removeWatcher UGC", ex); } }
private void threadPoolDispatcher() { Boundary b = new Boundary(); // Bind to a Selector using an anonymous object Tuple2<Selector, Object> anon = $(); threadPoolReactor.on(anon.getT1(), b.bind(consumer, 3)); threadPoolReactor.notify(anon.getT2(), Event.wrap(threadPoolReactor)); threadPoolReactor.notify(anon.getT2(), Event.wrap(threadPoolReactor)); threadPoolReactor.notify(anon.getT2(), Event.wrap(threadPoolReactor)); b.await(); }
@Override @HasPermission(action = UGC_UPDATE, type = SocialPermission.class) public FileInfo updateAttachment( @SecuredObject final String ugcId, final String contextId, final String attachmentId, final InputStream newAttachment) throws UGCException, FileNotFoundException { if (!ObjectId.isValid(ugcId)) { throw new IllegalArgumentException("Given Ugc Id is not valid"); } if (!ObjectId.isValid(attachmentId)) { throw new IllegalArgumentException("Given UGC Id is not valid"); } try { T ugc = (T) ugcRepository.findUGC(contextId, ugcId); if (ugc == null) { throw new IllegalUgcException("Given UGC Id does not exists"); } FileInfo oldInfo = ugcRepository.getFileInfo(attachmentId); ugc.getAttachments().remove(oldInfo); FileInfo newInfo = ugcRepository.updateFile( new ObjectId(attachmentId), newAttachment, oldInfo.getFileName(), oldInfo.getContentType(), true); ugc.getAttachments().add(newInfo); ugcRepository.update(ugcId, ugc); reactor.notify( UGCEvent.DELETE_ATTACHMENT.getName(), Event.wrap(new SocialEvent<>(ugcId, attachmentId, UGCEvent.DELETE_ATTACHMENT))); reactor.notify( UGCEvent.ADD_ATTACHMENT.getName(), Event.wrap( new SocialEvent<>( ugcId, new InputStream[] {new CloseShieldInputStream(newAttachment)}), UGCEvent.ADD_ATTACHMENT)); return newInfo; } catch (MongoDataException e) { log.error("logging.ugc.attachmentError"); throw new UGCException("Unable to removeWatcher Attachment"); } catch (FileExistsException e) { log.error("logging.ugc.attachmentNotFound", attachmentId); throw new UGCException("Unable to find attachment with given id", e); } }
@Override @HasPermission(action = UGC_UPDATE, type = SocialPermission.class) public void removeAttachment( @SecuredObject final String ugcId, final String contextId, final String attachmentId) throws UGCException, FileNotFoundException { try { UGC ugc = ugcRepository.findUGC(contextId, ugcId); if (ugc == null) { throw new IllegalUgcException("UGC with given Id does not exist"); } if (!ObjectId.isValid(attachmentId)) { throw new IllegalArgumentException("Given Attachment id is not valid"); } ObjectId attachmentOid = new ObjectId(attachmentId); FileInfo info = ugcRepository.getFileInfo(attachmentOid); if (!info.getStoreName().startsWith(File.separator + contextId)) { throw new IllegalSocialQueryException( "Given Attachment does not belong to the given context"); } ugc.getAttachments().remove(info); ugcRepository.deleteFile(attachmentOid); ugcRepository.update(ugcId, ugc); reactor.notify( UGCEvent.DELETE_ATTACHMENT.getName(), Event.wrap(new SocialEvent<>(ugcId, attachmentId, UGCEvent.DELETE_ATTACHMENT))); } catch (MongoDataException e) { log.error("logging.ugc.attachmentToRemove", e, attachmentId); throw new UGCException("Unable to save File to UGC"); } }
/** * Accept an image upload via POST and notify a Reactor that the image needs to be thumbnailed. * Asynchronously respond to the client when the thumbnailing has completed. * * @param channel the channel on which to send an HTTP response * @param thumbnail a reference to the shared thumbnail path * @param reactor the Reactor on which to publish events * @return a consumer to handle HTTP requests */ public static Consumer<FullHttpRequest> thumbnailImage( NetChannel<FullHttpRequest, FullHttpResponse> channel, AtomicReference<Path> thumbnail, Reactor reactor) { return req -> { if (req.getMethod() != HttpMethod.POST) { channel.send(badRequest(req.getMethod() + " not supported for this URI")); return; } // write to a temp file Path imgIn = null; try { imgIn = readUpload(req.content()); } catch (IOException e) { throw new IllegalStateException(e.getMessage(), e); } // Asynchronously thumbnail the image to 250px on the long side reactor.sendAndReceive( "thumbnail", Event.wrap(imgIn), ev -> { thumbnail.set(ev.getData()); channel.send(redirect()); }); }; }
@Override @HasPermission(action = UGC_UPDATE, type = SocialPermission.class) public void setAttributes( @SecuredObject final String ugcId, final String contextId, final Map attributes) throws SocialException, UGCNotFound { log.debug("logging.ugc.addingAttributes", attributes, ugcId, contextId); try { T toUpdate = (T) ugcRepository.findUGC(contextId, ugcId); if (toUpdate == null) { throw new UGCNotFound("Unable to found ugc with id " + ugcId); } final Map attrs = toUpdate.getAttributes(); attrs.putAll(attrs); ugcRepository.setAttributes(ugcId, contextId, attrs); final SocialEvent<T> event = new SocialEvent<T>( ugcId, attributes, SocialSecurityUtils.getCurrentProfile().getId().toString(), UGCEvent.UPDATE_ATTRIBUTES); event.setAttribute("baseUrl", calculateBaseUrl()); reactor.notify(UGCEvent.UPDATE_ATTRIBUTES.getName(), Event.wrap(event)); } catch (MongoDataException ex) { log.debug("logging.ugc.unableToAddAttrs", ex, attributes, ugcId, contextId); throw new UGCException("Unable to add Attributes to UGC", ex); } }
public void publishJokes() throws InterruptedException { long start = System.currentTimeMillis(); AtomicInteger counter = new AtomicInteger(); for (int i = 0; i < numberOfJokes; i++) { reactor.notify("jokes", Event.wrap(counter.getAndIncrement())); } latch.await(); long elapsed = System.currentTimeMillis() - start; System.out.println("Elapsed time: " + elapsed + "ms"); System.out.println("Average time per joke: " + elapsed / numberOfJokes + "ms"); }
public static void main(String[] args) throws InterruptedException { final TradeServer server = new TradeServer(); // Use a Reactor to dispatch events using the default Dispatcher Reactor reactor = new Reactor(); // Create a single Selector for efficiency Selector trade = $("trade.execute"); // For each Trade event, execute that on the server reactor.on( trade, new Consumer<Event<Trade>>() { @Override public void accept(Event<Trade> tradeEvent) { server.execute(tradeEvent.getData()); // Since we're async, for this test, use a latch to tell when we're done latch.countDown(); } }); // 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 reactor.notify(trade, Fn.event(t)); } // Stop throughput timer and output metrics endTimer(); server.stop(); }
protected void postRun(Reactor reactor) throws InterruptedException { awaitConsumer(); long end = System.currentTimeMillis(); double elapsed = end - start; long throughput = Math.round((selectors * iterations) / (elapsed / 1000)); log.info( reactor.getDispatcher().getClass().getSimpleName() + " throughput (" + ((long) elapsed) + "ms): " + throughput + "/sec"); }
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); }
@Override @HasPermission(action = UGC_DELETE, type = SocialPermission.class) public boolean deleteUgc(final String ugcId, final String contextId) throws SocialException { log.debug("logging.ugc.deleteUgc", ugcId); try { ugcRepository.deleteUgc(ugcId, contextId); final SocialEvent<T> event = new SocialEvent<T>( ugcId, SocialSecurityUtils.getCurrentProfile().getId().toString(), UGCEvent.DELETE); event.setAttribute("baseUrl", calculateBaseUrl()); reactor.notify(UGCEvent.DELETE.getName(), Event.wrap(event)); } catch (MongoDataException ex) { log.error("logging.ugc.deleteUgcError", ex, ugcId, contextId); throw new UGCException("Unable to delete UGC", ex); } return false; }
@Override @HasPermission(action = UGC_CREATE, type = SocialPermission.class) public UGC create( final String contextId, final String ugcParentId, final String targetId, final String textContent, final String subject, final Map attrs, final boolean isAnonymous) throws SocialException { log.debug("logging.ugc.creatingUgc", contextId, targetId, ugcParentId, subject, attrs); final UGC template = new UGC(subject, textContent, targetId); template.setAnonymousFlag(isAnonymous); T newUgc = (T) ugcFactory.newInstance(template); newUgc.setAttributes(attrs); try { if (ObjectId.isValid(ugcParentId)) { setupAncestors(newUgc, ugcParentId, contextId); } else { log.debug("logging.ugc.invalidParentId"); } if (StringUtils.isBlank(contextId)) { throw new IllegalArgumentException("context cannot be null"); } pipeline.processUgc(newUgc); ugcRepository.save(newUgc); final SocialEvent<T> event = new SocialEvent<>( newUgc, SocialSecurityUtils.getCurrentProfile().getId().toString(), UGCEvent.CREATE); event.setAttribute("baseUrl", calculateBaseUrl()); reactor.notify(UGCEvent.CREATE.getName(), Event.wrap(event)); setupAutoWatch(targetId, SocialSecurityUtils.getCurrentProfile(), contextId); log.info("logging.ugc.created", newUgc); return newUgc; } catch (MongoDataException ex) { log.error("logging.ugc.errorSaving", ex); throw new UGCException("Unable to Save UGC"); } }
@Override @HasPermission(action = UGC_UPDATE, type = SocialPermission.class) public FileInfo addAttachment( @SecuredObject final String ugcId, final String contextId, final InputStream attachment, final String fileName, final String contentType) throws FileExistsException, UGCException { String internalFileName = File.separator + contextId + File.separator + ugcId + File.separator + fileName; try { checkForVirus(attachment); } catch (IOException | VirusScannerException ex) { log.error("logging.ugc.errorScanVirus", ex); return null; } try { UGC ugc = ugcRepository.findUGC(contextId, ugcId); if (ugc == null) { throw new IllegalUgcException("UGC with given Id does not exist"); } FileInfo info = ugcRepository.saveFile(attachment, internalFileName, contentType); try { info.setFileName(new URLCodec().decode(fileName)); } catch (DecoderException e) { info.setFileName(fileName); } info.setAttribute("owner", ugcId); ugc.getAttachments().add(info); ugcRepository.update(ugcId, ugc); reactor.notify( UGCEvent.ADD_ATTACHMENT.getName(), Event.wrap( new SocialEvent<>( ugcId, new InputStream[] {new CloseShieldInputStream(attachment)}))); return info; } catch (MongoDataException e) { log.error("logging.ugc.unableToSaveAttachment", e, internalFileName); throw new UGCException("Unable to save File to UGC"); } }
@Override @HasPermission(action = UGC_UPDATE, type = SocialPermission.class) public void deleteAttribute( @SecuredObject final String ugcId, final String[] attributesName, final String contextId) throws SocialException { log.debug("logging.ugc.deleteAttributes", attributesName, ugcId); try { ugcRepository.deleteAttribute(ugcId, contextId, attributesName); final SocialEvent<T> event = new SocialEvent<T>( ugcId, SocialSecurityUtils.getCurrentProfile().getId().toString(), UGCEvent.DELETE_ATTRIBUTES); event.setAttribute("baseUrl", calculateBaseUrl()); reactor.notify(UGCEvent.DELETE_ATTRIBUTES.getName(), Event.wrap(event)); } catch (MongoDataException ex) { log.debug("logging.ugc.unableToDelAttrs", ex, attributesName, ugcId); throw new UGCException("Unable to delete attribute for ugc", ex); } }
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()); } }); } }
private void multipleRingBufferDispatchers() { Boundary b = new Boundary(); Reactor r1 = Reactors.reactor().env(env).dispatcher(Environment.RING_BUFFER).get(); Reactor r2 = Reactors.reactor().env(env).dispatcher(Environment.RING_BUFFER).get(); // Bind to a Selector using an anonymous object Tuple2<Selector, Object> anon = $(); r1.on(anon.getT1(), b.bind(consumer, 3)); r2.on(anon.getT1(), b.bind(consumer, 2)); r1.notify(anon.getT2(), Event.wrap(r1)); r1.notify(anon.getT2(), Event.wrap(r1)); r1.notify(anon.getT2(), Event.wrap(r1)); r2.notify(anon.getT2(), Event.wrap(r2)); r2.notify(anon.getT2(), Event.wrap(r2)); b.await(); }
public Promise<Boolean> close() { Deferred<Boolean, Promise<Boolean>> d = Promises.defer(env, reactor.getDispatcher()); close(d); return d.compose(); }
/** * 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 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)); }
/** * Abstract base class for {@code Codec Codecs} that perform serialization of objects. Optionally * handles writing class names so that an object that is serialized can be properly instantiated * with full type information on the other end. * * @author Jon Brisbin * @author Stephane Maldini */ public abstract class SerializationCodec<E, IN, OUT> extends BufferCodec<IN, OUT> { private final Logger log = Reactor.getLogger(getClass()); private final Map<String, Class<IN>> types = new ConcurrentHashMap<String, Class<IN>>(); private final E engine; private final boolean lengthFieldFraming; private final Codec<Buffer, IN, OUT> encoder; /** * Create a {@code SerializationCodec} using the given engine and specifying whether or not to * prepend a length field to frame the message. * * @param engine the engine which will perform the serialization * @param lengthFieldFraming {@code true} to prepend a length field, or {@code false} to skip */ protected SerializationCodec(E engine, boolean lengthFieldFraming) { this.engine = engine; this.lengthFieldFraming = lengthFieldFraming; if (lengthFieldFraming) { this.encoder = new LengthFieldCodec<IN, OUT>(new DelegateCodec()); } else { this.encoder = new DelegateCodec(); } } @Override protected int canDecodeNext(Buffer buffer, Object context) { return buffer.remaining() > 0 ? buffer.limit() : -1; } @Override public Function<Buffer, IN> decoder(Consumer<IN> next) { if (lengthFieldFraming) { return new LengthFieldCodec<IN, OUT>(new DelegateCodec()).decoder(next); } else { return new DelegateCodec().decoder(next); } } @Override protected IN decodeNext(Buffer buffer, Object context) { try { Class<IN> clazz = readType(buffer); byte[] bytes = buffer.asBytes(); buffer.position(buffer.limit()); return deserializer(engine, clazz).apply(bytes); } catch (RuntimeException e) { if (log.isErrorEnabled()) { log.error("Could not decode " + buffer, e); } throw e; } } @Override public Buffer apply(OUT out) { return encoder.apply(out); } protected E getEngine() { return engine; } protected abstract Function<byte[], IN> deserializer(E engine, Class<IN> type); protected abstract Function<OUT, byte[]> serializer(E engine); private String readTypeName(Buffer buffer) { int len = buffer.readInt(); if (buffer.remaining() <= len) { throw new IllegalArgumentException( "Incomplete buffer. Must contain " + len + " bytes, " + "but only " + buffer.remaining() + " were found."); } byte[] bytes = new byte[len]; buffer.read(bytes); return new String(bytes); } private Buffer writeTypeName(Class<?> type, byte[] bytes) { String typeName = type.getName(); int len = typeName.length(); Buffer buffer = new Buffer(4 + len + bytes.length, true); return buffer.append(len).append(typeName).append(bytes).flip(); } public Class<IN> readType(Buffer buffer) { String typeName = readTypeName(buffer); return getType(typeName); } @SuppressWarnings("unchecked") private Class<IN> getType(String name) { Class<IN> type = types.get(name); if (null == type) { try { type = (Class<IN>) Class.forName(name); } catch (ClassNotFoundException e) { throw new IllegalArgumentException(e.getMessage(), e); } types.put(name, type); } return type; } private class DelegateCodec extends Codec<Buffer, IN, OUT> { final Function<OUT, byte[]> fn = serializer(engine); @Override protected IN decodeNext(Buffer buffer, Object context) { return SerializationCodec.this.decodeNext(buffer, context); } @Override public Buffer apply(OUT o) { try { return writeTypeName(o.getClass(), fn.apply(o)); } catch (RuntimeException e) { if (log.isErrorEnabled()) { log.error("Could not encode " + o, e); } throw e; } } } }
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(); }
/** @author Anatoly Kadyshev */ public final class EmbeddedMediaDriverManager { private static final Logger logger = Reactor.getLogger(EmbeddedMediaDriverManager.class); private static final EmbeddedMediaDriverManager INSTANCE = new EmbeddedMediaDriverManager(); private final List<String> aeronDirNames = new ArrayList<>(); private Thread shutdownHook; enum State { NOT_STARTED, STARTED, SHUTTING_DOWN } public static final long DEFAULT_RETRY_SHUTDOWN_MILLIS = 250; public static final long DEFAULT_SHUTDOWN_TIMEOUT_NS = TimeUnit.SECONDS.toNanos(10); private long retryShutdownMillis = DEFAULT_RETRY_SHUTDOWN_MILLIS; /** Timeout in nanos to wait until forcefully shutting down the embedded Media driver */ private long shutdownTimeoutNs = DEFAULT_SHUTDOWN_TIMEOUT_NS; private MediaDriver driver; private int counter = 0; private Aeron aeron; private AeronCounters aeronCounters; private MediaDriver.Context driverContext; private State state = State.NOT_STARTED; /** * If true then the Media driver is shutdown when the last functionality which started it is * shutdown */ private boolean shouldShutdownWhenNotUsed = true; /** If true then deletes previously created Aeron dirs upon JVM termination */ private boolean deleteAeronDirsOnExit = false; private class RetryShutdownTask implements Runnable { private final long startNs; private final TimedScheduler timer; public RetryShutdownTask(TimedScheduler timer) { this.startNs = System.nanoTime(); this.timer = timer; } @Override public void run() { if (canShutdownMediaDriver() || System.nanoTime() - startNs > shutdownTimeoutNs) { doShutdown(); } else { timer.schedule(this, retryShutdownMillis, TimeUnit.MILLISECONDS); } } private boolean canShutdownMediaDriver() { final boolean canShutdownDriver[] = new boolean[] {true}; aeronCounters.forEach( (id, label) -> { if (label.startsWith(AeronUtils.LABEL_PREFIX_SENDER_POS) || label.startsWith(AeronUtils.LABEL_PREFIX_SUBSCRIBER_POS)) { canShutdownDriver[0] = false; } }); return canShutdownDriver[0]; } } public static EmbeddedMediaDriverManager getInstance() { return INSTANCE; } public synchronized MediaDriver.Context getDriverContext() { if (driverContext == null) { driverContext = new MediaDriver.Context(); } return driverContext; } public synchronized void launchDriver() { if (state == State.SHUTTING_DOWN) { throw new IllegalStateException("Manager is being shutdown"); } if (driver == null) { driver = MediaDriver.launchEmbedded(getDriverContext()); Aeron.Context ctx = new Aeron.Context(); String aeronDirName = driver.aeronDirectoryName(); ctx.aeronDirectoryName(aeronDirName); aeron = Aeron.connect(ctx); aeronCounters = new AeronCounters(aeronDirName); state = State.STARTED; aeronDirNames.add(aeronDirName); logger.info("Embedded media driver started"); } counter++; } public synchronized void shutdownDriver() { counter = (int) Operators.subOrZero(counter, 1); if (counter == 0 && shouldShutdownWhenNotUsed) { shutdown(); } } public synchronized Aeron getAeron() { return aeron; } public synchronized int getCounter() { return counter; } public synchronized void shutdown() { if (state != State.STARTED) { throw new IllegalStateException("Cannot shutdown manager in state: " + state); } state = State.SHUTTING_DOWN; counter = 0; if (driver != null) { aeron.close(); TimedScheduler timer = Schedulers.timer(); timer.schedule(new RetryShutdownTask(timer), retryShutdownMillis, TimeUnit.MILLISECONDS); } } /** Could result into JVM crashes when there is pending Aeron activity */ private synchronized void doShutdown() { aeron = null; try { aeronCounters.shutdown(); } catch (Throwable t) { logger.error("Failed to shutdown Aeron counters", t); } aeronCounters = null; CloseHelper.quietClose(driver); driverContext = null; driver = null; state = State.NOT_STARTED; setupShutdownHook(); logger.info("Embedded media driver shutdown"); } private void setupShutdownHook() { if (!deleteAeronDirsOnExit || shutdownHook != null) { return; } shutdownHook = new Thread() { @Override public void run() { synchronized (EmbeddedMediaDriverManager.this) { for (String aeronDirName : aeronDirNames) { try { File dirFile = new File(aeronDirName); IoUtil.delete(dirFile, false); } catch (Exception e) { logger.error("Failed to delete Aeron directory: {}", aeronDirName); } } } } }; Runtime.getRuntime().addShutdownHook(shutdownHook); } public synchronized boolean isTerminated() { return state == State.NOT_STARTED; } public void setShouldShutdownWhenNotUsed(boolean shouldShutdownWhenNotUsed) { this.shouldShutdownWhenNotUsed = shouldShutdownWhenNotUsed; } public void setDeleteAeronDirsOnExit(boolean deleteAeronDirsOnExit) { this.deleteAeronDirsOnExit = deleteAeronDirsOnExit; } public void setShutdownTimeoutNs(long shutdownTimeoutNs) { this.shutdownTimeoutNs = shutdownTimeoutNs; } }
/** * Netty {@link io.netty.channel.ChannelInboundHandler} implementation that passes data to a Reactor * {@link Channel}. * * @author Stephane Maldini */ public class NettyChannelHandler<C extends NettyChannel> extends ChannelDuplexHandler implements Producer, Publisher<Object> { protected static final Logger log = Reactor.getLogger(NettyChannelHandler.class); protected final ChannelHandler<ByteBuf, ByteBuf, NettyChannel> handler; protected final ChannelBridge<C> bridgeFactory; protected final Flux<Object> input; final InboundSink inboundEmitter; public NettyChannelHandler( ChannelHandler<ByteBuf, ByteBuf, NettyChannel> handler, ChannelBridge<C> bridgeFactory, io.netty.channel.Channel ch) { this(handler, bridgeFactory, ch, null); } @SuppressWarnings("unchecked") public NettyChannelHandler( ChannelHandler<ByteBuf, ByteBuf, NettyChannel> handler, ChannelBridge<C> bridgeFactory, io.netty.channel.Channel ch, NettyChannelHandler parent) { this.handler = handler; if (parent == null) { this.inboundEmitter = new InboundSink(ch); // guard requests/cancel/subscribe this.input = Flux.from(this).subscribeOn(Schedulers.fromExecutor(ch.eventLoop())); } else { this.inboundEmitter = parent.inboundEmitter; this.input = parent.input; } this.bridgeFactory = bridgeFactory; } @Override public FluxSink<Object> downstream() { return inboundEmitter; } @Override public void channelActive(final ChannelHandlerContext ctx) throws Exception { super.channelActive(ctx); handler .apply(bridgeFactory.createChannelBridge(ctx.channel(), input)) .subscribe(new CloseSubscriber(ctx)); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { try { inboundEmitter.complete(); super.channelInactive(ctx); } catch (Throwable err) { Exceptions.throwIfFatal(err); inboundEmitter.fail(err); } } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { doRead(msg); } @SuppressWarnings("unchecked") protected final void doRead(Object msg) { if (msg == null) { return; } try { if (msg == Unpooled.EMPTY_BUFFER || msg instanceof EmptyByteBuf) { return; } inboundEmitter.next(msg); } catch (Throwable err) { Exceptions.throwIfFatal(err); inboundEmitter.fail(err); } } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { if (inboundEmitter.requested != 0L) { ctx.read(); } else { if (log.isDebugEnabled()) { log.debug("Pausing read due to lack of request"); } } ctx.fireChannelReadComplete(); } @Override public void write(final ChannelHandlerContext ctx, Object msg, final ChannelPromise promise) throws Exception { if (msg instanceof ChannelWriter) { @SuppressWarnings("unchecked") ChannelWriter dataWriter = (ChannelWriter) msg; if (dataWriter.flushMode == FlushMode.MANUAL_COMPLETE) { dataWriter.writeStream.subscribe(new FlushOnTerminateSubscriber(ctx, promise)); } else { dataWriter.writeStream.subscribe(new FlushOnEachSubscriber(ctx, promise)); } } else { super.write(ctx, msg, promise); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable err) throws Exception { Exceptions.throwIfFatal(err); inboundEmitter.fail(err); } protected ChannelFuture doOnWrite(Object data, ChannelHandlerContext ctx) { if (Unpooled.EMPTY_BUFFER != data) { return ctx.channel().write(data); } return null; } protected void doOnTerminate( ChannelHandlerContext ctx, ChannelFuture last, final ChannelPromise promise, final Throwable exception) { if (ctx.channel().isOpen()) { ChannelFutureListener listener = future -> { if (exception != null) { promise.tryFailure(exception); } else if (future.isSuccess()) { promise.trySuccess(); } else { promise.tryFailure(future.cause()); } }; if (last != null) { ctx.flush(); last.addListener(listener); } else { ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(listener); } } else { if (exception != null) { promise.tryFailure(exception); } else { promise.trySuccess(); } } } /** */ public enum FlushMode { AUTO_EACH, AUTO_LOOP, MANUAL_COMPLETE, MANUAL_BOUNDARY } /** */ public static final class ChannelWriter { final Publisher<?> writeStream; final FlushMode flushMode; public ChannelWriter(Publisher<?> writeStream, FlushMode flushMode) { this.writeStream = writeStream; this.flushMode = flushMode; } } static final class CloseSubscriber implements Subscriber<Void> { private final ChannelHandlerContext ctx; public CloseSubscriber(ChannelHandlerContext ctx) { this.ctx = ctx; } @Override public void onSubscribe(Subscription s) { s.request(Long.MAX_VALUE); } @Override public void onNext(Void aVoid) {} @Override public void onError(Throwable t) { if (t instanceof IOException && t.getMessage().contains("Broken pipe")) { if (log.isDebugEnabled()) { log.debug("Connection closed remotely", t); } return; } log.error("Error processing connection. Closing the channel.", t); ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE); } @Override public void onComplete() { ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE); } } final class FlushOnTerminateSubscriber implements Subscriber<Object>, ChannelFutureListener, Loopback { private final ChannelHandlerContext ctx; private final ChannelPromise promise; ChannelFuture lastWrite; Subscription subscription; public FlushOnTerminateSubscriber(ChannelHandlerContext ctx, ChannelPromise promise) { this.ctx = ctx; this.promise = promise; } @Override public void operationComplete(ChannelFuture future) throws Exception { if (log.isDebugEnabled()) { log.debug("Cancel connection"); } if (subscription != null) { subscription.cancel(); } subscription = null; } @Override public Object connectedInput() { return NettyChannelHandler.this; } @Override public void onSubscribe(final Subscription s) { if (Operators.validate(subscription, s)) { this.subscription = s; ctx.channel().closeFuture().addListener(this); s.request(Long.MAX_VALUE); } } @Override public void onNext(final Object w) { if (w == null) { throw Exceptions.argumentIsNullException(); } if (subscription == null) { throw Exceptions.failWithCancel(); } try { ChannelFuture cf = doOnWrite(w, ctx); lastWrite = cf; if (cf != null && log.isDebugEnabled()) { cf.addListener( new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { if (!future.isSuccess()) { log.error("write error :" + w, future.cause()); if (ByteBuf.class.isAssignableFrom(w.getClass())) { ((ByteBuf) w).resetReaderIndex(); } } } }); } } catch (Throwable t) { log.error("Write error for " + w, t); onError(t); } } @Override public void onError(Throwable t) { if (t == null) { throw Exceptions.argumentIsNullException(); } if (subscription == null) { throw new IllegalStateException("already flushed", t); } log.error("Write error", t); subscription = null; ctx.channel().closeFuture().removeListener(this); doOnTerminate(ctx, lastWrite, promise, t); } @Override public void onComplete() { if (subscription == null) { throw new IllegalStateException("already flushed"); } subscription = null; ctx.channel().closeFuture().removeListener(this); doOnTerminate(ctx, lastWrite, promise, null); } } final class FlushOnEachSubscriber implements Subscriber<Object>, ChannelFutureListener, Loopback, Trackable, Receiver { private final ChannelHandlerContext ctx; private final ChannelPromise promise; private Subscription subscription; private final ChannelFutureListener writeListener = new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { if (!future.isSuccess()) { promise.tryFailure(future.cause()); if (log.isDebugEnabled()) { log.debug("Write error", future.cause()); } return; } if (subscription != null) { subscription.request(1L); } } }; public FlushOnEachSubscriber(ChannelHandlerContext ctx, ChannelPromise promise) { this.ctx = ctx; this.promise = promise; } @Override public boolean isCancelled() { return !ctx.channel().isOpen(); } @Override public boolean isStarted() { return subscription != null; } @Override public boolean isTerminated() { return !ctx.channel().isOpen(); } @Override public Object connectedInput() { return NettyChannelHandler.this; } @Override public void onSubscribe(final Subscription s) { if (Operators.validate(subscription, s)) { subscription = s; ctx.channel().closeFuture().addListener(this); s.request(1L); } } @Override public void onNext(Object w) { if (w == null) { throw Exceptions.argumentIsNullException(); } if (subscription == null) { throw Exceptions.failWithCancel(); } try { ChannelFuture cf = doOnWrite(w, ctx); if (cf != null) { cf.addListener(writeListener); } ctx.flush(); } catch (Throwable t) { log.error("Write error for " + w, t); onError(t); throw Exceptions.failWithCancel(); } } @Override public void onError(Throwable t) { if (t == null) { throw Exceptions.argumentIsNullException(); } if (subscription == null) { throw new IllegalStateException("already flushed", t); } log.error("Write error", t); subscription = null; ctx.channel().closeFuture().removeListener(this); doOnTerminate(ctx, null, promise, t); } @Override public void onComplete() { if (subscription == null) { throw new IllegalStateException("already flushed"); } subscription = null; if (log.isDebugEnabled()) { log.debug("Flush Connection"); } ctx.channel().closeFuture().removeListener(this); doOnTerminate(ctx, null, promise, null); } @Override public void operationComplete(ChannelFuture future) throws Exception { if (log.isDebugEnabled()) { log.debug("Cancel connection"); } if (subscription != null) { subscription.cancel(); } subscription = null; } @Override public Object upstream() { return subscription; } } public ChannelHandler<ByteBuf, ByteBuf, NettyChannel> getHandler() { return handler; } @Override public void subscribe(Subscriber<? super Object> s) { if (log.isDebugEnabled()) { log.debug( "Subscribing inbound receiver [pending: " + "" + inboundEmitter.getPending() + ", done: " + inboundEmitter.done + "]"); } if (inboundEmitter.actual == null) { if (inboundEmitter.done) { if (inboundEmitter.error != null) { Operators.error(s, inboundEmitter.error); return; } else if (inboundEmitter.getPending() == 0) { Operators.complete(s); return; } } inboundEmitter.init(s); s.onSubscribe(inboundEmitter); } else { Operators.error( s, new IllegalStateException("Only one connection receive subscriber allowed.")); } } static final class InboundSink implements FluxSink<Object>, Trackable, Cancellation, Subscription, Producer { final io.netty.channel.Channel ch; /** guarded by event loop */ Subscriber<? super Object> actual; boolean caughtUp; Queue<Object> queue; boolean done; Throwable error; long requested; int wip; volatile Cancellation cancel; @SuppressWarnings("rawtypes") static final AtomicReferenceFieldUpdater<InboundSink, Cancellation> CANCEL = AtomicReferenceFieldUpdater.newUpdater(InboundSink.class, Cancellation.class, "cancel"); static final Cancellation CANCELLED = () -> {}; public InboundSink(io.netty.channel.Channel channel) { this.ch = channel; CANCEL.lazySet(this, this); } void init(Subscriber<? super Object> s) { actual = s; CANCEL.lazySet(this, this); wip = 0; } @Override public void next(Object value) { if (value == null) { fail(new NullPointerException("value is null")); return; } if (done) { Exceptions.onNextDropped(value); return; } if (caughtUp && actual != null) { try { actual.onNext(value); } finally { ch.read(); ReferenceCountUtil.release(value); } } else { Queue<Object> q = queue; if (q == null) { q = QueueSupplier.unbounded().get(); queue = q; } q.offer(value); if (drain()) { caughtUp = true; } } } @Override public void fail(Throwable error) { if (error == null) { error = new NullPointerException("error is null"); } if (isCancelled() || done) { Exceptions.onErrorDropped(error); return; } done = true; if (caughtUp && actual != null) { actual.onError(error); } else { this.error = error; done = true; drain(); } } @Override public boolean isCancelled() { return cancel == CANCELLED; } @Override public void complete() { if (isCancelled() || done) { return; } done = true; if (caughtUp && actual != null) { actual.onComplete(); } drain(); } boolean drain() { if (wip++ != 0) { return false; } int missed = 1; for (; ; ) { final Queue<Object> q = queue; final Subscriber<? super Object> a = actual; if (a == null) { return false; } long r = requested; long e = 0L; while (e != r) { if (isCancelled()) { return false; } boolean d = done; Object v = q != null ? q.poll() : null; boolean empty = v == null; if (d && empty) { cancelResource(); if (q != null) { q.clear(); } Throwable ex = error; if (ex != null) { a.onError(ex); } else { a.onComplete(); } return false; } if (empty) { ch.read(); break; } try { a.onNext(v); } finally { ReferenceCountUtil.release(v); ch.read(); } e++; } if (e == r) { if (isCancelled()) { return false; } if (done && (q == null || q.isEmpty())) { cancelResource(); if (q != null) { q.clear(); } Throwable ex = error; if (ex != null) { a.onError(ex); } else { a.onComplete(); } return false; } } if (e != 0L) { if (r != Long.MAX_VALUE) { if ((requested -= e) > 0L) { ch.read(); } } } missed = (wip = wip - missed); if (missed == 0) { if (r == Long.MAX_VALUE) { ch.config().setAutoRead(true); ch.read(); return true; } return false; } } } @Override public void setCancellation(Cancellation c) { if (!CANCEL.compareAndSet(this, null, c)) { if (cancel != CANCELLED && c != null) { c.dispose(); } } } @Override public void request(long n) { if (Operators.validate(n)) { this.requested = Operators.addCap(requested, n); drain(); } } void cancelResource() { Cancellation c = cancel; if (c != CANCELLED) { c = CANCEL.getAndSet(this, CANCELLED); if (c != null && c != CANCELLED) { requested = 0L; c.dispose(); } } } @Override public void cancel() { cancelResource(); if (wip++ == 0) { Queue<Object> q = queue; if (q != null) { q.clear(); } } } @Override public long requestedFromDownstream() { return requested; } @Override public long getCapacity() { return Long.MAX_VALUE; } @Override public long getPending() { return queue != null ? queue.size() : 0; } @Override public Throwable getError() { return error; } @Override public Object downstream() { return actual; } void dereference() { actual = null; } @Override public void dispose() { if (ch.eventLoop().inEventLoop()) { dereference(); } else { ch.eventLoop().execute(this::dereference); } ch.config().setAutoRead(false); } } }
/** * 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)); }