All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.opencensus.impl.internal.DisruptorEventQueue Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2017, OpenCensus 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 io.opencensus.impl.internal;

import com.lmax.disruptor.EventFactory;
import com.lmax.disruptor.EventHandler;
import com.lmax.disruptor.RingBuffer;
import com.lmax.disruptor.SleepingWaitStrategy;
import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.dsl.ProducerType;
import io.opencensus.implcore.internal.DaemonThreadFactory;
import io.opencensus.implcore.internal.EventQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;

/**
 * A low-latency event queue for background updating of (possibly contended) objects. This is
 * intended for use by instrumentation methods to ensure that they do not block foreground
 * activities. To customize the action taken on reading the queue, derive a new class from {@link
 * EventQueue.Entry} and pass it to the {@link #enqueue(Entry)} method. The {@link Entry#process()}
 * method of your class will be called and executed in a background thread. This class is a
 * Singleton.
 *
 * 

Example Usage: Given a class as follows: * *

 * public class someClass {
 *   public void doSomething() {
 *     // Do the work of the method. One result is a measurement of something.
 *     int measurement = doSomeWork();
 *     // Make an update to the class state, based on this measurement. This work can take some
 *     // time, but can be done asynchronously, in the background.
 *     update(measurement);
 *   }
 *
 *   public void update(int arg) {
 *     // do something
 *   }
 * }
 * 
* *

The work of calling {@code someClass.update()} can be executed in the backgound as follows: * *

 * public class someClass {
 *   // Add a EventQueueEntry class that will process the update call.
 *   private static final class SomeClassUpdateEvent implements EventQueueEntry {
 *     private final SomeClass someClassInstance;
 *     private final int arg;
 *
 *     SomeObjectUpdateEvent(SomeObject someClassInstance, int arg) {
 *       this.someClassInstance = someClassInstance;
 *       this.arg = arg;
 *     }
 *
 *     @Override
 *     public void process() {
 *       someClassInstance.update(arg);
 *     }
 *   }
 *
 *   public void doSomething() {
 *     int measurement = doSomeWork();
 *     // Instead of calling update() directly, create an event to do the processing, and insert
 *     // it into the EventQueue. It will be processed in a background thread, and doSomething()
 *     // can return immediately.
 *     EventQueue.getInstance.enqueue(new SomeClassUpdateEvent(this, measurement));
 *   }
 * }
 * 
*/ @ThreadSafe public final class DisruptorEventQueue implements EventQueue { private static final Logger logger = Logger.getLogger(DisruptorEventQueue.class.getName()); // Number of events that can be enqueued at any one time. If more than this are enqueued, // then subsequent attempts to enqueue new entries will block. // TODO(aveitch): consider making this a parameter to the constructor, so the queue can be // configured to a size appropriate to the system (smaller/less busy systems will not need as // large a queue. private static final int DISRUPTOR_BUFFER_SIZE = 8192; // The single instance of the class. private static final DisruptorEventQueue eventQueue = create(); // The event queue is built on this {@link Disruptor}. private final Disruptor disruptor; private volatile DisruptorEnqueuer enqueuer; // Creates a new EventQueue. Private to prevent creation of non-singleton instance. private DisruptorEventQueue(Disruptor disruptor, DisruptorEnqueuer enqueuer) { this.disruptor = disruptor; this.enqueuer = enqueuer; } // Creates a new EventQueue. Private to prevent creation of non-singleton instance. private static DisruptorEventQueue create() { // Create new Disruptor for processing. Note that Disruptor creates a single thread per // consumer (see https://github.com/LMAX-Exchange/disruptor/issues/121 for details); // this ensures that the event handler can take unsynchronized actions whenever possible. Disruptor disruptor = new Disruptor<>( DisruptorEventFactory.INSTANCE, DISRUPTOR_BUFFER_SIZE, new DaemonThreadFactory("OpenCensus.Disruptor"), ProducerType.MULTI, new SleepingWaitStrategy(0, 1000 * 1000)); disruptor.handleEventsWith(new DisruptorEventHandler[] {DisruptorEventHandler.INSTANCE}); disruptor.start(); final RingBuffer ringBuffer = disruptor.getRingBuffer(); DisruptorEnqueuer enqueuer = new DisruptorEnqueuer() { @Override public void enqueue(Entry entry) { long sequence = ringBuffer.next(); try { DisruptorEvent event = ringBuffer.get(sequence); event.setEntry(entry); } finally { ringBuffer.publish(sequence); } } }; return new DisruptorEventQueue(disruptor, enqueuer); } /** * Returns the {@link DisruptorEventQueue} instance. * * @return the singleton {@code EventQueue} instance. */ public static DisruptorEventQueue getInstance() { return eventQueue; } /** * Enqueues an event on the {@link DisruptorEventQueue}. * * @param entry a class encapsulating the actions to be taken for event processing. */ @Override public void enqueue(Entry entry) { enqueuer.enqueue(entry); } /** Shuts down the underlying disruptor. */ @Override public void shutdown() { enqueuer = new DisruptorEnqueuer() { final AtomicBoolean logged = new AtomicBoolean(false); @Override public void enqueue(Entry entry) { if (!logged.getAndSet(true)) { logger.log(Level.INFO, "Attempted to enqueue entry after Disruptor shutdown."); } } }; disruptor.shutdown(); } // Allows this event queue to safely shutdown by not enqueuing events on the ring buffer private abstract static class DisruptorEnqueuer { public abstract void enqueue(Entry entry); } // An event in the {@link EventQueue}. Just holds a reference to an EventQueue.Entry. private static final class DisruptorEvent { // TODO(bdrutu): Investigate if volatile is needed. This object is shared between threads so // intuitively this variable must be volatile. @Nullable private volatile Entry entry = null; // Sets the EventQueueEntry associated with this DisruptorEvent. void setEntry(@Nullable Entry entry) { this.entry = entry; } // Returns the EventQueueEntry associated with this DisruptorEvent. @Nullable Entry getEntry() { return entry; } } // Factory for DisruptorEvent. private enum DisruptorEventFactory implements EventFactory { INSTANCE; @Override public DisruptorEvent newInstance() { return new DisruptorEvent(); } } /** * Every event that gets added to {@link EventQueue} will get processed here. Just calls the * underlying process() method. */ private enum DisruptorEventHandler implements EventHandler { INSTANCE; @Override public void onEvent(DisruptorEvent event, long sequence, boolean endOfBatch) { Entry entry = event.getEntry(); if (entry != null) { entry.process(); } // Remove the reference to the previous entry to allow the memory to be gc'ed. event.setEntry(null); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy