/* * Copyright (c) 2011-2013 The original author or authors * ------------------------------------------------------ * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Apache License v2.0 which accompanies this distribution. * * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html * * The Apache License v2.0 is available at * http://www.opensource.org/licenses/apache2.0.php * * You may elect to redistribute this code under either of these licenses. */ package io.vertx.core.eventbus.impl; import io.vertx.core.AsyncResult; import io.vertx.core.Handler; import io.vertx.core.Vertx; import io.vertx.core.eventbus.*; import java.util.ArrayDeque; import java.util.Queue; import java.util.UUID; /** * @author <a href="mailto:julien@julienviet.com">Julien Viet</a> */ public class MessageProducerImpl<T> implements MessageProducer<T> { public static final String CREDIT_ADDRESS_HEADER_NAME = "__vertx.credit"; private final Vertx vertx; private final EventBus bus; private final boolean send; private final String address; private final Queue<T> pending = new ArrayDeque<>(); private final MessageConsumer<Integer> creditConsumer; private DeliveryOptions options; private int maxSize = DEFAULT_WRITE_QUEUE_MAX_SIZE; private int credits = DEFAULT_WRITE_QUEUE_MAX_SIZE; private Handler<Void> drainHandler; public MessageProducerImpl(Vertx vertx, String address, boolean send, DeliveryOptions options) { this.vertx = vertx; this.bus = vertx.eventBus(); this.address = address; this.send = send; this.options = options; if (send) { String creditAddress = UUID.randomUUID().toString() + "-credit"; creditConsumer = bus.consumer(creditAddress, msg -> { doReceiveCredit(msg.body()); }); options.addHeader(CREDIT_ADDRESS_HEADER_NAME, creditAddress); } else { creditConsumer = null; } } @Override public synchronized MessageProducer<T> deliveryOptions(DeliveryOptions options) { if (creditConsumer != null) { options = new DeliveryOptions(options); options.addHeader(CREDIT_ADDRESS_HEADER_NAME, this.options.getHeaders().get(CREDIT_ADDRESS_HEADER_NAME)); } this.options = options; return this; } @Override public MessageProducer<T> send(T message) { doSend(message, null); return this; } @Override public <R> MessageProducer<T> send(T message, Handler<AsyncResult<Message<R>>> replyHandler) { doSend(message, replyHandler); return this; } @Override public MessageProducer<T> exceptionHandler(Handler<Throwable> handler) { return this; } @Override public synchronized MessageProducer<T> setWriteQueueMaxSize(int s) { int delta = s - maxSize; maxSize = s; credits += delta; return this; } @Override public synchronized MessageProducer<T> write(T data) { if (send) { doSend(data, null); } else { bus.publish(address, data, options); } return this; } @Override public synchronized boolean writeQueueFull() { return credits == 0; } @Override public synchronized MessageProducer<T> drainHandler(Handler<Void> handler) { this.drainHandler = handler; return this; } @Override public String address() { return address; } @Override public void end() { close(); } @Override public void close() { if (creditConsumer != null) { creditConsumer.unregister(); } } // Just in case user forget to call close() @Override protected void finalize() throws Throwable { close(); super.finalize(); } private synchronized <R> void doSend(T data, Handler<AsyncResult<Message<R>>> replyHandler) { if (credits > 0) { credits--; if (replyHandler == null) { bus.send(address, data, options); } else { bus.send(address, data, options, replyHandler); } } else { pending.add(data); } } private synchronized void doReceiveCredit(int credit) { credits += credit; while (credits > 0) { T data = pending.poll(); if (data == null) { break; } else { credits--; bus.send(address, data, options); } } final Handler<Void> theDrainHandler = drainHandler; if (theDrainHandler != null && credits >= maxSize / 2) { this.drainHandler = null; vertx.runOnContext(v -> theDrainHandler.handle(null)); } } }