Skip to content

Commit e3f7ab5

Browse files
committed
bugfix: Only delete directories if nothing is running
Previously, we would sometimes delete directories when another compilation was running concurrently. Now, we only delete at the end if no other compilation is running.
1 parent b22afcd commit e3f7ab5

File tree

5 files changed

+130
-105
lines changed

5 files changed

+130
-105
lines changed

backend/src/main/scala/bloop/Compiler.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,6 @@ object Compiler {
435435
// new compilation artifacts coming from scalac, which we will not
436436
// have in this case and it's going to remain empty.
437437
val firstTask = Task { BloopPaths.delete(AbsolutePath(newClassesDir)) }
438-
439438
// Then we copy previous best effort artifacts to a clientDir from the
440439
// cached compilation result.
441440
// This is useful if e.g. the client restarted after the last compilation

frontend/src/main/scala/bloop/engine/caches/LastSuccessfulResult.scala

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import bloop.data.Project
99
import bloop.io.AbsolutePath
1010
import bloop.task.Task
1111

12-
import monix.execution.atomic.AtomicInt
1312
import xsbti.compile.CompileAnalysis
1413
import xsbti.compile.MiniSetup
1514
import xsbti.compile.PreviousResult
@@ -18,7 +17,6 @@ case class LastSuccessfulResult(
1817
noClassPathAndSources: Boolean,
1918
previous: PreviousResult,
2019
classesDir: AbsolutePath,
21-
counterForClassesDir: AtomicInt,
2220
populatingProducts: Task[Unit]
2321
) {
2422
def isEmpty: Boolean = {
@@ -43,7 +41,6 @@ object LastSuccessfulResult {
4341
noClassPathAndSources = true,
4442
EmptyPreviousResult,
4543
emptyClassesDir,
46-
AtomicInt(0),
4744
Task.now(())
4845
)
4946
}
@@ -57,7 +54,6 @@ object LastSuccessfulResult {
5754
noClassPathAndSources = inputs.sources.size == 0 && inputs.classpath.size == 0,
5855
products.resultForFutureCompilationRuns,
5956
AbsolutePath(products.newClassesDir),
60-
AtomicInt(0),
6157
backgroundIO
6258
)
6359
}

frontend/src/main/scala/bloop/engine/tasks/CompileTask.scala

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
package bloop.engine.tasks
22

3+
import java.nio.file.Path
34
import java.util.Optional
5+
import java.util.concurrent.ConcurrentLinkedQueue
6+
import java.util.concurrent.atomic.AtomicInteger
7+
48
import scala.collection.mutable
59
import scala.concurrent.Promise
10+
611
import bloop.CompileBackgroundTasks
712
import bloop.CompileInputs
813
import bloop.CompileOutPaths
@@ -35,17 +40,18 @@ import bloop.reporter.ReporterInputs
3540
import bloop.task.Task
3641
import bloop.tracing.BraveTracer
3742
import bloop.util.BestEffortUtils.BestEffortProducts
43+
3844
import monix.execution.CancelableFuture
3945
import monix.reactive.MulticastStrategy
4046
import monix.reactive.Observable
4147
import xsbti.compile.CompileAnalysis
4248
import xsbti.compile.MiniSetup
4349
import xsbti.compile.PreviousResult
4450

45-
import java.nio.file.Path
46-
4751
object CompileTask {
4852
private implicit val logContext: DebugFilter = DebugFilter.Compilation
53+
private val compilationCounter = new AtomicInteger(0)
54+
private val cleanUpTasks = new ConcurrentLinkedQueue[AbsolutePath]()
4955

5056
def compile[UseSiteLogger <: Logger](
5157
state: State,
@@ -57,6 +63,8 @@ object CompileTask {
5763
store: CompileClientStore,
5864
rawLogger: UseSiteLogger
5965
): Task[State] = Task.defer {
66+
compilationCounter.incrementAndGet()
67+
6068
import bloop.data.ClientInfo
6169
import bloop.internal.build.BuildInfo
6270
val originUri = state.build.origin
@@ -337,7 +345,7 @@ object CompileTask {
337345
}
338346

339347
val client = state.client
340-
CompileGraph
348+
val compilation = CompileGraph
341349
.traverse(dag, client, store, bestEffortAllowed, setup, compile, tracer)
342350
.flatMap { pdag =>
343351
val partialResults = Dag.dfs(pdag, mode = Dag.PreOrder)
@@ -408,7 +416,12 @@ object CompileTask {
408416
.doOnFinish(_ => Task(rootTracer.terminate()))
409417
}
410418
}
419+
compilation
420+
.doOnFinish(_ =>
421+
cleanupIfNoCompilationRunning(compilationCounter.decrementAndGet(), tracer, rawLogger)
422+
)
411423
}
424+
412425
}
413426

414427
case class ConfiguredCompilation(scalacOptions: List[String])
@@ -470,13 +483,14 @@ object CompileTask {
470483
for {
471484
_ <- previousSuccessful.populatingProducts
472485
_ <- populateNewProductsTask
473-
_ <- cleanUpPreviousResult(
486+
toDelete = previousDirectoriesToCleanup(
474487
previousSuccessful,
475488
previousResult,
476489
compilerResult,
477490
tracer,
478491
logger
479492
)
493+
_ = toDelete.foreach(cleanUpTasks.add)
480494
} yield ()
481495
}
482496

@@ -514,15 +528,14 @@ object CompileTask {
514528
* superseeded by the new classes directory generated during a successful
515529
* compile.
516530
*/
517-
private def cleanUpPreviousResult(
531+
private def previousDirectoriesToCleanup(
518532
previousSuccessful: LastSuccessfulResult,
519533
previousResult: Option[Compiler.Result],
520534
compilerResult: Compiler.Result,
521535
tracer: BraveTracer,
522536
logger: Logger
523-
): Task[Unit] = tracer.trace("clean up previous result") { implicit tracer =>
537+
): List[AbsolutePath] = tracer.trace("clean up previous result") { _ =>
524538
val previousClassesDir = previousSuccessful.classesDir
525-
val currentlyUsedCounter = previousSuccessful.counterForClassesDir.decrementAndGet(1)
526539

527540
val previousReadOnlyToDelete = compilerResult match {
528541
case Success(_, products, _, _, isNoOp, _, _) =>
@@ -532,11 +545,6 @@ object CompileTask {
532545
} else if (CompileOutPaths.hasEmptyClassesDir(previousClassesDir)) {
533546
logger.debug(s"Skipping delete of empty classes dir ${previousClassesDir}")
534547
None
535-
} else if (currentlyUsedCounter != 0) {
536-
logger.debug(
537-
s"Skipping delete of $previousClassesDir, counter is $currentlyUsedCounter"
538-
)
539-
None
540548
} else {
541549
val newClassesDir = products.newClassesDir
542550
logger.debug(s"Scheduling to delete ${previousClassesDir} superseded by $newClassesDir")
@@ -566,28 +574,34 @@ object CompileTask {
566574
case _ => None
567575
}
568576

569-
def deleteOrphanDir(orphanDir: Option[AbsolutePath])(implicit
570-
tracer: BraveTracer
571-
): Task[Unit] = {
572-
tracer.trace("delete orphan dir") { _ =>
573-
orphanDir match {
574-
case None => Task.unit
575-
case Some(classesDir) =>
577+
List(
578+
previousReadOnlyToDelete,
579+
previousBestEffortToDelete
580+
).flatten
581+
582+
}
583+
584+
def cleanupIfNoCompilationRunning(
585+
counter: Int,
586+
tracer: BraveTracer,
587+
logger: Logger
588+
): Task[Unit] = {
589+
if (counter == 0) {
590+
tracer.trace("delete orphan directories") { _ =>
591+
val allDeleteDirectories = new mutable.ListBuffer[AbsolutePath]()
592+
while (cleanUpTasks.size() > 0) allDeleteDirectories += cleanUpTasks.poll()
593+
val allDeleteTasks = allDeleteDirectories
594+
.result()
595+
.map(classesDir => {
576596
Task.eval {
577-
logger.debug(s"Deleting contents of orphan dir $classesDir")
597+
logger.debug(s"Deleting contents of orphan dir $classesDir : ${classesDir.exists}")
578598
BloopPaths.delete(classesDir)
579599
}.asyncBoundary
580-
}
600+
})
601+
Task.gatherUnordered(allDeleteTasks).map(_ => ())
581602
}
603+
} else {
604+
Task.unit
582605
}
583-
584-
Task
585-
.gatherUnordered(
586-
List(
587-
deleteOrphanDir(previousReadOnlyToDelete),
588-
deleteOrphanDir(previousBestEffortToDelete)
589-
)
590-
)
591-
.map(_ => ())
592606
}
593607
}

frontend/src/main/scala/bloop/engine/tasks/compilation/CompileGatekeeper.scala

Lines changed: 12 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
package bloop.engine.tasks.compilation
22

3-
import bloop.{Compiler, UniqueCompileInputs}
4-
import bloop.data.{ClientInfo, Project}
3+
import java.util.concurrent.ConcurrentHashMap
4+
5+
import bloop.Compiler
6+
import bloop.UniqueCompileInputs
7+
import bloop.data.ClientInfo
8+
import bloop.data.Project
59
import bloop.engine.Dag
610
import bloop.engine.caches.LastSuccessfulResult
7-
import bloop.io.AbsolutePath
8-
import bloop.logging.{DebugFilter, Logger, LoggerAction}
11+
import bloop.logging.DebugFilter
12+
import bloop.logging.Logger
13+
import bloop.logging.LoggerAction
914
import bloop.reporter.ReporterAction
1015
import bloop.task.Task
1116
import bloop.tracing.BraveTracer
12-
import monix.execution.atomic.{AtomicBoolean, AtomicInt}
13-
import monix.reactive.Observable
1417

15-
import java.util.concurrent.ConcurrentHashMap
18+
import monix.execution.atomic.AtomicBoolean
19+
import monix.reactive.Observable
1620

1721
object CompileGatekeeper {
1822
private implicit val filter: DebugFilter = DebugFilter.Compilation
@@ -28,7 +32,6 @@ object CompileGatekeeper {
2832

2933
/* -------------------------------------------------------------------------------------------- */
3034

31-
private val currentlyUsedClassesDirs = new ConcurrentHashMap[AbsolutePath, AtomicInt]()
3235
private val runningCompilations = new ConcurrentHashMap[UniqueCompileInputs, RunningCompilation]()
3336
private val lastSuccessfulResults = new ConcurrentHashMap[ProjectId, LastSuccessfulResult]()
3437

@@ -62,39 +65,6 @@ object CompileGatekeeper {
6265
"Found matching compilation",
6366
("uniqueInputs", bundle.uniqueInputs.toString)
6467
) { tracer =>
65-
val usedClassesDir = running.usedLastSuccessful.classesDir
66-
val usedClassesDirCounter = running.usedLastSuccessful.counterForClassesDir
67-
68-
usedClassesDirCounter.getAndTransform { count =>
69-
if (count == 0) {
70-
tracer.trace(
71-
"Aborting deduplication",
72-
("uniqueInputs", bundle.uniqueInputs.toString)
73-
) { tracer =>
74-
logger.debug(
75-
s"Abort deduplication, dir is scheduled to be deleted in background:${bundle.uniqueInputs}"
76-
)
77-
// Abort deduplication, dir is scheduled to be deleted in background
78-
deduplicate = false
79-
// Remove from map of used classes dirs in case it hasn't already been
80-
currentlyUsedClassesDirs.remove(usedClassesDir, usedClassesDirCounter)
81-
// Return previous count, this counter will soon be deallocated
82-
count
83-
}
84-
} else {
85-
tracer.trace(
86-
"Increasing compilation counter",
87-
("uniqueInputs", bundle.uniqueInputs.toString)
88-
) { tracer =>
89-
logger.debug(
90-
s"Increase count to prevent other compiles to schedule its deletion:${bundle.uniqueInputs}"
91-
)
92-
// Increase count to prevent other compiles to schedule its deletion
93-
count + 1
94-
}
95-
}
96-
}
97-
9868
if (deduplicate) running
9969
else scheduleCompilation(inputs, bundle, client, compile, tracer)
10070
}
@@ -139,8 +109,6 @@ object CompileGatekeeper {
139109
import bundle.logger
140110
import inputs.project
141111

142-
var counterForUsedClassesDir: AtomicInt = null
143-
144112
def initializeLastSuccessful(previousOrNull: LastSuccessfulResult): LastSuccessfulResult =
145113
tracer.trace(s"initialize last successful") { _ =>
146114
val result = Option(previousOrNull).getOrElse(bundle.lastSuccessful)
@@ -156,7 +124,6 @@ object CompileGatekeeper {
156124
// Replace classes dir, counter and populating with values from previous for correctness
157125
.copy(
158126
classesDir = result.classesDir,
159-
counterForClassesDir = result.counterForClassesDir,
160127
populatingProducts = result.populatingProducts
161128
)
162129
} else {
@@ -176,32 +143,7 @@ object CompileGatekeeper {
176143
s"Return previous result or the initial last successful coming from the bundle:${project.uniqueId}"
177144
)
178145
// Return previous result or the initial last successful coming from the bundle
179-
val previousResult = initializeLastSuccessful(previousResultOrNull)
180-
181-
currentlyUsedClassesDirs.compute(
182-
previousResult.classesDir,
183-
(_: AbsolutePath, counter: AtomicInt) => {
184-
logger.debug(
185-
s"Set counter for used classes dir when init or incrementing:${previousResult.classesDir}"
186-
)
187-
// Set counter for used classes dir when init or incrementing
188-
if (counter == null) {
189-
logger.debug(s"Create new counter:${previousResult.classesDir}")
190-
val initialCounter = AtomicInt(1)
191-
counterForUsedClassesDir = initialCounter
192-
initialCounter
193-
} else {
194-
counterForUsedClassesDir = counter
195-
val newCount = counter.incrementAndGet(1)
196-
logger.debug(
197-
s"Increasing counter for ${previousResult.classesDir} to $newCount"
198-
)
199-
counter
200-
}
201-
}
202-
)
203-
204-
previousResult.copy(counterForClassesDir = counterForUsedClassesDir)
146+
initializeLastSuccessful(previousResultOrNull)
205147
}
206148
)
207149
}

0 commit comments

Comments
 (0)