
org.killbill.commons.eventbus.Dispatcher Maven / Gradle / Ivy
/*
* Copyright (C) 2007 The Guava Authors
* Copyright 2020-2022 Equinix, Inc
* Copyright 2014-2022 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.killbill.commons.eventbus;
import java.util.ArrayDeque;
import java.util.Iterator;
import java.util.Queue;
import org.killbill.commons.utils.Preconditions;
/**
* Note: Not like Guava, {@code LegacyAsyncDispatcher} get removed as it considered as legacy code.
*
* Handler for dispatching events to subscribers, providing different event ordering guarantees that
* make sense for different situations.
*
* Note: The dispatcher is orthogonal to the subscriber's {@code Executor}. The dispatcher
* controls the order in which events are dispatched, while the executor controls how (i.e. on which
* thread) the subscriber is actually called when an event is dispatched to it.
*
* @author Colin Decker
*/
abstract class Dispatcher {
/**
* Returns a dispatcher that queues events that are posted reentrantly on a thread that is already
* dispatching an event, guaranteeing that all events posted on a single thread are dispatched to
* all subscribers in the order they are posted.
*
*
When all subscribers are dispatched to using a direct executor (which dispatches on
* the same thread that posts the event), this yields a breadth-first dispatch order on each
* thread. That is, all subscribers to a single event A will be called before any subscribers to
* any events B and C that are posted to the event bus by the subscribers to A.
*/
static Dispatcher perThreadDispatchQueue() {
return new PerThreadQueuedDispatcher();
}
/**
* Returns a dispatcher that dispatches events to subscribers immediately as they're posted
* without using an intermediate queue to change the dispatch order. This is effectively a
* depth-first dispatch order, vs. breadth-first when using a queue.
*/
static Dispatcher immediate() {
return ImmediateDispatcher.INSTANCE;
}
/**
* Dispatches the given {@code event} to the given {@code subscribers}.
*/
abstract void dispatch(Object event, Iterator subscribers);
/**
* Implementation of a {@link #perThreadDispatchQueue()} dispatcher.
*/
static final class PerThreadQueuedDispatcher extends Dispatcher {
// This dispatcher matches the original dispatch behavior of EventBus.
/**
* Per-thread queue of events to dispatch.
*/
private final ThreadLocal> queue = ThreadLocal.withInitial(ArrayDeque::new);
/**
* Per-thread dispatch state, used to avoid reentrant event dispatching.
*/
private final ThreadLocal dispatching = ThreadLocal.withInitial(() -> false);
@Override
void dispatch(final Object event, final Iterator subscribers) {
Preconditions.checkNotNull(event);
Preconditions.checkNotNull(subscribers);
final Queue queueForThread = queue.get();
queueForThread.offer(new Event(event, subscribers));
if (!dispatching.get()) {
dispatching.set(true);
try {
Event nextEvent;
while ((nextEvent = queueForThread.poll()) != null) {
while (nextEvent.subscribers.hasNext()) {
nextEvent.subscribers.next().dispatchEvent(nextEvent.event);
}
}
} finally {
dispatching.remove();
queue.remove();
}
}
}
private static final class Event {
private final Object event;
private final Iterator subscribers;
private Event(final Object event, final Iterator subscribers) {
this.event = event;
this.subscribers = subscribers;
}
}
}
/**
* Implementation of {@link #immediate()}.
*/
static final class ImmediateDispatcher extends Dispatcher {
private static final ImmediateDispatcher INSTANCE = new ImmediateDispatcher();
@Override
void dispatch(final Object event, final Iterator subscribers) {
Preconditions.checkNotNull(event);
while (subscribers.hasNext()) {
subscribers.next().dispatchEvent(event);
}
}
}
}