Skip to content

Commit bfabc3b

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 bfabc3b

File tree

6 files changed

+151
-119
lines changed

6 files changed

+151
-119
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: 64 additions & 43 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
@@ -335,16 +343,15 @@ object CompileTask {
335343
o
336344
)
337345
}
338-
339346
val client = state.client
340347
CompileGraph
341348
.traverse(dag, client, store, bestEffortAllowed, setup, compile, tracer)
342349
.flatMap { pdag =>
343350
val partialResults = Dag.dfs(pdag, mode = Dag.PreOrder)
344351
val finalResults = partialResults.map(r => PartialCompileResult.toFinalResult(r))
345352
Task.gatherUnordered(finalResults).map(_.flatten).flatMap { results =>
346-
val cleanUpTasksToRunInBackground =
347-
markUnusedClassesDirAndCollectCleanUpTasks(results, state, tracer, rawLogger)
353+
val makrUnusedDirectoriesTask =
354+
markUnusedClassesDir(results, state, tracer, rawLogger)
348355

349356
val failures = results.flatMap {
350357
case FinalNormalCompileResult(p, results) =>
@@ -389,7 +396,14 @@ object CompileTask {
389396
}
390397

391398
// Schedule to run clean-up tasks in the background
392-
runIOTasksInParallel(cleanUpTasksToRunInBackground)
399+
runIOTasksInParallel(
400+
makrUnusedDirectoriesTask,
401+
runOnFinish = cleanupIfNoCompilationRunningTask(
402+
compilationCounter.decrementAndGet(),
403+
tracer,
404+
rawLogger
405+
)
406+
)
393407

394408
val runningTasksRequiredForCorrectness = Task.sequence {
395409
results.flatMap {
@@ -409,6 +423,7 @@ object CompileTask {
409423
}
410424
}
411425
}
426+
412427
}
413428

414429
case class ConfiguredCompilation(scalacOptions: List[String])
@@ -445,14 +460,14 @@ object CompileTask {
445460
}
446461
}
447462

448-
private def markUnusedClassesDirAndCollectCleanUpTasks(
463+
private def markUnusedClassesDir(
449464
results: List[FinalCompileResult],
450465
previousState: State,
451466
tracer: BraveTracer,
452467
logger: Logger
453468
): List[Task[Unit]] =
454469
tracer.trace("markUnusedClassesDirAndCollectCleanUpTasks") { _ =>
455-
val cleanUpTasksToSpawnInBackground = mutable.ListBuffer[Task[Unit]]()
470+
val markDeleteTasks = mutable.ListBuffer[Task[Unit]]()
456471
results.foreach { finalResult =>
457472
val resultBundle = finalResult.result
458473
val newSuccessful = resultBundle.successful
@@ -464,36 +479,40 @@ object CompileTask {
464479
case _ => None
465480
}
466481
val populateNewProductsTask = newSuccessful.map(_.populatingProducts).getOrElse(Task.unit)
467-
val cleanUpPreviousLastSuccessful = resultBundle.previous match {
482+
val markDeletePreviousLastSuccessful = resultBundle.previous match {
468483
case None => populateNewProductsTask
469484
case Some(previousSuccessful) =>
470485
for {
471486
_ <- previousSuccessful.populatingProducts
472487
_ <- populateNewProductsTask
473-
_ <- cleanUpPreviousResult(
488+
toDelete = previousDirectoriesToCleanup(
474489
previousSuccessful,
475490
previousResult,
476491
compilerResult,
477492
tracer,
478493
logger
479494
)
495+
_ = toDelete.foreach(cleanUpTasks.add)
480496
} yield ()
481497
}
482498

483-
cleanUpTasksToSpawnInBackground.+=(cleanUpPreviousLastSuccessful)
499+
markDeleteTasks.+=(markDeletePreviousLastSuccessful)
484500
}
485501

486-
cleanUpTasksToSpawnInBackground.toList
502+
markDeleteTasks.toList
487503
}
488504

489505
def runIOTasksInParallel[T](
490506
tasks: Traversable[Task[T]],
491-
parallelUnits: Int = Runtime.getRuntime().availableProcessors()
507+
parallelUnits: Int = Runtime.getRuntime().availableProcessors(),
508+
runOnFinish: Task[Unit]
492509
): Unit = {
493510
val aggregatedTask = Task.sequence(
494511
tasks.toList.grouped(parallelUnits).map(group => Task.gatherUnordered(group)).toList
495512
)
496-
aggregatedTask.map(_ => ()).runAsync(ExecutionContext.ioScheduler)
513+
aggregatedTask
514+
.flatMap(_ => runOnFinish)
515+
.runAsync(ExecutionContext.ioScheduler)
497516
()
498517
}
499518

@@ -514,15 +533,14 @@ object CompileTask {
514533
* superseeded by the new classes directory generated during a successful
515534
* compile.
516535
*/
517-
private def cleanUpPreviousResult(
536+
private def previousDirectoriesToCleanup(
518537
previousSuccessful: LastSuccessfulResult,
519538
previousResult: Option[Compiler.Result],
520539
compilerResult: Compiler.Result,
521540
tracer: BraveTracer,
522541
logger: Logger
523-
): Task[Unit] = tracer.trace("clean up previous result") { implicit tracer =>
542+
): List[AbsolutePath] = tracer.trace("clean up previous result") { _ =>
524543
val previousClassesDir = previousSuccessful.classesDir
525-
val currentlyUsedCounter = previousSuccessful.counterForClassesDir.decrementAndGet(1)
526544

527545
val previousReadOnlyToDelete = compilerResult match {
528546
case Success(_, products, _, _, isNoOp, _, _) =>
@@ -532,11 +550,6 @@ object CompileTask {
532550
} else if (CompileOutPaths.hasEmptyClassesDir(previousClassesDir)) {
533551
logger.debug(s"Skipping delete of empty classes dir ${previousClassesDir}")
534552
None
535-
} else if (currentlyUsedCounter != 0) {
536-
logger.debug(
537-
s"Skipping delete of $previousClassesDir, counter is $currentlyUsedCounter"
538-
)
539-
None
540553
} else {
541554
val newClassesDir = products.newClassesDir
542555
logger.debug(s"Scheduling to delete ${previousClassesDir} superseded by $newClassesDir")
@@ -566,28 +579,36 @@ object CompileTask {
566579
case _ => None
567580
}
568581

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) =>
576-
Task.eval {
577-
logger.debug(s"Deleting contents of orphan dir $classesDir")
578-
BloopPaths.delete(classesDir)
579-
}.asyncBoundary
582+
List(
583+
previousReadOnlyToDelete,
584+
previousBestEffortToDelete
585+
).flatten
586+
587+
}
588+
589+
def cleanupIfNoCompilationRunningTask(
590+
counter: Int,
591+
tracer: BraveTracer,
592+
logger: Logger
593+
): Task[Unit] = {
594+
Task {
595+
if (counter == 0) {
596+
tracer.trace("delete orphan directories") { _ =>
597+
val allDeleteDirectories = new mutable.ListBuffer[AbsolutePath]()
598+
while (cleanUpTasks.size() > 0) allDeleteDirectories += cleanUpTasks.poll()
599+
val allDeleteTasks = allDeleteDirectories
600+
.result()
601+
.map(classesDir => {
602+
Task.eval {
603+
logger.debug(s"Deleting contents of orphan dir $classesDir : ${classesDir.exists}")
604+
BloopPaths.delete(classesDir)
605+
}.asyncBoundary
606+
})
607+
Task.gatherUnordered(allDeleteTasks).map(_ => ())
580608
}
609+
} else {
610+
Task.unit
581611
}
582-
}
583-
584-
Task
585-
.gatherUnordered(
586-
List(
587-
deleteOrphanDir(previousReadOnlyToDelete),
588-
deleteOrphanDir(previousBestEffortToDelete)
589-
)
590-
)
591-
.map(_ => ())
612+
}.flatten
592613
}
593614
}

0 commit comments

Comments
 (0)