/* * Copyright 2017 Google 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.google.firebase.internal; import static com.google.common.base.Preconditions.checkArgument; import com.google.common.base.Strings; import java.util.Collection; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; /** * A ScheduledExecutorService instance that can operate in the Google App Engine environment. The * scheduling operations (i.e. operations specific to the ScheduledExecutorService interface) can * only be used when background thread support is enabled. These operations will throw * UnsupportedOperationException when invoked in an auto-scaled instance without background thread * support. Operations inherited from the ExecutorService and Executor interfaces will work * regardless of the background threads support. This implementation is also lazy loaded to prevent * unnecessary RPC calls to the GAE backend. */ public class GaeScheduledExecutorService implements ScheduledExecutorService { private final AtomicReference<ExecutorWrapper> executor = new AtomicReference<>(); private final String threadName; GaeScheduledExecutorService(String threadName) { checkArgument(!Strings.isNullOrEmpty(threadName)); this.threadName = threadName; } private ExecutorWrapper ensureExecutorWrapper() { ExecutorWrapper wrapper = executor.get(); if (wrapper == null) { synchronized (executor) { wrapper = executor.get(); if (wrapper == null) { wrapper = new ExecutorWrapper(threadName); executor.compareAndSet(null, wrapper); } } } return wrapper; } private ExecutorService ensureExecutorService() { return ensureExecutorWrapper().getExecutorService(); } private ScheduledExecutorService ensureScheduledExecutorService() { ScheduledExecutorService scheduledExecutorService = ensureExecutorWrapper().getScheduledExecutorService(); if (scheduledExecutorService != null) { return scheduledExecutorService; } else { throw new UnsupportedOperationException( "ScheduledExecutorService not available. A manually-scaled instance is " + "required when running the Firebase Admin SDK on GAE."); } } @Override public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) { return ensureScheduledExecutorService().schedule(command, delay, unit); } @Override public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) { return ensureScheduledExecutorService().schedule(callable, delay, unit); } @Override public ScheduledFuture<?> scheduleAtFixedRate( Runnable command, long initialDelay, long period, TimeUnit unit) { return ensureScheduledExecutorService() .scheduleAtFixedRate(command, initialDelay, period, unit); } @Override public ScheduledFuture<?> scheduleWithFixedDelay( Runnable command, long initialDelay, long delay, TimeUnit unit) { return ensureScheduledExecutorService() .scheduleWithFixedDelay(command, initialDelay, delay, unit); } @Override public <T> Future<T> submit(Callable<T> task) { return ensureExecutorService().submit(task); } @Override public <T> Future<T> submit(Runnable task, T result) { return ensureExecutorService().submit(task, result); } @Override public Future<?> submit(Runnable task) { return ensureExecutorService().submit(task); } @Override public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException { return ensureExecutorService().invokeAll(tasks); } @Override public <T> List<Future<T>> invokeAll( Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException { return ensureExecutorService().invokeAll(tasks, timeout, unit); } @Override public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException { return ensureExecutorService().invokeAny(tasks); } @Override public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return ensureExecutorService().invokeAny(tasks, timeout, unit); } @Override public void shutdown() { ensureExecutorService().shutdown(); } @Override public List<Runnable> shutdownNow() { return ensureExecutorService().shutdownNow(); } @Override public boolean isShutdown() { return ensureExecutorService().isShutdown(); } @Override public boolean isTerminated() { return ensureExecutorService().isTerminated(); } @Override public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { return ensureExecutorService().awaitTermination(timeout, unit); } @Override public void execute(Runnable command) { ensureExecutorService().execute(command); } private static class ExecutorWrapper { private final ExecutorService executorService; private final ScheduledExecutorService scheduledExecutorService; ExecutorWrapper(String threadName) { GaeThreadFactory threadFactory = GaeThreadFactory.getInstance(); if (threadFactory.isUsingBackgroundThreads()) { scheduledExecutorService = new RevivingScheduledExecutor(threadFactory, threadName, true); executorService = scheduledExecutorService; } else { scheduledExecutorService = null; executorService = new ThreadPoolExecutor( 0, Integer.MAX_VALUE, 0L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), threadFactory); } } ExecutorService getExecutorService() { return executorService; } ScheduledExecutorService getScheduledExecutorService() { return scheduledExecutorService; } } }