001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.eclipse.aether.util.concurrency;
020
021import java.util.concurrent.Executors;
022
023import org.eclipse.aether.RepositorySystemSession;
024
025import static java.util.Objects.requireNonNull;
026
027/**
028 * Utilities for executors and sizing them.
029 * <em>Big fat note:</em> Do not use this class outside of resolver. This and related classes are not meant as "drop
030 * in replacement" for Jave Executors, is used in very controlled fashion only.
031 *
032 * @since 2.0.11
033 */
034public final class SmartExecutorUtils {
035    private static final SmartExecutor DIRECT = new SmartExecutor.Direct();
036
037    private SmartExecutorUtils() {}
038
039    /**
040     * Returns a smart executor for given parameters. If {@code tasks} is known (non-null), it must be greater than 0.
041     * The {@code maxConcurrentTasks} also must be greater than 0. The {@code namePrefix} must be non-null.
042     * <p>
043     * If {@code tasks} is set (is known), and equals to 1 (one), or {@code maxConcurrentTasks} equals to 1, the
044     * {@link #DIRECT} executor is returned, otherwise pooled one.
045     * <p>
046     * If @code tasks} is not set (is null), pooled one is returned with pool size of {@code maxConcurrentTasks}. In
047     * this case caller is advised to <em>reuse created executor across session</em>.
048     * <p>
049     * The returned instance must be closed out, ideally in try-with-resources construct. Returned instances when
050     * {@code tasks} parameter is given should not be cached (like in a session) as they may return {@link #DIRECT}
051     * executor for one call and a pool for subsequent call, based on value of tasks.
052     *
053     * @param tasks The amount of tasks, if known, {@code null} otherwise.
054     * @param maxConcurrentTasks The maximum concurrency caller wants.
055     * @param namePrefix The thread name prefixes, must not be {@code null).}
056     */
057    public static SmartExecutor newSmartExecutor(Integer tasks, int maxConcurrentTasks, String namePrefix) {
058        if (maxConcurrentTasks < 1) {
059            throw new IllegalArgumentException("maxConcurrentTasks must be > 0");
060        }
061        requireNonNull(namePrefix);
062        int poolSize;
063        if (tasks != null) {
064            if (tasks < 1) {
065                throw new IllegalArgumentException("tasks must be > 0");
066            }
067            if (tasks == 1 || maxConcurrentTasks == 1) {
068                return DIRECT;
069            }
070            poolSize = Math.min(tasks, maxConcurrentTasks);
071        } else {
072            if (maxConcurrentTasks == 1) {
073                return DIRECT;
074            }
075            poolSize = maxConcurrentTasks;
076        }
077        return new SmartExecutor.Pooled(Executors.newFixedThreadPool(poolSize, new WorkerThreadFactory(namePrefix)));
078    }
079
080    /**
081     * Returns a smart executor, bound to session if tasks to execute are not known ahead of time. The returned
082     * instance should be handled transparently, so preferably in try-with-resource even if underlying executor is
083     * probably tied to session lifecycle, if applicable.
084     * <p>
085     * Implementation note: by this change, the caller "concurrency" is made deterministic and global(!). If you consider
086     * collector example, it is called from project builder that in Maven 4 is already multithreaded, and before this
087     * change the actual threads doing IO (HTTP) was {@code callerThreadCount x maxConcurrentTask} per JVM/Maven process.
088     * Now, the {@code maxConcurrentTask} becomes global limit, and hence can be upped without unexpected "explosion"
089     * in increasing build threading or anything.
090     */
091    public static SmartExecutor smartExecutor(
092            RepositorySystemSession session, Integer tasks, int maxConcurrentTasks, String namePrefix) {
093        if (tasks == null && maxConcurrentTasks > 1) {
094            return (SmartExecutor)
095                    session.getData().computeIfAbsent(SmartExecutor.class.getSimpleName() + "-" + namePrefix, () -> {
096                        SmartExecutor smartExecutor = newSmartExecutor(null, maxConcurrentTasks, namePrefix);
097                        session.addOnSessionEndedHandler(smartExecutor::close);
098                        return new SmartExecutor.NonClosing(smartExecutor);
099                    });
100        } else {
101            return newSmartExecutor(tasks, maxConcurrentTasks, namePrefix);
102        }
103    }
104}