View Javadoc
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.RepositorySystemSession;
24  
25  import static java.util.Objects.requireNonNull;
26  
27  /**
28   * Utilities for executors and sizing them.
29   * <em>Big fat note:</em> Do not use this class outside of resolver. This and related classes are not meant as "drop
30   * in replacement" for Jave Executors, is used in very controlled fashion only.
31   *
32   * @since 2.0.11
33   */
34  public final class SmartExecutorUtils {
35      private static final SmartExecutor DIRECT = new SmartExecutor.Direct();
36  
37      private SmartExecutorUtils() {}
38  
39      /**
40       * Returns a smart executor for given parameters. If {@code tasks} is known (non-null), it must be greater than 0.
41       * The {@code maxConcurrentTasks} also must be greater than 0. The {@code namePrefix} must be non-null.
42       * <p>
43       * If {@code tasks} is set (is known), and equals to 1 (one), or {@code maxConcurrentTasks} equals to 1, the
44       * {@link #DIRECT} executor is returned, otherwise pooled one.
45       * <p>
46       * If @code tasks} is not set (is null), pooled one is returned with pool size of {@code maxConcurrentTasks}. In
47       * this case caller is advised to <em>reuse created executor across session</em>.
48       * <p>
49       * The returned instance must be closed out, ideally in try-with-resources construct. Returned instances when
50       * {@code tasks} parameter is given should not be cached (like in a session) as they may return {@link #DIRECT}
51       * executor for one call and a pool for subsequent call, based on value of tasks.
52       *
53       * @param tasks The amount of tasks, if known, {@code null} otherwise.
54       * @param maxConcurrentTasks The maximum concurrency caller wants.
55       * @param namePrefix The thread name prefixes, must not be {@code null).}
56       */
57      public static SmartExecutor newSmartExecutor(Integer tasks, int maxConcurrentTasks, String namePrefix) {
58          if (maxConcurrentTasks < 1) {
59              throw new IllegalArgumentException("maxConcurrentTasks must be > 0");
60          }
61          requireNonNull(namePrefix);
62          int poolSize;
63          if (tasks != null) {
64              if (tasks < 1) {
65                  throw new IllegalArgumentException("tasks must be > 0");
66              }
67              if (tasks == 1 || maxConcurrentTasks == 1) {
68                  return DIRECT;
69              }
70              poolSize = Math.min(tasks, maxConcurrentTasks);
71          } else {
72              if (maxConcurrentTasks == 1) {
73                  return DIRECT;
74              }
75              poolSize = maxConcurrentTasks;
76          }
77          return new SmartExecutor.Pooled(Executors.newFixedThreadPool(poolSize, new WorkerThreadFactory(namePrefix)));
78      }
79  
80      /**
81       * Returns a smart executor, bound to session if tasks to execute are not known ahead of time. The returned
82       * instance should be handled transparently, so preferably in try-with-resource even if underlying executor is
83       * probably tied to session lifecycle, if applicable.
84       * <p>
85       * Implementation note: by this change, the caller "concurrency" is made deterministic and global(!). If you consider
86       * collector example, it is called from project builder that in Maven 4 is already multithreaded, and before this
87       * change the actual threads doing IO (HTTP) was {@code callerThreadCount x maxConcurrentTask} per JVM/Maven process.
88       * Now, the {@code maxConcurrentTask} becomes global limit, and hence can be upped without unexpected "explosion"
89       * in increasing build threading or anything.
90       */
91      public static SmartExecutor smartExecutor(
92              RepositorySystemSession session, Integer tasks, int maxConcurrentTasks, String namePrefix) {
93          if (tasks == null && maxConcurrentTasks > 1) {
94              return (SmartExecutor)
95                      session.getData().computeIfAbsent(SmartExecutor.class.getSimpleName() + "-" + 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 }