Skip to content

Commit 3df6898

Browse files
committed
.
1 parent 32b1a0a commit 3df6898

22 files changed

Lines changed: 175 additions & 85 deletions

File tree

core/api/daemon/src/mill/api/daemon/internal/LauncherLocking.scala

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@ package mill.api.daemon.internal
22

33
import java.nio.file.Path
44

5+
/**
6+
* Locking APIs to let us safely run multiple concurrent launchers at the same time
7+
*
8+
* [[metaBuildLock]] ensures that only one launcher can be bootstrapping the meta-build,
9+
* but once the meta-build is done multiple launchers can run concurrently and only lock
10+
* on the individual tasks they need to run or use.
11+
*/
512
private[mill] trait LauncherLocking extends AutoCloseable {
613

714
def metaBuildLock(depth: Int, kind: LauncherLocking.LockKind): LauncherLocking.Lease
@@ -18,8 +25,6 @@ private[mill] object LauncherLocking {
1825
case Read, Write
1926
}
2027

21-
final case class HolderInfo(pid: Long, command: String)
22-
2328
trait Lease extends AutoCloseable {
2429
def downgradeToRead(): Unit = ()
2530
}

core/api/daemon/src/mill/api/daemon/internal/bsp/BspBootstrapBridge.scala

Lines changed: 0 additions & 25 deletions
This file was deleted.

core/api/daemon/src/mill/api/daemon/internal/bsp/BspServerHandle.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ package mill.api.daemon.internal.bsp
22

33
import scala.concurrent.Future
44

5+
/** With this server handle you can interact with a running Mill BSP server. */
56
trait BspServerHandle {
67

78
/**
9+
* Completes when the BSP server shuts down or asks the launcher to reload the workspace.
810
*/
911
def shutdownFuture: Future[BspServerResult]
1012

core/api/src/mill/api/Task.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@ object Task {
306306
*
307307
* Like [[PersistentImpl]], The user defining a [[Worker]] assumes the
308308
* responsibility of ensuring the implementation is idempotent regardless of
309+
* what in-memory state the worker may have.
309310
*/
310311
inline def Worker[T](inline t: Result[T])(using inline ctx: mill.api.ModuleCtx): Worker[T] =
311312
${ Macros.workerImpl2[T]('t)('ctx) }

core/constants/src/mill/constants/EnvVars.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,13 @@ public class EnvVars {
2929

3030
/**
3131
* Output directory where Mill workers' state and Mill tasks output should be
32+
* written to in BSP server mode.
3233
*/
3334
public static final String MILL_BSP_OUTPUT_DIR = "MILL_BSP_OUTPUT_DIR";
3435

3536
/**
37+
* If set to "1", Mill will re-use the regular out/ folder instead of
38+
* using a separate one for BSP output.
3639
*/
3740
public static final String MILL_NO_SEPARATE_BSP_OUTPUT_DIR = "MILL_NO_SEPARATE_BSP_OUTPUT_DIR";
3841

core/constants/src/mill/constants/OutFiles.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ private OutFiles1() {}
3535
public final String defaultOut = "out";
3636

3737
/**
38+
* Default hard-coded value for the Mill `out/` folder path in BSP server mode.
39+
* To get the effective out dir, use {@link #outFor}.
3840
*/
3941
public final String defaultBspOut = ".bsp/out";
4042

@@ -193,6 +195,7 @@ public static String outFor(OutFolderMode outMode) {
193195
/** @deprecated Use inner OutFiles instead, since Mill 1.1.0 */
194196
@Deprecated
195197
public static final String millOutLock = OutFiles.millOutLock;
198+
/** @deprecated Use inner OutFiles instead, since Mill 1.1.0 */
196199
@Deprecated
197200
public static final String millActive = "mill-active.json";
198201
/** @deprecated Use millActive instead */

core/exec/src/mill/exec/Execution.scala

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,11 @@ case class Execution(
295295
ec.fold(ExecutionContexts.RunNow)(new ExecutionContexts.ThreadPool(_))
296296
implicit val taskExecutionContext =
297297
if (exclusive) ExecutionContexts.RunNow else forkExecutionContext
298+
// We walk the task graph in topological order and schedule the futures
299+
// to run asynchronously. During this walk, we store the scheduled futures
300+
// in a dictionary. When scheduling each future, we are guaranteed that the
301+
// necessary upstream futures will have already been scheduled and stored,
302+
// due to the topological order of traversal.
298303
for (terminal <- terminals) {
299304
val deps = interGroupDeps(terminal)
300305

@@ -376,9 +381,11 @@ case class Execution(
376381

377382
val newFailures = res.newResults.values.count(r => r.asFailing.isDefined)
378383

384+
// Count new failures: tasks with upstream failures should be skipped, not failed.
379385
rootFailedCount.addAndGet(newFailures)
380386
completedCount.incrementAndGet()
381387

388+
// Always show the completed count in the header after a task finishes.
382389
logger.prompt.setPromptHeaderPrefix(formatHeaderPrefix())
383390

384391
if (failFast && res.newResults.values.exists(_.asSuccess.isEmpty))
@@ -404,9 +411,14 @@ case class Execution(
404411
}
405412
}
406413
} catch {
407-
case e: mill.api.daemon.StopWithResponse[?] => throw e
414+
case e: mill.api.daemon.StopWithResponse[?] =>
415+
// Let StopWithResponse propagate: it is a controlled shutdown signal.
416+
throw e
408417
case e: Throwable if !mill.api.daemon.internal.NonFatal(e) =>
418+
// Wrap fatal errors so Scala's Future machinery reports them instead of
419+
// silently terminating the future and leaving downstream Awaits hanging.
409420
val nonFatal = new Exception(s"fatal exception occurred: $e", e)
421+
// Preserve the original stack trace, since that points at the real failure.
410422
nonFatal.setStackTrace(e.getStackTrace)
411423
throw nonFatal
412424
} finally {
@@ -416,9 +428,13 @@ case class Execution(
416428
}
417429
}
418430

431+
// Make sure we wait for all tasks from this batch to finish before starting the next
432+
// one, so we don't mix up exclusive and non-exclusive tasks running at the same time.
419433
terminals.map(t => (t, Await.result(futures(t), duration.Duration.Inf)))
420434
}
421435

436+
// Run all non-command tasks according to the configured thread count,
437+
// but run exclusive commands in linear order.
422438
val (nonExclusiveTasks, leafExclusiveCommands) = indexToTerminal.partition {
423439
case t: Task.Named[_] => !downstreamOfExclusive.contains(t)
424440
case _ => !serialCommandExec
@@ -431,13 +447,17 @@ case class Execution(
431447
val isOutermostExecution = executionNestingDepth.get() == 1
432448
val hasFailures = rootFailedCount.get() > 0
433449
val showFinalStatus = isOutermostExecution && (hasFailures || isFinalDepth)
450+
// Set final header showing SUCCESS/FAILED status:
451+
// - FAILED: show for any outermost execution with failures
452+
// - SUCCESS: only show for the final requested depth
434453
logger.prompt.setPromptHeaderPrefix(formatHeaderPrefix(completed = showFinalStatus))
435454

436455
logger.prompt.clearPromptStatuses()
437456

438457
val finishedOptsMap = (nonExclusiveResults ++ exclusiveResults).toMap
439458

440459
val taskInvalidationReasons = {
460+
// Convert versionMismatchReasons to Map[String, String] for invalidation-tree logging.
441461
import scala.jdk.CollectionConverters.ConcurrentMapHasAsScala
442462
versionMismatchReasons.asScala.collect {
443463
case (t: Task.Named[?], reason) => t.ctx.segments.render -> reason

core/exec/src/mill/exec/ExecutionContexts.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ object ExecutionContexts {
4040

4141
override def compareTo(other: QueuedRunnable): Int =
4242
priority.compareTo(other.priority) match {
43-
case 0 => submissionIndex.compareTo(other.submissionIndex)
43+
case 0 =>
44+
// Comparable needs a total ordering, so break ties using submission order.
45+
submissionIndex.compareTo(other.submissionIndex)
4446
case n => n
4547
}
4648
}

core/exec/src/mill/exec/GroupExecution.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,8 @@ trait GroupExecution {
309309
logger = logger,
310310
inputsHash = inputsHash,
311311
labelled = labelled,
312+
// `cached.isEmpty` means the worker metadata file was removed by the user,
313+
// so recompute the worker.
312314
forceDiscard = cached.isEmpty,
313315
deps = deps,
314316
paths = Some(paths),
@@ -405,6 +407,10 @@ trait GroupExecution {
405407
(valueHash, serializedPaths, true)
406408

407409
case _ =>
410+
// Wipe out any cached meta.json file that exists, so
411+
// a following run won't look at the cached metadata file and
412+
// assume it's associated with the possibly-borked state of the
413+
// destPath after an evaluation failure.
408414
os.remove.all(paths.meta)
409415
(0, Nil, false)
410416
}

core/internal/src/mill/internal/LauncherLockingImpl.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package mill.internal
22

33
import mill.api.daemon.internal.LauncherLocking
4-
import mill.api.daemon.internal.LauncherLocking.HolderInfo
4+
import mill.internal.WriterPreferringRwLock.HolderInfo
55

66
import java.io.PrintStream
77
import java.util.concurrent.atomic.AtomicBoolean

0 commit comments

Comments
 (0)