// Copyright 2016 The Nomulus Authors. All Rights Reserved. // // 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 google.registry.util; import static com.google.appengine.api.ThreadManager.currentRequestThreadFactory; import static com.google.common.base.Preconditions.checkNotNull; import static java.lang.Math.max; import static java.lang.Math.min; import static java.util.concurrent.Executors.newFixedThreadPool; import com.google.common.base.Function; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.UncheckedExecutionException; import com.google.common.util.concurrent.Uninterruptibles; import java.util.ArrayList; 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.ThreadFactory; /** Utilities for multithreaded operations in App Engine requests. */ public final class Concurrent { /** Maximum number of threads per pool. The actual GAE per-request limit is 50. */ private static final int MAX_THREADS = 10; /** * Runs transform with the default number of threads. * * @see #transform(Collection, int, Function) */ public static ImmutableList transform(Collection items, final Function funk) { return transform(items, max(1, min(items.size(), MAX_THREADS)), funk); } /** * Processes {@code items} in parallel using {@code funk}, with the specified number of threads. * *

Note: Spawned threads will inherit the same namespace. * * @throws UncheckedExecutionException to wrap the exception thrown by {@code funk}. This will * only contain the exception information for the first exception thrown. * @return transformed {@code items} in the same order. */ public static ImmutableList transform( Collection items, int threadCount, final Function funk) { checkNotNull(funk); checkNotNull(items); ThreadFactory threadFactory = currentRequestThreadFactory(); if (threadFactory == null) { // Fall back to non-concurrent transform if we can't get an App Engine thread factory (most // likely caused by hitting this code from a command-line tool). Default Java system threads // are not compatible with code that needs to interact with App Engine (such as Objectify), // which we often have in funk when calling Concurrent.transform(). // For more info see: http://stackoverflow.com/questions/15976406 return FluentIterable.from(items).transform(funk).toList(); } ExecutorService executor = newFixedThreadPool(threadCount, threadFactory); try { List> futures = new ArrayList<>(); for (final A item : items) { futures.add(executor.submit(new Callable() { @Override public B call() { return funk.apply(item); }})); } ImmutableList.Builder results = new ImmutableList.Builder<>(); for (Future future : futures) { try { results.add(Uninterruptibles.getUninterruptibly(future)); } catch (ExecutionException e) { throw new UncheckedExecutionException(e.getCause()); } } return results.build(); } finally { executor.shutdownNow(); } } private Concurrent() {} }