Skip to content

Conversation

@unlsycn
Copy link
Contributor

@unlsycn unlsycn commented Aug 29, 2025

After recently bumping Scala-CLI, I noticed that the race condition in the local repository setup still persists even after merging #3693. I then identified a critical issue: the withLock mechanism does not work at all on Linux.

According to https://bugs.openjdk.org/browse/JDK-7095452, the root cause is that a .lock file cannot be observed by other processes:

On Solaris/Linux if DELETE_ON_CLOSE is specified, the appropriate file is unlinked from the directory when opened and therefore appears inaccessible via path.

In this case, each Scala-CLI process considers itself holding the lock, leading to a race condition.

This issue is difficult to fully capture with a single test due to its concurrent nature, but adding a Thread.sleep before extractZip and run multiple Scala-CLI instances in parallel can reproduce the issue reliably nearly 100% of the time.

On Solaris/Linux if DELETE_ON_CLOSE is specified, the appropriate file is
unlinked from the directory when opened and therefore appears inaccessible via
path. In this case, each Scala-CLI process considers itself holding the lock,
leading to a race condition.

Ref: https://bugs.openjdk.org/browse/JDK-7095452

Signed-off-by: unlsycn <[email protected]>
@Gedochao
Copy link
Contributor

This issue is difficult to fully capture with a single test due to its concurrent nature, but adding a Thread.sleep before extractZip and run multiple Scala-CLI instances in parallel can reproduce the issue reliably nearly 100% of the time.

We have tests for running multiple Scala CLI instances in parallel right here:
https://github.com/VirtusLab/scala-cli/blob/main/modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala#L2320-L2361

Can you try adding a similar test for your case? Optimally, I'd like to be able to track if this works correctly on all platforms.

@unlsycn
Copy link
Contributor Author

unlsycn commented Aug 31, 2025

This issue is difficult to fully capture with a single test due to its concurrent nature, but adding a Thread.sleep before extractZip and run multiple Scala-CLI instances in parallel can reproduce the issue reliably nearly 100% of the time.

We have tests for running multiple Scala CLI instances in parallel right here: https://github.com/VirtusLab/scala-cli/blob/main/modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala#L2320-L2361

Can you try adding a similar test for your case? Optimally, I'd like to be able to track if this works correctly on all platforms.

Sure. Could you provide some guidance on accessing localRepoDir in integration tests? Since the setup only occurs in environments without cached local repo, I need to remove the local repo for this test. Alternatively, is there a way to run this test in a sandboxed environment to ensure the setup process executes?

@Gedochao
Copy link
Contributor

Gedochao commented Sep 1, 2025

You can print all relevant locations (including the local repository) with the following command:

scala-cli directories --power

Where the output should look like (example for a Mac):

Local repository: ~/Library/Caches/ScalaCli/local-repo
Completions: ~/Library/Application Support/ScalaCli/completions
Virtual projects: ~/Library/Caches/ScalaCli/virtual-projects
BSP sockets: ~/Library/Caches/ScalaCli/bsp-sockets
Bloop daemon directory: ~/Library/Caches/ScalaCli/bloop/daemon
Secrets directory: ~/Library/Application Support/ScalaCli/secret

The definition in-code can be found here:

lazy val localRepoDir: os.Path =
os.Path(projDirs.cacheDir, Os.pwd) / "local-repo"

@unlsycn
Copy link
Contributor Author

unlsycn commented Sep 1, 2025

You can print all relevant locations (including the local repository) with the following command:

scala-cli directories --power

Where the output should look like (example for a Mac):

Local repository: ~/Library/Caches/ScalaCli/local-repo
Completions: ~/Library/Application Support/ScalaCli/completions
Virtual projects: ~/Library/Caches/ScalaCli/virtual-projects
BSP sockets: ~/Library/Caches/ScalaCli/bsp-sockets
Bloop daemon directory: ~/Library/Caches/ScalaCli/bloop/daemon
Secrets directory: ~/Library/Application Support/ScalaCli/secret

The definition in-code can be found here:

lazy val localRepoDir: os.Path =
os.Path(projDirs.cacheDir, Os.pwd) / "local-repo"

Thanks for your help! I've added a test that fails before this patch and passes after it.

Copy link
Contributor

@Gedochao Gedochao left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks for the contribution!

@Gedochao Gedochao merged commit 420ca08 into VirtusLab:main Sep 2, 2025
109 of 110 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants