@@ -11,6 +11,62 @@ import mill.api.Logger
1111import mill .api .daemon .internal .NonFatal
1212
1313object ExecutionContexts {
14+ private object RunnablePriority {
15+ private val priorityMethod = new ClassValue [Option [java.lang.reflect.Method ]] {
16+ override def computeValue (clazz : Class [? ]): Option [java.lang.reflect.Method ] =
17+ try {
18+ val method = clazz.getMethod(" priority" )
19+ method.trySetAccessible()
20+ Some (method)
21+ } catch {
22+ case _ : ReflectiveOperationException => None
23+ }
24+ }
25+
26+ def apply (runnable : Runnable ): Int =
27+ priorityMethod.get(runnable.getClass) match {
28+ case Some (method) => method.invoke(runnable).asInstanceOf [Int ]
29+ case None => 0
30+ }
31+ }
32+
33+ private final class QueuedRunnable (
34+ runnable : Runnable ,
35+ val priority : Int ,
36+ val submissionIndex : Long
37+ ) extends Runnable
38+ with Comparable [QueuedRunnable ] {
39+ def run (): Unit = runnable.run()
40+
41+ override def compareTo (other : QueuedRunnable ): Int =
42+ priority.compareTo(other.priority) match {
43+ case 0 => submissionIndex.compareTo(other.submissionIndex)
44+ case n => n
45+ }
46+ }
47+
48+ private final class PriorityThreadPoolExecutor (
49+ threadCount : Int ,
50+ threadFactory : ThreadFactory
51+ ) extends ThreadPoolExecutor (
52+ threadCount,
53+ threadCount,
54+ 60 * 1000 ,
55+ TimeUnit .SECONDS ,
56+ new PriorityBlockingQueue [Runnable ](),
57+ threadFactory
58+ ) {
59+ private val submissionCount = new java.util.concurrent.atomic.AtomicLong ()
60+
61+ override def execute (command : Runnable ): Unit =
62+ super .execute(
63+ new QueuedRunnable (
64+ runnable = command,
65+ priority = RunnablePriority (command),
66+ submissionIndex = submissionCount.getAndIncrement()
67+ )
68+ )
69+ }
1470
1571 /**
1672 * Execution context that runs code immediately when scheduled, without
@@ -76,29 +132,15 @@ object ExecutionContexts {
76132 def reportFailure (t : Throwable ): Unit = {}
77133 def close (): Unit = executor.shutdown()
78134
79- val priorityRunnableCount = java.util.concurrent.atomic.AtomicLong ()
80-
81135 /**
82136 * Subclass of [[java.lang.Runnable ]] that assigns a priority to execute it
83137 *
84138 * Priority 0 is the default priority of all Mill task, priorities <0 can be used to
85139 * prioritize this runnable over most other tasks, while priorities >0 can be used to
86140 * de-prioritize it.
87141 */
88- class PriorityRunnable (val priority : Int , run0 : () => Unit ) extends Runnable
89- with Comparable [PriorityRunnable ] {
90- def run () = run0()
91- val priorityRunnableIndex : Long = priorityRunnableCount.getAndIncrement()
92- override def compareTo (o : PriorityRunnable ): Int = priority.compareTo(o.priority) match {
93- case 0 =>
94- // `Comparable` wants a *total* ordering, so we need to use `priorityRunnableIndex`
95- // to break ties between instances with the same priority. This index is assigned
96- // when a task is submitted, so it should more or less follow insertion order,
97- // and is a `Long` which should be big enough never to overflow
98- assert(this == o || this .priorityRunnableIndex != o.priorityRunnableIndex)
99- this .priorityRunnableIndex.compareTo(o.priorityRunnableIndex)
100- case n => n
101- }
142+ class PriorityRunnable (val priority : Int , run0 : () => Unit ) extends Runnable {
143+ def run (): Unit = run0()
102144 }
103145
104146 /**
@@ -151,25 +193,15 @@ object ExecutionContexts {
151193 def createExecutor (threadCount : Int ): ThreadPoolExecutor = {
152194 val executorIndex = executorCounter.incrementAndGet()
153195 val threadCounter = new AtomicInteger
154- new ThreadPoolExecutor (
155- threadCount,
156- threadCount,
157- 60 * 1000 ,
158- TimeUnit .SECONDS ,
159- // Use a `Deque` rather than a normal `Queue`, with the various `poll`/`take`
160- // operations reversed, providing elements in a LIFO order. This ensures that
161- // child `fork.async` tasks always take priority over parent tasks, avoiding
162- // large numbers of blocked parent tasks from piling up
163- new PriorityBlockingQueue [Runnable ](),
164- runnable => {
165- val threadIndex = threadCounter.incrementAndGet()
166- val t = new Thread (
167- runnable,
168- s " execution-contexts-threadpool- $executorIndex-thread- $threadIndex"
169- )
170- t.setDaemon(true )
171- t
172- }
173- )
196+ val threadFactory : ThreadFactory = runnable => {
197+ val threadIndex = threadCounter.incrementAndGet()
198+ val t = new Thread (
199+ runnable,
200+ s " execution-contexts-threadpool- $executorIndex-thread- $threadIndex"
201+ )
202+ t.setDaemon(true )
203+ t
204+ }
205+ new PriorityThreadPoolExecutor (threadCount, threadFactory)
174206 }
175207}
0 commit comments