Skip to content

Commit 420ca08

Browse files
authored
Fix completely broken lock during setuping local repo on Linux (#3846)
* Fix completely broken lock during setuping local repo on Linux 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]> * Add test for parallel local repo setup Signed-off-by: unlsycn <[email protected]> --------- Signed-off-by: unlsycn <[email protected]>
1 parent 9dc0a59 commit 420ca08

File tree

2 files changed

+34
-22
lines changed

2 files changed

+34
-22
lines changed

modules/build/src/main/scala/scala/build/LocalRepo.scala

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,7 @@ object LocalRepo {
7777
// potential race conditions between initial check and lock acquisition
7878
if !os.exists(repoDir) then
7979
val tmpRepoDir = repoDir / os.up / s".$version.tmp"
80-
try os.remove.all(tmpRepoDir)
81-
catch {
82-
case t: Throwable =>
83-
logger.message(s"Error removing $tmpRepoDir: ${t.getMessage}")
84-
}
80+
os.remove.all(tmpRepoDir)
8581
using(archiveUrl.openStream()) { is =>
8682
using(WrappedZipInputStream.create(new BufferedInputStream(is))) { zis =>
8783
extractZip(zis, tmpRepoDir)
@@ -101,30 +97,21 @@ object LocalRepo {
10197
val lockFile = dir.resolve(s".lock-$id");
10298
Util.createDirectories(lockFile.getParent)
10399
var channel: FileChannel = null
100+
var lock: FileLock = null
104101

105102
try {
106103
channel = FileChannel.open(
107104
lockFile,
108105
StandardOpenOption.CREATE,
109-
StandardOpenOption.WRITE,
110-
StandardOpenOption.DELETE_ON_CLOSE
106+
StandardOpenOption.WRITE
111107
)
112-
113-
var lock: FileLock = null
114-
try {
115-
lock = channel.lock()
116-
117-
try f
118-
finally {
119-
lock.release()
120-
lock = null
121-
channel.close()
122-
channel = null
123-
}
124-
}
125-
finally if (lock != null) lock.release()
108+
lock = channel.lock()
109+
f
110+
}
111+
finally {
112+
if (lock != null) lock.release()
113+
if (channel != null) channel.close()
126114
}
127-
finally if (channel != null) channel.close()
128115
}
129116

130117
}

modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2413,4 +2413,29 @@ abstract class RunTestDefinitions
24132413
expect(!res.err.trim().contains(legacyRunnerWarning))
24142414
}
24152415
}
2416+
2417+
for (parallelInstancesCount <- Seq(2, 5, 10))
2418+
test(
2419+
s"run $parallelInstancesCount instances in parallel without local repo"
2420+
) {
2421+
TestInputs.empty.fromRoot { root =>
2422+
val localRepoPath =
2423+
os.Path(os.proc(
2424+
TestUtil.cli,
2425+
"directories",
2426+
"--power"
2427+
).call().out.text().linesIterator.find(_.startsWith("Local repository:")).getOrElse(
2428+
throw new RuntimeException("Local repository line not found in directories output")
2429+
).split(":", 2).last.trim())
2430+
os.remove.all(localRepoPath)
2431+
2432+
val processes =
2433+
(0 until parallelInstancesCount).map { _ =>
2434+
os.proc(TestUtil.cli, "--version")
2435+
.spawn(cwd = root)
2436+
}.zipWithIndex
2437+
processes.foreach { case (p, _) => p.waitFor() }
2438+
processes.foreach { case (p, _) => expect(p.exitCode == 0) }
2439+
}
2440+
}
24162441
}

0 commit comments

Comments
 (0)