1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.eclipse.aether.util.concurrency;
20
21 import java.util.concurrent.Executors;
22
23 import org.eclipse.aether.Keys;
24 import org.eclipse.aether.RepositorySystemSession;
25
26 import static java.util.Objects.requireNonNull;
27
28 /**
29 * Utilities for executors and sizing them.
30 * <em>Big fat note:</em> Do not use this class outside of resolver. This and related classes are not meant as "drop
31 * in replacement" for Jave Executors, is used in very controlled fashion only.
32 *
33 * @since 2.0.11
34 */
35 public final class SmartExecutorUtils {
36 private static final SmartExecutor DIRECT = new SmartExecutor.Direct();
37
38 private SmartExecutorUtils() {}
39
40 /**
41 * Returns a smart executor for given parameters. If {@code tasks} is known (non-null), it must be greater than 0.
42 * The {@code maxConcurrentTasks} also must be greater than 0. The {@code namePrefix} must be non-null.
43 * <p>
44 * If {@code tasks} is set (is known), and equals to 1 (one), or {@code maxConcurrentTasks} equals to 1, the
45 * {@link #DIRECT} executor is returned, otherwise pooled one.
46 * <p>
47 * If @code tasks} is not set (is null), pooled one is returned with pool size of {@code maxConcurrentTasks}. In
48 * this case caller is advised to <em>reuse created executor across session</em>.
49 * <p>
50 * The returned instance must be closed out, ideally in try-with-resources construct. Returned instances when
51 * {@code tasks} parameter is given should not be cached (like in a session) as they may return {@link #DIRECT}
52 * executor for one call and a pool for subsequent call, based on value of tasks.
53 *
54 * @param tasks the amount of tasks, if known, {@code null} otherwise
55 * @param maxConcurrentTasks the maximum concurrency caller wants
56 * @param namePrefix the thread name prefixes, must not be {@code null).}
57 */
58 public static SmartExecutor newSmartExecutor(Integer tasks, int maxConcurrentTasks, String namePrefix) {
59 if (maxConcurrentTasks < 1) {
60 throw new IllegalArgumentException("maxConcurrentTasks must be > 0");
61 }
62 requireNonNull(namePrefix);
63 int poolSize;
64 if (tasks != null) {
65 if (tasks < 1) {
66 throw new IllegalArgumentException("tasks must be > 0");
67 }
68 if (tasks == 1 || maxConcurrentTasks == 1) {
69 return DIRECT;
70 }
71 poolSize = Math.min(tasks, maxConcurrentTasks);
72 } else {
73 if (maxConcurrentTasks == 1) {
74 return DIRECT;
75 }
76 poolSize = maxConcurrentTasks;
77 }
78 return new SmartExecutor.Pooled(Executors.newFixedThreadPool(poolSize, new WorkerThreadFactory(namePrefix)));
79 }
80
81 /**
82 * Returns a smart executor, bound to session if tasks to execute are not known ahead of time. The returned
83 * instance should be handled transparently, so preferably in try-with-resource even if underlying executor is
84 * probably tied to session lifecycle, if applicable.
85 * <p>
86 * Implementation note: by this change, the caller "concurrency" is made deterministic and global(!). If you consider
87 * collector example, it is called from project builder that in Maven 4 is already multithreaded, and before this
88 * change the actual threads doing IO (HTTP) was {@code callerThreadCount x maxConcurrentTask} per JVM/Maven process.
89 * Now, the {@code maxConcurrentTask} becomes global limit, and hence can be upped without unexpected "explosion"
90 * in increasing build threading or anything.
91 */
92 public static SmartExecutor smartExecutor(
93 RepositorySystemSession session, Integer tasks, int maxConcurrentTasks, String namePrefix) {
94 if (tasks == null && maxConcurrentTasks > 1) {
95 return (SmartExecutor) session.getData().computeIfAbsent(Keys.of(SmartExecutor.class, namePrefix), () -> {
96 SmartExecutor smartExecutor = newSmartExecutor(null, maxConcurrentTasks, namePrefix);
97 session.addOnSessionEndedHandler(smartExecutor::close);
98 return new SmartExecutor.NonClosing(smartExecutor);
99 });
100 } else {
101 return newSmartExecutor(tasks, maxConcurrentTasks, namePrefix);
102 }
103 }
104 }