One attached client answering the sbt/terminalpropertiesquery request badly, or
slower than 5 seconds, could freeze the whole server for every client:
- The response handler dropped malformed responses (response.foreach(buffer.put))
instead of falling back to a default like every sibling handler, so the updater
thread waiting on the queue timed out with the properties reference still null.
- getProperties(block = true) waits while properties is null, but nothing
completes it after the updater's one-shot 5-second poll times out: waiters woke
from the notify, saw null, and waited again with no updater outstanding. The
1-second lastUpdate throttle also let a caller start waiting with no query in
flight at all, and the wait condition was checked outside the pending monitor,
losing wakeups that fired between the check and the wait.
- A response arriving after the poll timeout was delivered into a queue that was
never deregistered, so it neither set properties nor woke anyone.
The threads that block here include the command loop iterating channels and the
fast-track thread handling attach and cancel, so one bad or briefly-stalled
client wedged prompts, Ctrl-C, and command dispatch server-wide until that
client disconnected.
The properties response handler now falls back to a default like its siblings;
the updater expires its query on timeout, deregistering it (rescuing a response
that raced in) and completing properties with the empty default so waiters
always make progress; and both wait sites hold the pending monitor and gate on
the query in flight. waitForPending gets the same treatment, since it seeds
lazy vals whose initialization otherwise parks every thread touching them.
Regression test: a raw-protocol session that answers every server request with
a result of the wrong shape attaches interactively; a well-behaved batch client
must then still be served twice. Fails on develop with a three-minute timeout
(the server is frozen), passes with this change. A unit spec pins the expiry
semantics, including the late-response rescue.
Co-authored-by: Claude Fable 5 <[email protected]>
**Problem**
subproject build.sbt like a/build.sbt leaks to siblings.
**Solution**
Don't forward freshly computed common settings unless
the build.sbt is at root.
forked run used the project's baseDirectory as the working directory, while non-forked execution inherits sbt's own working directory — so toggling fork silently changed how relative paths resolved.
forked run (and forked console) now inherit sbt's working directory, consistent with non-forked execution and `sbtn` expectations.
Running reboot in the sbt shell dropped to the OS shell instead of rebooting.
The break was in teardown: Server.shutdown opened with
log.info, and during a client-initiated reboot the terminal in scope is that
client's already-closed virtual terminal, so the log write throws
ClosedChannelException through the terminal proxy. That aborted teardown
before the portfile was deleted and the server socket closed, and the
exception was swallowed by the shutdown hook (whose own error print goes to
the same dead terminal).
Server.shutdown now completes its state cleanup (portfile, tokenfile, running
flag, server socket) before logging, and CommandExchange.shutdown wraps each
channel shutdown and the server shutdown individually so one failing step
cannot skip the rest.
Fixes#9095
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
**Problem**
sbt 2.x currently constructs the test classloader using symlinks
to the JARs in CAS (content-addressable storage).
This causes issues because JVM apparently caches opened JAR files by path,
and since the symlink itself doesn't change it could end up
serving stale resource files.
**Solution**
We can workaround this issue by resolving the symlinks to the real path.
Problem:
When useIvy := false, ivyless publishing dropped the optional
scala_[scalaVersion]/ and sbt_[sbtVersion]/ path segments from Ivy-style
plugin publish patterns. This caused sbt 1 plugins published from sbt 2 builds
to land under non-plugin Ivy paths, so consumers looking under
scala_2.12/sbt_1.0/ could not resolve them.
Solution:
Read the plugin cross-version attributes from the CsrProject module and use
them when constructing ivyless Ivy-layout publish paths. Apply the same
substitution for local/file Ivy publishing and remote Ivy-style URL publishing,
while continuing to omit those optional segments when the attributes are absent.
Add scripted regressions for local and HTTP Ivy-style publishing of an sbt 1
plugin with useIvy := false.
A task that floods stdout could not be stopped promptly: cancelling it
(Ctrl+C from the thin client) stopped the task, but the server kept
draining the already-queued output to the client for several seconds,
because onCancellationRequest never discarded the pending backlog.
onCancellationRequest now sets a per-channel isCanceled flag and clears the
queued frames and buffered stdout (discardPending), and jsonRpcNotify
drops the cancelled task's systemOut/systemErr while the flag is set, so
output stops promptly.
Co-authored-by: Claude Fable 5 <[email protected]>
NetworkChannel batches writes to the client at most once per 20ms to
reduce terminal flicker and byte volume. forceFlush(), used to order
buffered stdout ahead of a control-plane message, called
flushExecutor.shutdownNow() -- the only place the executor was ever
shut down. Once it ran during active output it tore the executor
down (and, if a coalesced flush was pending, cancelled its future
without resetting flushFuture, leaving the slot stuck), so the 20ms
coalescing was gone for the rest of the connection: stdout was then
flushed per write via the inline fallback instead of batched. Output
still reaches the client, so the effect is extra flushes rather than
lost output, but shutting the shared executor down on the first
forceFlush is clearly unintended.
Extract the flush state machine into CoalescingFlusher. forceFlush now
drains immediately and leaves the timer live (a pending coalesced
drain harmlessly drains the remainder when it fires); the executor is
shut down once, at channel teardown, so it no longer leaks. doFlush
now holds a lock across both the drain and the publish so an inline
forceFlush and the timer's drain can't deliver two stdout batches out
of order.
Fixes#9415
Co-authored-by: Claude Fable 5 <[email protected]>
Any IOException while creating the boot io socket was wrapped in
ServerAlreadyBootingException and reported as "sbt thinks that server
is already booting" with a stack trace, and non-interactive
invocations exited with code 2. Permission or path-length problems
with XDG_RUNTIME_DIR or the temp directory and Windows named-pipe
access errors all hit this, blocking sbt entirely (#6777). Raw
IOExceptions from the constructor (socket directory creation) were
not caught at all and crashed startup.
getSocketOrExit now connects to the socket (BootServerSocketProbe,
shared with the test suite) to check for a live server before
believing the exception.
Co-authored-by: Claude Fable 5 <[email protected]>
This applies a range of optimisations local to ClassStamper that bring the time needed for refinedTestDigests down to acceptable levels (see #9108):
* Cache the digests of transitive dependencies (big impact)
* Avoid sorting of digest subsets that would later get sorted again (small)
* Pre-compute which Analysis instances are required for each class to avoid repeated scanning of the whole list (medium)
* Merge two loops on relations.externalDeps into one (small)
* Compute the set of extra digests outside loop (small)
* Track the digest closure of each test via a BitSet (big)
**Problem**
Global plugin loading doesn't work.
**Solution**
1. Use ModuleID from to supply the location of global-plugin module.
2. Update pluginData with the global plugin classpath.
- Use Builders to avoid building intermediate collections
- Use a mutable.Set for alreadySeen
- Use plain Set instead of SortedSet
- Sorting only needs to happen at the end of the computation in transitiveStamp
Job ids are long. The job id parser currently uses NotSpace to parse the ids, which fails with a NumberFormatException for short non-numeric values and OOMs SBT for long non-numeric values.
**Problem**
Test is based on https://github.com/sbt/sbt/issues/9345#issuecomment-4718229113 which gives us the following sequence:
1. Metals sends buildTarget/compile.
2. sbt publishes real non-empty diagnostics.
3. Metals sends buildTarget/scalaMainClasses.
4. During that request, sbt emits build/publishDiagnostics with diagnostics: [] and reset: true.
5. The following build/taskFinish still reports errors: 1.
Previously, errors for diagnostics reporting via bsp were collected from a live compilation run. In the sequence above, that is triggered by buildTarget/compile. Then, buildTarget/scalaMainClasses does not trigger such a run for the second time, it uses the cached compilation result. Therefore, the diagnostics is not populated.
**Solution**
The proposed fix modifies sendFailureReport to accept an optional CompileFailed object that contains the diagnostics even in case the actual compiler did not run because the cached result was used. If no problems were found for a file via default means, this CompileFailed object is queried to see if it has any information about problems in a given file.
In a project with multiple main classes, the "multiple main classes detected" warning is unwanted noise when running tests. This message is already suppressed for explicit run commands, now also suppress it for test.
Building on #9270 (which parallelized resolution in non-interactive runs by
narrowing the lm-coursier lock to only fire while coursier renders its
interactive progress bar), this makes `update` resolve in parallel under the
interactive super shell as well.
Count distinct non-checksum urls instead and drop the module claim.
Report the elapsed time of the current burst so the super shell renders
a live counter rather than a frozen "0s". Tests now encode coursier's
per-session call pattern.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
**Problem**
console / javaOptions is ignored.
**Solution**
1. This enables forked console even for sbt --server mode.
2. This includes console / javaOptions to the forkOptions.
**Problem**
We can get into a situation where CompileInputs2 become identical for Scala 3.x and 2.13, which breaks the recovery of Zinc analysis.
**Solution**
This includes Scala version into CompileInput2 to workaround this issue.
**Problem**
Platform suffix is absent in published artifacts.
#9118 partially solved this. It didn't touch publish-side naming though, so the published files still drop the suffix in three places.
**Solution**
This fixes
- ivyless publication names - CoursierArtifactsTasks.coursierPublicationsTask
- ivyless POM <artifactId> (the coordinate written inside the .pom) - PomGenerator.crossVersionDep
- Ivy backend (useIvy := true) - CrossVersion.substituteCross via IvyActions
Deduplicate executable jar package entries. In the issue, the duplicate was something like:
(resource_managed/.../sbt/sbt.autoplugins, "sbt/sbt.autoplugins")
(classes/.../sbt/sbt.autoplugins, "sbt/sbt.autoplugins")
This is because copyResources copied autoplugin metadata to the classes directory.
Solution: when composing the packageBinMappings, differentiate between artifacts that come from class directory and from outside of it. If there is a duplicate, prefer resource that comes from outside of it.
Fixes#9009.
dependencyMode := DependencyMode.PlusOne strips every internal
project dependency from the compile classpath, even direct ones --
LibA/compile fails with Not found: CoreClass despite
LibA.dependsOn(Core). Direct mode happens to work today, but only by
accident (filterByDirectDeps matches against allDependencies, which
includes projectDependencies -- the bug surface is one step removed).
Root cause: ClasspathImpl.filterByPlusOne resolves the
direct-dep key set from UpdateReport, which only contains externally
resolved (LM) modules. Internal project refs never appear there, so
resolvedDirectKeys is empty for internal entries; plusOneKeys is
likewise empty; the final filter strips every internal classpath
entry whose moduleIDStr identifies an internal project. Diagnosis on
the issue by eureka0928.
Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
Fixes#6886.
dependencyTree, dependencyBrowseTree, and inspect tree re-explore
the same node once per incoming edge. In a DAG with N levels and M
children per node the rendered output is O(M^N) -- the OP needed
>16 GB heap, #7360 has a 6 GB heap dump, and Friendseeker's analysis
on the issue showed the exponential re-traversal directly.
Fix: track a visited set across the renderer's recursion. The first
time a node is encountered it is rendered in full; on subsequent
visits the entry collapses to a one-line +- <id> (*) (ASCII) or a
<id> (*) leaf (JSON), matching Maven's dependency:tree (*)
convention. Cycle detection (separate parents set, (cycle) marker)
is unchanged.
Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
Fixes#2998
When test is run at the prompt in a multi-project build,
sbt aggregates <proj>/test across subprojects. Each subproject's
TestResultLogger emits its own pass/fail summary inline, but the
aggregation layer never restated which projects failed. The final
"sbt.TestsFailedException" line surfaced no project names or failing
test classes, so on CI users had to scrollback-grep through thousands
of log lines to find what broke.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
**Problem**
We need some tooling to debug caching issues.
**Solution**
This adds an exeprimental execution log support,
which shows input and output of cached tasks.