@@ -9,12 +9,14 @@ import mill.constants.InputPumper
99import mill .constants .ProxyStream
1010
1111import java .io .*
12- import java .net .{InetAddress , Socket }
12+ import java .net .{InetAddress , Socket , ServerSocket }
1313import scala .jdk .CollectionConverters .*
1414import scala .util .Try
1515import scala .util .Using
1616import mill .constants .OutFiles
1717
18+ import java .util .concurrent .atomic .AtomicBoolean
19+
1820/**
1921 * Models a long-lived server that receives requests from a client and calls a [[main0 ]]
2022 * method to run the commands in-process. Provides the command args, env variables,
@@ -52,7 +54,7 @@ abstract class Server[T](
5254 val initialSystemProperties = sys.props.toMap
5355
5456 try {
55- Server .tryLockBlock(locks.serverLock) {
57+ Server .tryLockBlock(locks.serverLock) { locked =>
5658 serverLog(" server file locked" )
5759 Server .watchProcessIdFile(
5860 serverDir / ServerFiles .processId,
@@ -64,27 +66,42 @@ abstract class Server[T](
6466 }
6567 )
6668 val serverSocket = new java.net.ServerSocket (0 , 0 , InetAddress .getByName(null ))
67- os.write.over(serverDir / ServerFiles .socketPort, serverSocket.getLocalPort.toString)
68- serverLog(" listening on port " + serverSocket.getLocalPort)
69- while (
70- running && {
71- interruptWithTimeout(() => serverSocket.close(), () => serverSocket.accept()) match {
72- case None => false
73- case Some (sock) =>
74- serverLog(" handling run" )
75- new Thread (
76- () =>
77- try handleRun(sock, initialSystemProperties)
78- catch {
79- case e : Throwable =>
80- serverLog(e.toString + " \n " + e.getStackTrace.mkString(" \n " ))
81- } finally sock.close();,
82- " HandleRunThread"
83- ).start()
84- true
85- }
69+ try {
70+ os.write.over(serverDir / ServerFiles .socketPort, serverSocket.getLocalPort.toString)
71+ serverLog(" listening on port " + serverSocket.getLocalPort)
72+
73+ def systemExit (exitCode : Int ) = {
74+ // Explicitly close serverSocket before exiting otherwise it can keep the
75+ // server alive 500-1000ms before letting it exit properly
76+ serverSocket.close()
77+ // Explicitly release process lock to indicate this serverwill not be
78+ // taking any more requests, and a new server should be spawned if necessary.
79+ // Otherwise launchers may continue trying to connect to the server and
80+ // failing since the socket is closed.
81+ locked.release()
82+ sys.exit(exitCode)
8683 }
87- ) ()
84+
85+ while (
86+ running && {
87+ interruptWithTimeout(() => serverSocket.close(), () => serverSocket.accept()) match {
88+ case None => false
89+ case Some (sock) =>
90+ serverLog(" handling run" )
91+ new Thread (
92+ () =>
93+ try handleRun(systemExit, sock, initialSystemProperties)
94+ catch {
95+ case e : Throwable =>
96+ serverLog(e.toString + " \n " + e.getStackTrace.mkString(" \n " ))
97+ } finally sock.close();,
98+ " HandleRunThread"
99+ ).start()
100+ true
101+ }
102+ }
103+ ) ()
104+ } finally serverSocket.close()
88105 serverLog(" server loop ended" )
89106 }.getOrElse(throw new Exception (" Mill server process already present" ))
90107 } catch {
@@ -142,9 +159,19 @@ abstract class Server[T](
142159 }
143160 }
144161
145- def handleRun (clientSocket : Socket , initialSystemProperties : Map [String , String ]): Unit = {
146-
162+ def handleRun (
163+ systemExit : Int => Nothing ,
164+ clientSocket : Socket ,
165+ initialSystemProperties : Map [String , String ]
166+ ): Unit = {
147167 val currentOutErr = clientSocket.getOutputStream
168+ val writtenExitCode = AtomicBoolean ()
169+ def writeExitCode (code : Int ) = {
170+ if (! writtenExitCode.getAndSet(true )) {
171+ ProxyStream .sendEnd(currentOutErr, code)
172+ }
173+ }
174+
148175 var clientDisappeared = false
149176 // We cannot use Socket#{isConnected, isClosed, isBound} because none of these
150177 // detect client-side connection closing, so instead we send a no-op heartbeat
@@ -181,6 +208,7 @@ abstract class Server[T](
181208
182209 val millVersionChanged = lastMillVersion.exists(_ != clientMillVersion)
183210 val javaVersionChanged = lastJavaVersion.exists(_ != clientJavaVersion)
211+
184212 if (millVersionChanged || javaVersionChanged) {
185213 Server .withOutLock(
186214 noBuildLock = false ,
@@ -204,11 +232,9 @@ abstract class Server[T](
204232 s " Java version changed ( $lastJavaVersion -> $clientJavaVersion), re-starting server "
205233 )
206234 }
207- os.write(
208- serverDir / ServerFiles .exitCode,
209- ClientUtil .ExitServerCodeWhenVersionMismatch ().toString.getBytes()
210- )
211- System .exit(ClientUtil .ExitServerCodeWhenVersionMismatch ())
235+
236+ writeExitCode(ClientUtil .ExitServerCodeWhenVersionMismatch ())
237+ systemExit(ClientUtil .ExitServerCodeWhenVersionMismatch ())
212238 }
213239 }
214240 lastMillVersion = Some (clientMillVersion)
@@ -228,15 +254,15 @@ abstract class Server[T](
228254 Map (),
229255 initialSystemProperties,
230256 systemExit = exitCode => {
231- os.write.over(serverDir / ServerFiles . exitCode, exitCode.toString )
232- sys.exit (exitCode)
257+ writeExitCode( exitCode)
258+ systemExit (exitCode)
233259 }
234260 )
235261
236262 stateCache = newStateCache
237- val exitCode = if (result) " 0 " else " 1 "
263+ val exitCode = if (result) 0 else 1
238264 serverLog(" exitCode " + exitCode)
239- os.write.over(serverDir / ServerFiles .exitCode, exitCode)
265+ writeExitCode( exitCode)
240266 } finally {
241267 done = true
242268 idle = true
@@ -271,7 +297,10 @@ abstract class Server[T](
271297 System .err.flush()
272298
273299 } finally {
274- if (! clientDisappeared) ProxyStream .sendEnd(currentOutErr) // Send a termination
300+ try writeExitCode(1 ) // Send a termination if it has not already happened
301+ catch {
302+ case e : Throwable => /* donothing*/
303+ }
275304 }
276305 }
277306
@@ -329,12 +358,12 @@ object Server {
329358 processIdThread.start()
330359 }
331360
332- def tryLockBlock [T ](lock : Lock )(t : => T ): Option [T ] = {
361+ def tryLockBlock [T ](lock : Lock )(block : mill.client.lock. TryLocked => T ): Option [T ] = {
333362 lock.tryLock() match {
334363 case null => None
335364 case l =>
336365 if (l.isLocked) {
337- try Some (t )
366+ try Some (block(l) )
338367 finally l.release()
339368 } else {
340369 None
0 commit comments