diff --git a/example/scalalib/web/1-todo-webapp/test/src/WebAppTests.scala b/example/scalalib/web/1-todo-webapp/test/src/WebAppTests.scala index 2aa948961ab..c17e542f781 100644 --- a/example/scalalib/web/1-todo-webapp/test/src/WebAppTests.scala +++ b/example/scalalib/web/1-todo-webapp/test/src/WebAppTests.scala @@ -5,12 +5,12 @@ import utest._ object WebAppTests extends TestSuite { def withServer[T](example: cask.main.Main)(f: String => T): T = { val server = io.undertow.Undertow.builder - .addHttpListener(8081, "localhost") + .addHttpListener(8181, "localhost") .setHandler(example.defaultHandler) .build server.start() val res = - try f("http://localhost:8081") + try f("http://localhost:8181") finally server.stop() res } diff --git a/example/scalalib/web/2-webapp-cache-busting/test/src/WebAppTests.scala b/example/scalalib/web/2-webapp-cache-busting/test/src/WebAppTests.scala index 2aa948961ab..5122ca87a77 100644 --- a/example/scalalib/web/2-webapp-cache-busting/test/src/WebAppTests.scala +++ b/example/scalalib/web/2-webapp-cache-busting/test/src/WebAppTests.scala @@ -5,12 +5,12 @@ import utest._ object WebAppTests extends TestSuite { def withServer[T](example: cask.main.Main)(f: String => T): T = { val server = io.undertow.Undertow.builder - .addHttpListener(8081, "localhost") + .addHttpListener(8182, "localhost") .setHandler(example.defaultHandler) .build server.start() val res = - try f("http://localhost:8081") + try f("http://localhost:8182") finally server.stop() res } diff --git a/example/scalalib/web/4-webapp-scalajs/test/src/WebAppTests.scala b/example/scalalib/web/4-webapp-scalajs/test/src/WebAppTests.scala index 2aa948961ab..b60aa6562c0 100644 --- a/example/scalalib/web/4-webapp-scalajs/test/src/WebAppTests.scala +++ b/example/scalalib/web/4-webapp-scalajs/test/src/WebAppTests.scala @@ -5,12 +5,12 @@ import utest._ object WebAppTests extends TestSuite { def withServer[T](example: cask.main.Main)(f: String => T): T = { val server = io.undertow.Undertow.builder - .addHttpListener(8081, "localhost") + .addHttpListener(8184, "localhost") .setHandler(example.defaultHandler) .build server.start() val res = - try f("http://localhost:8081") + try f("http://localhost:8184") finally server.stop() res } diff --git a/example/scalalib/web/5-webapp-scalajs-shared/test/src/WebAppTests.scala b/example/scalalib/web/5-webapp-scalajs-shared/test/src/WebAppTests.scala index 2aa948961ab..459fd56bec0 100644 --- a/example/scalalib/web/5-webapp-scalajs-shared/test/src/WebAppTests.scala +++ b/example/scalalib/web/5-webapp-scalajs-shared/test/src/WebAppTests.scala @@ -5,12 +5,12 @@ import utest._ object WebAppTests extends TestSuite { def withServer[T](example: cask.main.Main)(f: String => T): T = { val server = io.undertow.Undertow.builder - .addHttpListener(8081, "localhost") + .addHttpListener(8185, "localhost") .setHandler(example.defaultHandler) .build server.start() val res = - try f("http://localhost:8081") + try f("http://localhost:8185") finally server.stop() res } diff --git a/main/client/src/mill/main/client/ServerLauncher.java b/main/client/src/mill/main/client/ServerLauncher.java index 3ce9a3c23a3..5bafa98690c 100644 --- a/main/client/src/mill/main/client/ServerLauncher.java +++ b/main/client/src/mill/main/client/ServerLauncher.java @@ -47,7 +47,6 @@ public static class Result { public Path serverDir; } - static final int tailerRefreshIntervalMillis = 2; final int serverProcessesLimit = 5; final int serverInitWaitMillis = 10000; @@ -120,75 +119,63 @@ public Result acquireLocksAndRun(String outDir) throws Exception { } int run(Path serverDir, boolean setJnaNoSys, Locks locks) throws Exception { - try (final FileToStreamTailer stdoutTailer = new FileToStreamTailer( - new java.io.File(serverDir + "/" + ServerFiles.stdout), - stdout, - tailerRefreshIntervalMillis); - final FileToStreamTailer stderrTailer = new FileToStreamTailer( - new java.io.File(serverDir + "/" + ServerFiles.stderr), - stderr, - tailerRefreshIntervalMillis); ) { - stdoutTailer.start(); - stderrTailer.start(); - String serverPath = serverDir + "/" + ServerFiles.runArgs; - try (OutputStream f = Files.newOutputStream(Paths.get(serverPath))) { - f.write(System.console() != null ? 1 : 0); - Util.writeString(f, BuildInfo.millVersion); - Util.writeArgs(args, f); - Util.writeMap(env, f); - } - - if (locks.processLock.probe()) { - initServer(serverDir, setJnaNoSys, locks); - } - - while (locks.processLock.probe()) Thread.sleep(3); - - String socketName = ServerFiles.pipe(serverDir.toString()); - AFUNIXSocketAddress addr = AFUNIXSocketAddress.of(new File(socketName)); + String serverPath = serverDir + "/" + ServerFiles.runArgs; + try (OutputStream f = Files.newOutputStream(Paths.get(serverPath))) { + f.write(System.console() != null ? 1 : 0); + Util.writeString(f, BuildInfo.millVersion); + Util.writeArgs(args, f); + Util.writeMap(env, f); + } - long retryStart = System.currentTimeMillis(); - Socket ioSocket = null; - Throwable socketThrowable = null; - while (ioSocket == null && System.currentTimeMillis() - retryStart < serverInitWaitMillis) { - try { - ioSocket = AFUNIXSocket.connectTo(addr); - } catch (Throwable e) { - socketThrowable = e; - Thread.sleep(10); - } - } + if (locks.processLock.probe()) { + initServer(serverDir, setJnaNoSys, locks); + } - if (ioSocket == null) { - throw new Exception("Failed to connect to server", socketThrowable); - } + while (locks.processLock.probe()) Thread.sleep(3); - InputStream outErr = ioSocket.getInputStream(); - OutputStream in = ioSocket.getOutputStream(); - ProxyStream.Pumper outPumper = new ProxyStream.Pumper(outErr, stdout, stderr); - InputPumper inPump = new InputPumper(() -> stdin, () -> in, true); - Thread outPumperThread = new Thread(outPumper, "outPump"); - outPumperThread.setDaemon(true); - Thread inThread = new Thread(inPump, "inPump"); - inThread.setDaemon(true); - outPumperThread.start(); - inThread.start(); - - if (forceFailureForTestingMillisDelay > 0) { - Thread.sleep(forceFailureForTestingMillisDelay); - throw new Exception("Force failure for testing: " + serverDir); - } - outPumperThread.join(); + String socketName = ServerFiles.pipe(serverDir.toString()); + AFUNIXSocketAddress addr = AFUNIXSocketAddress.of(new File(socketName)); + long retryStart = System.currentTimeMillis(); + Socket ioSocket = null; + Throwable socketThrowable = null; + while (ioSocket == null && System.currentTimeMillis() - retryStart < serverInitWaitMillis) { try { - return Integer.parseInt( - Files.readAllLines(Paths.get(serverDir + "/" + ServerFiles.exitCode)) - .get(0)); + ioSocket = AFUNIXSocket.connectTo(addr); } catch (Throwable e) { - return Util.ExitClientCodeCannotReadFromExitCodeFile(); - } finally { - ioSocket.close(); + socketThrowable = e; + Thread.sleep(10); } } + + if (ioSocket == null) { + throw new Exception("Failed to connect to server", socketThrowable); + } + + InputStream outErr = ioSocket.getInputStream(); + OutputStream in = ioSocket.getOutputStream(); + ProxyStream.Pumper outPumper = new ProxyStream.Pumper(outErr, stdout, stderr); + InputPumper inPump = new InputPumper(() -> stdin, () -> in, true); + Thread outPumperThread = new Thread(outPumper, "outPump"); + outPumperThread.setDaemon(true); + Thread inThread = new Thread(inPump, "inPump"); + inThread.setDaemon(true); + outPumperThread.start(); + inThread.start(); + + if (forceFailureForTestingMillisDelay > 0) { + Thread.sleep(forceFailureForTestingMillisDelay); + throw new Exception("Force failure for testing: " + serverDir); + } + outPumperThread.join(); + + try { + return Integer.parseInt( + Files.readAllLines(Paths.get(serverDir + "/" + ServerFiles.exitCode)).get(0)); + } catch (Throwable e) { + return Util.ExitClientCodeCannotReadFromExitCodeFile(); + } finally { + ioSocket.close(); + } } } diff --git a/main/util/src/mill/util/Jvm.scala b/main/util/src/mill/util/Jvm.scala index 50e78931a0e..267d0566a8d 100644 --- a/main/util/src/mill/util/Jvm.scala +++ b/main/util/src/mill/util/Jvm.scala @@ -2,6 +2,7 @@ package mill.util import mill.api.Loose.Agg import mill.api._ +import mill.main.client.ServerFiles import os.{ProcessOutput, SubProcess} import java.io._ @@ -117,8 +118,18 @@ object Jvm extends CoursierSupport { mainArgs, workingDir, if (!background) None - else if (runBackgroundLogToConsole) Some((os.Inherit, os.Inherit)) - else Jvm.defaultBackgroundOutputs(ctx.dest), + else if (runBackgroundLogToConsole) { + val pwd0 = os.Path(java.nio.file.Paths.get(".").toAbsolutePath) + // Hack to forward the background subprocess output to the Mill server process + // stdout/stderr files, so the output will get properly slurped up by the Mill server + // and shown to any connected Mill client even if the current command has completed + Some( + ( + os.PathAppendRedirect(pwd0 / ".." / ServerFiles.stdout), + os.PathAppendRedirect(pwd0 / ".." / ServerFiles.stderr) + ) + ) + } else Jvm.defaultBackgroundOutputs(ctx.dest), useCpPassingJar ) } diff --git a/mill b/mill index d03a045cb77..84c22230cfe 100755 --- a/mill +++ b/mill @@ -7,7 +7,7 @@ set -e if [ -z "${DEFAULT_MILL_VERSION}" ] ; then - DEFAULT_MILL_VERSION=0.11.12 + DEFAULT_MILL_VERSION=0.12.2 fi if [ -z "$MILL_VERSION" ] ; then diff --git a/runner/src/mill/runner/MillBuildBootstrap.scala b/runner/src/mill/runner/MillBuildBootstrap.scala index dc3e0f99961..dc078a7b8e5 100644 --- a/runner/src/mill/runner/MillBuildBootstrap.scala +++ b/runner/src/mill/runner/MillBuildBootstrap.scala @@ -70,9 +70,6 @@ class MillBuildBootstrap( } def evaluateRec(depth: Int): RunnerState = { - mill.main.client.DebugLog.println( - "MillBuildBootstrap.evaluateRec " + depth + " " + targetsAndParams.mkString(" ") - ) // println(s"+evaluateRec($depth) " + recRoot(projectRoot, depth)) val prevFrameOpt = prevRunnerState.frames.lift(depth) val prevOuterFrameOpt = prevRunnerState.frames.lift(depth - 1) diff --git a/runner/src/mill/runner/MillMain.scala b/runner/src/mill/runner/MillMain.scala index 9a5dcf29056..580faa3283a 100644 --- a/runner/src/mill/runner/MillMain.scala +++ b/runner/src/mill/runner/MillMain.scala @@ -4,7 +4,7 @@ import java.io.{PipedInputStream, PrintStream} import java.nio.file.Files import java.nio.file.StandardOpenOption import java.util.Locale -import scala.jdk.CollectionConverters._ +import scala.jdk.CollectionConverters.* import scala.util.Properties import mill.java9rtexport.Export import mill.api.{MillException, SystemStreams, WorkspaceRoot, internal} @@ -33,7 +33,7 @@ object MillMain { err.println(e.getCause.getMessage()) (false, onError) case NonFatal(e) => - err.println("An unexpected error occurred") + err.println("An unexpected error occurred " + e) throw e (false, onError) } @@ -221,69 +221,74 @@ object MillMain { while (repeatForBsp) { repeatForBsp = false - val (isSuccess, evalStateOpt) = Watching.watchLoop( - ringBell = config.ringBell.value, - watch = config.watch.value, - streams = streams, - setIdle = setIdle, - evaluate = (prevState: Option[RunnerState]) => { - adjustJvmProperties(userSpecifiedProperties, initialSystemProperties) - - withOutLock( - config.noBuildLock.value || bspContext.isDefined, - config.noWaitForBuildLock.value, - out, - targetsAndParams, - streams - ) { - val logger = getLogger( - streams, - config, - mainInteractive, - enableTicker = - config.ticker - .orElse(config.enableTicker) - .orElse(Option.when(config.disableTicker.value)(false)), - printLoggerState, - serverDir, - colored = colored, - colors = colors - ) - Using.resource(logger) { _ => - try new MillBuildBootstrap( - projectRoot = WorkspaceRoot.workspaceRoot, - output = out, - home = config.home, - keepGoing = config.keepGoing.value, - imports = config.imports, - env = env, - threadCount = threadCount, - targetsAndParams = targetsAndParams, - prevRunnerState = prevState.getOrElse(stateCache), - logger = logger, - disableCallgraph = config.disableCallgraph.value, - needBuildFile = needBuildFile(config), - requestedMetaLevel = config.metaLevel, - config.allowPositional.value, - systemExit = systemExit, - streams0 = streams0 - ).evaluate() + Using.resource(new TailManager(serverDir)) { tailManager => + val (isSuccess, evalStateOpt) = Watching.watchLoop( + ringBell = config.ringBell.value, + watch = config.watch.value, + streams = streams, + setIdle = setIdle, + evaluate = (prevState: Option[RunnerState]) => { + adjustJvmProperties(userSpecifiedProperties, initialSystemProperties) + + withOutLock( + config.noBuildLock.value || bspContext.isDefined, + config.noWaitForBuildLock.value, + out, + targetsAndParams, + streams + ) { + Using.resource(getLogger( + streams, + config, + mainInteractive, + enableTicker = + config.ticker + .orElse(config.enableTicker) + .orElse(Option.when(config.disableTicker.value)(false)), + printLoggerState, + serverDir, + colored = colored, + colors = colors + )) { logger => + SystemStreams.withStreams(logger.systemStreams) { + tailManager.withOutErr(logger.outputStream, logger.errorStream) { + new MillBuildBootstrap( + projectRoot = WorkspaceRoot.workspaceRoot, + output = out, + home = config.home, + keepGoing = config.keepGoing.value, + imports = config.imports, + env = env, + threadCount = threadCount, + targetsAndParams = targetsAndParams, + prevRunnerState = prevState.getOrElse(stateCache), + logger = logger, + disableCallgraph = config.disableCallgraph.value, + needBuildFile = needBuildFile(config), + requestedMetaLevel = config.metaLevel, + config.allowPositional.value, + systemExit = systemExit, + streams0 = streams0 + ).evaluate() + } + } + } } - } - }, - colors = colors - ) - bspContext.foreach { ctx => - repeatForBsp = - BspContext.bspServerHandle.lastResult == Some( - BspServerResult.ReloadWorkspace - ) - streams.err.println( - s"`$bspCmd` returned with ${BspContext.bspServerHandle.lastResult}" + }, + colors = colors ) - } + bspContext.foreach { ctx => + repeatForBsp = + BspContext.bspServerHandle.lastResult == Some( + BspServerResult.ReloadWorkspace + ) + streams.err.println( + s"`$bspCmd` returned with ${BspContext.bspServerHandle.lastResult}" + ) + } - loopRes = (isSuccess, evalStateOpt) + loopRes = (isSuccess, evalStateOpt) + } } // while repeatForBsp bspContext.foreach { ctx => streams.err.println( diff --git a/runner/src/mill/runner/MillServerMain.scala b/runner/src/mill/runner/MillServerMain.scala index 917a2f3b619..b426af94dd4 100644 --- a/runner/src/mill/runner/MillServerMain.scala +++ b/runner/src/mill/runner/MillServerMain.scala @@ -24,7 +24,7 @@ object MillServerMain { ) val acceptTimeoutMillis = - Try(System.getProperty("mill.server_timeout").toInt).getOrElse(5 * 60 * 1000) // 5 minutes + Try(System.getProperty("mill.server_timeout").toInt).getOrElse(30 * 60 * 1000) // 30 minutes new MillServerMain( serverDir = os.Path(args0(0)), diff --git a/runner/src/mill/runner/TailManager.scala b/runner/src/mill/runner/TailManager.scala new file mode 100644 index 00000000000..9290fd07cf4 --- /dev/null +++ b/runner/src/mill/runner/TailManager.scala @@ -0,0 +1,48 @@ +package mill.runner + +import mill.api.SystemStreams.ThreadLocalStreams +import mill.main.client.{FileToStreamTailer, ServerFiles} + +import java.io.{OutputStream, PrintStream} + +class TailManager(serverDir: os.Path) extends AutoCloseable { + val tailerRefreshIntervalMillis = 2 + + // We need to explicitly manage tailerOut/tailerErr ourselves, rather than relying + // on System.out/System.err redirects, because those redirects are ThreadLocal and + // do not affect the tailers which run on their own separate threads + @volatile var tailerOut: OutputStream = System.out + @volatile var tailerErr: OutputStream = System.err + val stdoutTailer = new FileToStreamTailer( + (serverDir / ServerFiles.stdout).toIO, + new PrintStream(new ThreadLocalStreams.ProxyOutputStream { + def delegate(): OutputStream = tailerOut + }), + tailerRefreshIntervalMillis + ) + val stderrTailer = new FileToStreamTailer( + (serverDir / ServerFiles.stderr).toIO, + new PrintStream(new ThreadLocalStreams.ProxyOutputStream { + def delegate(): OutputStream = tailerErr + }), + tailerRefreshIntervalMillis + ) + + stdoutTailer.start() + stderrTailer.start() + + def withOutErr[T](newOut: OutputStream, newErr: OutputStream)(t: => T): T = { + tailerOut = newOut + tailerErr = newErr + try t + finally { + tailerOut = System.out + tailerErr = System.err + } + } + + override def close(): Unit = { + stdoutTailer.close() + stderrTailer.close() + } +} diff --git a/scalalib/backgroundwrapper/src/mill/scalalib/backgroundwrapper/BackgroundWrapper.java b/scalalib/backgroundwrapper/src/mill/scalalib/backgroundwrapper/BackgroundWrapper.java deleted file mode 100644 index dbbcff8d34c..00000000000 --- a/scalalib/backgroundwrapper/src/mill/scalalib/backgroundwrapper/BackgroundWrapper.java +++ /dev/null @@ -1,38 +0,0 @@ -package mill.scalalib.backgroundwrapper; - -public class BackgroundWrapper { - public static void main(String[] args) throws Exception { - String watched = args[0]; - String tombstone = args[1]; - String expected = args[2]; - Thread watcher = new Thread(new Runnable() { - @Override - public void run() { - while (true) { - try { - Thread.sleep(50); - String token = java.nio.file.Files.readString(java.nio.file.Paths.get(watched)); - if (!token.equals(expected)) { - new java.io.File(tombstone).createNewFile(); - System.exit(0); - } - } catch (Exception e) { - try { - new java.io.File(tombstone).createNewFile(); - } catch (Exception e2) { - } - System.exit(0); - } - } - } - }); - watcher.setDaemon(true); - watcher.start(); - String realMain = args[3]; - String[] realArgs = new String[args.length - 4]; - for (int i = 0; i < args.length - 4; i++) { - realArgs[i] = args[i + 4]; - } - Class.forName(realMain).getMethod("main", String[].class).invoke(null, (Object) realArgs); - } -} diff --git a/scalalib/backgroundwrapper/src/mill/scalalib/backgroundwrapper/MillBackgroundWrapper.java b/scalalib/backgroundwrapper/src/mill/scalalib/backgroundwrapper/MillBackgroundWrapper.java new file mode 100644 index 00000000000..ebdd96f84d9 --- /dev/null +++ b/scalalib/backgroundwrapper/src/mill/scalalib/backgroundwrapper/MillBackgroundWrapper.java @@ -0,0 +1,60 @@ +package mill.scalalib.backgroundwrapper; + +import java.io.RandomAccessFile; +import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; + +public class MillBackgroundWrapper { + public static void main(String[] args) throws Exception { + Path procUuidPath = Paths.get(args[0]); + Path procLockfile = Paths.get(args[1]); + String procUuid = args[2]; + int lockDelay = Integer.parseInt(args[3]); + + Files.writeString(procUuidPath, procUuid, StandardOpenOption.CREATE); + + // Take a lock on `procLockfile` to ensure that only one + // `runBackground` process is running at any point in time. + RandomAccessFile raf = new RandomAccessFile(procLockfile.toFile(), "rw"); + FileChannel chan = raf.getChannel(); + if (chan.tryLock() == null) { + System.err.println("Waiting for runBackground lock to be available"); + chan.lock(); + } + + // For some reason even after the previous process exits things like sockets + // may still take time to free, so sleep for a configurable duration before proceeding + Thread.sleep(lockDelay); + + // Start the thread to watch for updates on the process marker file, + // so we can exit if it is deleted or replaced + long startTime = System.currentTimeMillis(); + Thread watcher = new Thread(() -> { + while (true) { + long delta = (System.currentTimeMillis() - startTime) / 1000; + try { + Thread.sleep(1); + String token = Files.readString(procUuidPath); + if (!token.equals(procUuid)) { + System.err.println("runBackground exiting after " + delta + "s"); + System.exit(0); + } + } catch (Exception e) { + System.err.println("runBackground exiting after " + delta + "s"); + System.exit(0); + } + } + }); + + watcher.setDaemon(true); + watcher.start(); + + // Actually start the Java main method we wanted to run in the background + String realMain = args[4]; + String[] realArgs = java.util.Arrays.copyOfRange(args, 5, args.length); + Class.forName(realMain).getMethod("main", String[].class).invoke(null, (Object) realArgs); + } +} diff --git a/scalalib/src/mill/scalalib/RunModule.scala b/scalalib/src/mill/scalalib/RunModule.scala index 4742a01a864..02b603985db 100644 --- a/scalalib/src/mill/scalalib/RunModule.scala +++ b/scalalib/src/mill/scalalib/RunModule.scala @@ -150,10 +150,16 @@ trait RunModule extends WithZincWorker { def runBackgroundTask(mainClass: Task[String], args: Task[Args] = Task.Anon(Args())): Task[Unit] = Task.Anon { - val (procId, procTombstone, token) = backgroundSetup(T.dest) + val (procUuidPath, procLockfile, procUuid) = backgroundSetup(T.dest) runner().run( - args = Seq(procId.toString, procTombstone.toString, token, mainClass()) ++ args().value, - mainClass = "mill.scalalib.backgroundwrapper.BackgroundWrapper", + args = Seq( + procUuidPath.toString, + procLockfile.toString, + procUuid, + runBackgroundRestartDelayMillis().toString, + mainClass() + ) ++ args().value, + mainClass = "mill.scalalib.backgroundwrapper.MillBackgroundWrapper", workingDir = forkWorkingDir(), extraRunClasspath = zincWorker().backgroundWrapperClasspath().map(_.path).toSeq, background = true, @@ -171,6 +177,7 @@ trait RunModule extends WithZincWorker { */ // TODO: make this a task, to be more dynamic def runBackgroundLogToConsole: Boolean = true + def runBackgroundRestartDelayMillis: T[Int] = 500 @deprecated("Binary compat shim, use `.runner().run(..., background=true)`", "Mill 0.12.0") protected def doRunBackground( @@ -184,14 +191,20 @@ trait RunModule extends WithZincWorker { runUseArgsFile: Boolean, backgroundOutputs: Option[Tuple2[ProcessOutput, ProcessOutput]] )(args: String*): Ctx => Result[Unit] = ctx => { - val (procId, procTombstone, token) = backgroundSetup(taskDest) + val (procUuidPath, procLockfile, procUuid) = backgroundSetup(taskDest) try Result.Success( Jvm.runSubprocessWithBackgroundOutputs( - "mill.scalalib.backgroundwrapper.BackgroundWrapper", + "mill.scalalib.backgroundwrapper.MillBackgroundWrapper", (runClasspath ++ zwBackgroundWrapperClasspath).map(_.path), forkArgs, forkEnv, - Seq(procId.toString, procTombstone.toString, token, finalMainClass) ++ args, + Seq( + procUuidPath.toString, + procLockfile.toString, + procUuid, + 500.toString, + finalMainClass + ) ++ args, workingDir = forkWorkingDir, backgroundOutputs, useCpPassingJar = runUseArgsFile @@ -204,38 +217,11 @@ trait RunModule extends WithZincWorker { } private[this] def backgroundSetup(dest: os.Path): (Path, Path, String) = { - val token = java.util.UUID.randomUUID().toString - val procId = dest / ".mill-background-process-id" - val procTombstone = dest / ".mill-background-process-tombstone" - // The background subprocesses poll the procId file, and kill themselves - // when the procId file is deleted. This deletion happens immediately before - // the body of these commands run, but we cannot be sure the subprocess has - // had time to notice. - // - // To make sure we wait for the previous subprocess to - // die, we make the subprocess write a tombstone file out when it kills - // itself due to procId being deleted, and we wait a short time on task-start - // to see if such a tombstone appears. If a tombstone appears, we can be sure - // the subprocess has killed itself, and can continue. If a tombstone doesn't - // appear in a short amount of time, we assume the subprocess exited or was - // killed via some other means, and continue anyway. - val start = System.currentTimeMillis() - while ({ - if (os.exists(procTombstone)) { - Thread.sleep(10) - os.remove.all(procTombstone) - true - } else { - Thread.sleep(10) - System.currentTimeMillis() - start < 100 - } - }) () - - os.write(procId, token) - os.write(procTombstone, token) - (procId, procTombstone, token) + val procUuid = java.util.UUID.randomUUID().toString + val procUuidPath = dest / ".mill-background-process-uuid" + val procLockfile = dest / ".mill-background-process-lock" + (procUuidPath, procLockfile, procUuid) } - } object RunModule { diff --git a/testkit/src/mill/testkit/IntegrationTester.scala b/testkit/src/mill/testkit/IntegrationTester.scala index c6c3488de22..50e3fdefc65 100644 --- a/testkit/src/mill/testkit/IntegrationTester.scala +++ b/testkit/src/mill/testkit/IntegrationTester.scala @@ -144,7 +144,7 @@ object IntegrationTester { if (clientServerMode) { // try to stop the server os.call( - cmd = (millExecutable, "shutdown"), + cmd = (millExecutable, "--no-build-lock", "shutdown"), cwd = workspacePath, stdin = os.Inherit, stdout = os.Inherit,