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

com.linkedin.parseq.internal.SerialExecutor Maven / Gradle / Ivy

/*
 * Copyright 2012 LinkedIn, Inc
 *
 * 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 com.linkedin.parseq.internal;

import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;


/**
 * An executor that provides the following guarantees:
 * 

* 1. Only one task may be executed at a time * 2. The completion of a task happens-before the execution of the next task *

* For more on the happens-before constraint see the {@code java.util.concurrent} * package documentation. *

* It is possible for the underlying executor to throw an exception signaling * that it is not able to accept new work. For example, this can occur with an * executor that has a bounded queue size and an * {@link java.util.concurrent.ThreadPoolExecutor.AbortPolicy}. If this occurs * the executor will run the {@code rejectionHandler} to signal this failure * to a layer that can more appropriate handle this event. * * @author Chris Pettitt ([email protected]) */ public class SerialExecutor implements Executor { private final Executor _executor; private final RejectedSerialExecutionHandler _rejectionHandler; private final ExecutorLoop _executorLoop = new ExecutorLoop(); private final FIFOPriorityQueue _queue = new FIFOPriorityQueue(); private final AtomicInteger _pendingCount = new AtomicInteger(); public SerialExecutor(final Executor executor, final RejectedSerialExecutionHandler rejectionHandler) { assert executor != null; assert rejectionHandler != null; _executor = executor; _rejectionHandler = rejectionHandler; } public void execute(final Runnable runnable) { _queue.add(runnable); if (_pendingCount.getAndIncrement() == 0) { tryExecuteLoop(); } } private void tryExecuteLoop() { try { _executor.execute(_executorLoop); } catch (Throwable t) { _rejectionHandler.rejectedExecution(t); } } private class ExecutorLoop implements Runnable { @Override public void run() { // This runnable is only scheduled when the queue is non-empty. final Runnable runnable = _queue.poll(); // This seemingly unnecessary call ensures that we have full visibility // of any changes that occurred since the last task executed. It, in // combination with _pendingCount.decrementAndGet() below, effectively // causes this code to have memory consistency similar to entering and // exiting a monitor without the associated mutual exclusion. We need // this because there is no other happens-before guarantee when a new // task is added while this executor is currently processing a task. _pendingCount.get(); try { runnable.run(); } finally { // In addition to its obvious use, this CAS operation acts like an // exit from a monitor for memory consistency purposes. See the note // above for more details. if (_pendingCount.decrementAndGet() > 0) { tryExecuteLoop(); } } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy