org.apache.rocketmq.shaded.com.google.common.util.concurrent.ListenerCallQueue Maven / Gradle / Ivy
/*
* Copyright (C) 2014 The Guava Authors
*
* Licensed 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.apache.rocketmq.shaded.com.google.common.util.concurrent;
import static org.apache.rocketmq.shaded.com.google.common.base.Preconditions.checkNotNull;
import org.apache.rocketmq.shaded.com.google.common.annotations.GwtIncompatible;
import org.apache.rocketmq.shaded.com.google.common.base.Preconditions;
import org.apache.rocketmq.shaded.com.google.common.collect.Queues;
import org.apache.rocketmq.shaded.com.google.errorprone.annotations.concurrent.GuardedBy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.Executor;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A list of listeners for implementing a concurrency friendly observable object.
*
* Listeners are registered once via {@link #addListener} and then may be invoked by {@linkplain
* #enqueue enqueueing} and then {@linkplain #dispatch dispatching} events.
*
*
The API of this class is designed to make it easy to achieve the following properties
*
*
* - Multiple events for the same listener are never dispatched concurrently.
*
- Events for the different listeners are dispatched concurrently.
*
- All events for a given listener dispatch on the provided {@link #executor}.
*
- It is easy for the user to ensure that listeners are never invoked while holding locks.
*
*
* The last point is subtle. Often the observable object will be managing its own internal state
* using a lock, however it is dangerous to dispatch listeners while holding a lock because they
* might run on the {@code directExecutor()} or be otherwise re-entrant (call back into your
* object). So it is important to not call {@link #dispatch} while holding any locks. This is why
* {@link #enqueue} and {@link #dispatch} are 2 different methods. It is expected that the decision
* to run a particular event is made during the state change, but the decision to actually invoke
* the listeners can be delayed slightly so that locks can be dropped. Also, because {@link
* #dispatch} is expected to be called concurrently, it is idempotent.
*/
@GwtIncompatible
@ElementTypesAreNonnullByDefault
final class ListenerCallQueue {
// TODO(cpovirk): consider using the logger associated with listener.getClass().
private static final Logger logger = Logger.getLogger(ListenerCallQueue.class.getName());
// TODO(chrisn): promote AppendOnlyCollection for use here.
private final List> listeners =
Collections.synchronizedList(new ArrayList>());
/** Method reference-compatible listener event. */
interface Event {
/** Call a method on the listener. */
void call(L listener);
}
/**
* Adds a listener that will be called using the given executor when events are later {@link
* #enqueue enqueued} and {@link #dispatch dispatched}.
*/
public void addListener(L listener, Executor executor) {
checkNotNull(listener, "listener");
checkNotNull(executor, "executor");
listeners.add(new PerListenerQueue<>(listener, executor));
}
/**
* Enqueues an event to be run on currently known listeners.
*
* The {@code toString} method of the Event itself will be used to describe the event in the
* case of an error.
*
* @param event the callback to execute on {@link #dispatch}
*/
public void enqueue(Event event) {
enqueueHelper(event, event);
}
/**
* Enqueues an event to be run on currently known listeners, with a label.
*
* @param event the callback to execute on {@link #dispatch}
* @param label a description of the event to use in the case of an error
*/
public void enqueue(Event event, String label) {
enqueueHelper(event, label);
}
private void enqueueHelper(Event event, Object label) {
checkNotNull(event, "event");
checkNotNull(label, "label");
synchronized (listeners) {
for (PerListenerQueue queue : listeners) {
queue.add(event, label);
}
}
}
/**
* Dispatches all events enqueued prior to this call, serially and in order, for every listener.
*
* Note: this method is idempotent and safe to call from any thread
*/
public void dispatch() {
// iterate by index to avoid concurrent modification exceptions
for (int i = 0; i < listeners.size(); i++) {
listeners.get(i).dispatch();
}
}
/**
* A special purpose queue/executor that dispatches listener events serially on a configured
* executor. Each event can be added and dispatched as separate phases.
*
*
This class is very similar to {@link SequentialExecutor} with the exception that events can
* be added without necessarily executing immediately.
*/
private static final class PerListenerQueue implements Runnable {
final L listener;
final Executor executor;
@GuardedBy("this")
final Queue> waitQueue = Queues.newArrayDeque();
@GuardedBy("this")
final Queue