/* * Copyright 2016-present Facebook, 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.facebook.buck.util.concurrent; import com.google.common.util.concurrent.AbstractListeningExecutorService; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import javax.annotation.Nonnull; import javax.annotation.Nullable; /** * A {@link ListeningExecutorService} which gates execution using a {@link ListeningMultiSemaphore} * and allows resources to be assigned to submitted tasks. * * <p>NOTE: If futures for submitted jobs are cancelled while they are running, it's possible that * the semaphore will be released for that cancelled job before it is finished, meaning more jobs * may be scheduled than expected. */ public class WeightedListeningExecutorService extends AbstractListeningExecutorService { private final ListeningMultiSemaphore semaphore; private final ResourceAmounts defaultValues; private final ListeningExecutorService delegate; public WeightedListeningExecutorService( ListeningMultiSemaphore semaphore, ResourceAmounts defaultValues, ListeningExecutorService delegate) { this.semaphore = semaphore; this.defaultValues = defaultValues; this.delegate = delegate; } public ListeningMultiSemaphore getSemaphore() { return semaphore; } /** * Creates a new service that has different default resource amounts. Useful when you need to * propagate explicit default amounts when you submit the job through execute(), * Futures.transform() and similar calls. * * @param newDefaultAmounts new default amounts * @return Service that uses the same semaphore and delegate but with the given default resource * amounts. */ public WeightedListeningExecutorService withDefaultAmounts(ResourceAmounts newDefaultAmounts) { if (newDefaultAmounts.equals(defaultValues)) { return this; } return new WeightedListeningExecutorService(semaphore, newDefaultAmounts, delegate); } private <T> ListenableFuture<T> submitWithSemaphore( final Callable<T> callable, final ResourceAmounts amounts) { ListenableFuture<T> future = Futures.transformAsync( semaphore.acquire(amounts), input -> { try { return Futures.immediateFuture(callable.call()); } catch (Throwable thrown) { return Futures.immediateFailedFuture(thrown); } }, delegate); future.addListener( () -> semaphore.release(amounts), com.google.common.util.concurrent.MoreExecutors.directExecutor()); return future; } public ListenableFuture<?> submit(final Runnable task, ResourceAmounts amounts) { return submit(task, null, amounts); } @Nonnull @Override public ListenableFuture<?> submit(Runnable task) { return submit(task, defaultValues); } public <T> ListenableFuture<T> submit( final Runnable task, @Nullable final T result, ResourceAmounts amounts) { return submitWithSemaphore( () -> { task.run(); return result; }, amounts); } @Nonnull @Override public <T> ListenableFuture<T> submit(Runnable task, @Nullable T result) { return submit(task, result, defaultValues); } public <T> ListenableFuture<T> submit(Callable<T> task, ResourceAmounts amounts) { return submitWithSemaphore(task, amounts); } @Nonnull @Override public <T> ListenableFuture<T> submit(Callable<T> task) { return submit(task, defaultValues); } @Override public final boolean awaitTermination(long timeout, @Nonnull TimeUnit unit) throws InterruptedException { return delegate.awaitTermination(timeout, unit); } @Override public final boolean isShutdown() { return delegate.isShutdown(); } @Override public final boolean isTerminated() { return delegate.isTerminated(); } @Override public final void shutdown() { delegate.shutdown(); } @Override public final List<Runnable> shutdownNow() { return delegate.shutdownNow(); } @Override public final void execute(@Nonnull Runnable command) { submit(command); } }