Skip to content

Commit f796397

Browse files
More careful stream handling when starting BSP server (#4980)
When Mill is started with `--bsp`, this makes sure that only the BSP server ever writes something to stdout (and ever tries to read something from stdin). This fixes weird output issues I've been seeing while debugging the BSP tests in #4626, where the BSP server output and my debugging output weren't printed anywhere.
1 parent d2552c4 commit f796397

File tree

2 files changed

+99
-73
lines changed

2 files changed

+99
-73
lines changed

integration/ide/bsp-server/src/BspServerTestUtil.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ object BspServerTestUtil {
8181
// "git",
8282
"diff",
8383
"-u",
84-
os.temp(jsonStr, suffix = s"${snapshotPath.last}-jsonStr"),
85-
os.temp(expectedJsonStr, suffix = s"${snapshotPath.last}-expectedJsonStr")
84+
os.temp(expectedJsonStr, suffix = s"${snapshotPath.last}-expectedJsonStr"),
85+
os.temp(jsonStr, suffix = s"${snapshotPath.last}-jsonStr")
8686
))
8787
s"""Error: value differs from snapshot in $snapshotPath
8888
|

runner/src/mill/runner/MillMain.scala

+97-71
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ import mill.define
99
import mill.define.WorkspaceRoot
1010
import mill.util.BuildInfo
1111
import mill.runner.meta.ScalaCompilerWorker
12-
import mill.internal.{Colors, PromptLogger}
12+
import mill.internal.{Colors, MultiStream, PromptLogger}
1313
import mill.server.Server
1414

15-
import java.io.{PipedInputStream, PrintStream}
15+
import java.io.{InputStream, PipedInputStream, PrintStream}
1616
import java.lang.reflect.InvocationTargetException
1717
import java.util.Locale
1818
import scala.collection.immutable
@@ -79,6 +79,40 @@ object MillMain {
7979

8080
lazy val maybeScalaCompilerWorker = ScalaCompilerWorker.bootstrapWorker()
8181

82+
private def withStreams[T](
83+
bspMode: Boolean,
84+
streams: SystemStreams
85+
)(thunk: SystemStreams => T): T =
86+
if (bspMode) {
87+
// In BSP mode, don't let anything other than the BSP server write to stdout and read from stdin
88+
89+
val outFileStream = os.write.outputStream(
90+
WorkspaceRoot.workspaceRoot / OutFiles.out / "mill-bsp/out.log",
91+
createFolders = true
92+
)
93+
val errFileStream = os.write.outputStream(
94+
WorkspaceRoot.workspaceRoot / OutFiles.out / "mill-bsp/err.log",
95+
createFolders = true
96+
)
97+
98+
try {
99+
val streams0 = new SystemStreams(
100+
out = new MultiStream(streams.err, outFileStream),
101+
err = new MultiStream(streams.err, errFileStream),
102+
in = InputStream.nullInputStream()
103+
)
104+
mill.define.SystemStreams.withStreams(streams0) {
105+
thunk(streams0)
106+
}
107+
} finally {
108+
errFileStream.close()
109+
outFileStream.close()
110+
}
111+
} else
112+
mill.define.SystemStreams.withStreams(streams) {
113+
thunk(streams)
114+
}
115+
82116
def main0(
83117
args: Array[String],
84118
stateCache: RunnerState,
@@ -92,10 +126,17 @@ object MillMain {
92126
serverDir: os.Path
93127
): (Boolean, RunnerState) = {
94128

95-
val streams = streams0
96-
mill.define.SystemStreams.withStreams(streams) {
97-
os.SubProcess.env.withValue(env) {
98-
MillCliConfigParser.parse(args) match {
129+
os.SubProcess.env.withValue(env) {
130+
val parserResult = MillCliConfigParser.parse(args)
131+
// Detect when we're running in BSP mode as early as possible,
132+
// and ensure we don't log to the default stdout or use the default
133+
// stdin, meant to be used for BSP JSONRPC communication, where those
134+
// logs would be lost.
135+
// This is especially helpful if anything unexpectedly goes wrong
136+
// early on, when developing on Mill or debugging things for example.
137+
val bspMode = parserResult.toOption.exists(_.bsp.value)
138+
withStreams(bspMode, streams0) { streams =>
139+
parserResult match {
99140
// Cannot parse args
100141
case Result.Failure(msg) =>
101142
streams.err.println(msg)
@@ -270,7 +311,7 @@ object MillMain {
270311
requestedMetaLevel = config.metaLevel,
271312
config.allowPositional.value,
272313
systemExit = systemExit,
273-
streams0 = streams0,
314+
streams0 = streams,
274315
selectiveExecution = config.watch.value,
275316
scalaCompilerWorker = scalaCompilerWorker,
276317
offline = config.offline.value
@@ -283,13 +324,14 @@ object MillMain {
283324
if (bspMode) {
284325

285326
runBspSession(
327+
streams0,
286328
streams,
287-
(prevRunnerState, splitStreams) =>
329+
prevRunnerState =>
288330
runMillBootstrap(
289331
false,
290332
prevRunnerState,
291333
Seq("version"),
292-
splitStreams
334+
streams
293335
).result
294336
)
295337

@@ -335,77 +377,61 @@ object MillMain {
335377
}
336378
}
337379

380+
/**
381+
* Runs the BSP server, and exits when the server is done
382+
*
383+
* @param bspStreams Streams to use for BSP JSONRPC communication with the BSP client
384+
* @param logStreams Streams to use for logging
385+
* @param runMillBootstrap Load the Mill build, building / updating meta-builds along the way
386+
*/
338387
def runBspSession(
339-
streams0: SystemStreams,
340-
runMillBootstrap: (Option[RunnerState], SystemStreams) => RunnerState
388+
bspStreams: SystemStreams,
389+
logStreams: SystemStreams,
390+
runMillBootstrap: Option[RunnerState] => RunnerState
341391
): Result[BspServerResult] = {
342-
val splitOut = new mill.internal.MultiStream(
343-
streams0.out,
344-
os.write.outputStream(
345-
WorkspaceRoot.workspaceRoot / OutFiles.out / "mill-bsp/out.log",
346-
createFolders = true
347-
)
348-
)
349-
val splitErr = new mill.internal.MultiStream(
350-
streams0.out,
351-
os.write.outputStream(
352-
WorkspaceRoot.workspaceRoot / OutFiles.out / "mill-bsp/err.log",
353-
createFolders = true
392+
logStreams.err.println("Trying to load BSP server...")
393+
394+
val wsRoot = WorkspaceRoot.workspaceRoot
395+
val logDir = wsRoot / OutFiles.out / "mill-bsp"
396+
val bspServerHandleRes = {
397+
os.makeDir.all(logDir)
398+
mill.bsp.worker.BspWorkerImpl.startBspServer(
399+
define.WorkspaceRoot.workspaceRoot,
400+
bspStreams,
401+
logDir,
402+
true
354403
)
355-
)
356-
val splitStreams = new SystemStreams(splitOut, splitErr, streams0.in)
357-
358-
mill.define.SystemStreams.withStreams(splitStreams) {
359-
try {
360-
splitStreams.err.println("Trying to load BSP server...")
361-
362-
val wsRoot = WorkspaceRoot.workspaceRoot
363-
val logDir = wsRoot / OutFiles.out / "mill-bsp"
364-
val bspServerHandleRes = {
365-
os.makeDir.all(logDir)
366-
mill.bsp.worker.BspWorkerImpl.startBspServer(
367-
define.WorkspaceRoot.workspaceRoot,
368-
splitStreams,
369-
logDir,
370-
true
371-
)
372-
}
373-
374-
streams0.err.println("BSP server started")
375-
376-
val runSessionRes = bspServerHandleRes.flatMap { bspServerHandle =>
377-
try {
378-
var repeatForBsp = true
379-
var bspRes: Option[Result[BspServerResult]] = None
380-
var prevRunnerState: Option[RunnerState] = None
381-
while (repeatForBsp) {
382-
repeatForBsp = false
383-
val runnerState = runMillBootstrap(prevRunnerState, splitStreams)
384-
val runSessionRes =
385-
bspServerHandle.runSession(runnerState.frames.flatMap(_.evaluator))
386-
prevRunnerState = Some(runnerState)
387-
repeatForBsp = runSessionRes == BspServerResult.ReloadWorkspace
388-
bspRes = Some(runSessionRes)
389-
streams0.err.println(s"BSP session returned with $runSessionRes")
390-
}
404+
}
391405

392-
// should make the lsp4j-managed BSP server exit
393-
streams0.in.close()
406+
logStreams.err.println("BSP server started")
394407

395-
bspRes.get
396-
} finally bspServerHandle.close()
408+
val runSessionRes = bspServerHandleRes.flatMap { bspServerHandle =>
409+
try {
410+
var repeatForBsp = true
411+
var bspRes: Option[Result[BspServerResult]] = None
412+
var prevRunnerState: Option[RunnerState] = None
413+
while (repeatForBsp) {
414+
repeatForBsp = false
415+
val runnerState = runMillBootstrap(prevRunnerState)
416+
val runSessionRes =
417+
bspServerHandle.runSession(runnerState.frames.flatMap(_.evaluator))
418+
prevRunnerState = Some(runnerState)
419+
repeatForBsp = runSessionRes == BspServerResult.ReloadWorkspace
420+
bspRes = Some(runSessionRes)
421+
logStreams.err.println(s"BSP session returned with $runSessionRes")
397422
}
398423

399-
splitErr.println(
400-
s"Exiting BSP runner loop. Stopping BSP server. Last result: $runSessionRes"
401-
)
402-
runSessionRes
403-
} finally {
424+
// should make the lsp4j-managed BSP server exit
425+
bspStreams.in.close()
404426

405-
splitErr.close()
406-
splitOut.close()
407-
}
427+
bspRes.get
428+
} finally bspServerHandle.close()
408429
}
430+
431+
logStreams.err.println(
432+
s"Exiting BSP runner loop. Stopping BSP server. Last result: $runSessionRes"
433+
)
434+
runSessionRes
409435
}
410436

411437
private[runner] def parseThreadCount(

0 commit comments

Comments
 (0)