desruisseaux commented on code in PR #347:
URL:
https://github.com/apache/maven-clean-plugin/pull/347#discussion_r4110874605
##########
src/main/java/org/apache/maven/plugins/clean/BackgroundCleaner.java:
##########
@@ -98,57 +169,114 @@ final class BackgroundCleaner extends Cleaner implements
Listener, Runnable {
*/
private IOException errors;
- /**
- * Whether at least one deletion task has been queued.
- */
- private boolean started;
-
/**
* Whether to disable the deletion of files in background threads.
* This is used for avoiding to repeat the same warning many times
* when the {@link #fastDir} directory does not exist.
+ *
+ * <p>This field is written by {@link #fastDeleteError(IOException)}
without holding any lock
+ * (to avoid a lock-ordering risk), and read by the synchronized {@link
#fastDelete} method.
+ * Declaring it {@code volatile} ensures that the write is immediately
visible to all threads
+ * without requiring the reader to hold the same monitor as the writer.</p>
*/
- private boolean disabled;
+ private volatile boolean disabled;
/**
- * Creates a new cleaner to be executed in a background thread.
+ * Creates a new background cleaner service.
+ * Use {@link #getOrCreate} to obtain a session-scoped instance.
*
- * @param session the Maven session to be used
- * @param matcherFactory the service to use for creating include and
exclude filters.
- * @param logger the logger to use
- * @param verbose whether to perform verbose logging
- * @param fastDir the explicit configured directory or to be
deleted in fast mode
- * @param fastMode the fast deletion mode
- * @param followSymlinks whether to follow symlinks
- * @param force whether to force the deletion of read-only files
- * @param failOnError whether to abort with an exception in case a
selected file/directory could not be deleted
- * @param retryOnError whether to undertake additional delete attempts
in case the first attempt failed
+ * @param session the Maven session to be used
+ * @param logger the logger to use
+ * @param fastDir the directory where to temporarily move the files to
delete
+ * @param fastMode the fast deletion mode
*/
- @SuppressWarnings("checkstyle:ParameterNumber")
- BackgroundCleaner(
- @Nonnull Session session,
- @Nonnull PathMatcherFactory matcherFactory,
- @Nonnull Log logger,
- boolean verbose,
- @Nonnull Path fastDir,
- @Nonnull FastMode fastMode,
- boolean followSymlinks,
- boolean force,
- boolean failOnError,
- boolean retryOnError) {
- super(matcherFactory, logger, verbose, followSymlinks, force,
failOnError, retryOnError);
+ private BackgroundCleaner(
+ @Nonnull Session session, @Nonnull Log logger, @Nonnull Path
fastDir, @Nonnull FastMode fastMode) {
this.session = session;
+ this.logger = logger;
this.fastDir = fastDir;
this.fastMode = fastMode;
filesToDeleteAtEnd = (fastMode != FastMode.BACKGROUND) ? new
ArrayList<>() : null;
directoriesToDeleteIfEmpty = new LinkedHashSet<>(); // Will need to
delete in order.
executor = Executors.newSingleThreadExecutor((task) -> new
Thread(task, "mvn-background-cleaner"));
+ // Note: registerListener() and scanForLeftovers() are called by
getOrCreate() AFTER
+ // computeIfAbsent() returns, to avoid re-entrant ConcurrentHashMap
access and to
+ // prevent `this` from escaping the constructor.
+ }
+
+ /**
+ * Initializes the background cleaner by registering the session listener
and scanning
+ * for leftover directories. This method must be called exactly once,
immediately after
+ * the instance is created by {@link #getOrCreate}, but outside the
+ * {@link java.util.concurrent.ConcurrentHashMap#computeIfAbsent} mapping
function
+ * to avoid re-entrant deadlock.
+ */
+ private void init() {
+ session.registerListener(this);
+ scanForLeftovers();
+ }
+
+ /**
+ * Returns the session-scoped {@code BackgroundCleaner}, creating it on
first access.
+ * The instance is stored in {@link SessionData} so that all subprojects
in a reactor
+ * share the same background thread and session listener.
+ *
+ * @param session the Maven session to be used
+ * @param logger the logger to use
+ * @param fastDir the directory where to temporarily move the files to
delete
+ * @param fastMode the fast deletion mode
+ * @return the shared background cleaner instance for the session
+ */
+ static BackgroundCleaner getOrCreate(
+ @Nonnull Session session, @Nonnull Log logger, @Nonnull Path
fastDir, @Nonnull FastMode fastMode) {
+ boolean[] created = {false};
+ BackgroundCleaner bc = session.getData().computeIfAbsent(KEY, () -> {
+ created[0] = true;
+ return new BackgroundCleaner(session, logger, fastDir, fastMode);
+ });
+ if (created[0]) {
+ // Initialization is deferred to here (outside computeIfAbsent) to
avoid
+ // re-entrant ConcurrentHashMap access and `this` escaping the
constructor.
+ bc.init();
+ }
+ if (!bc.fastDir.equals(fastDir) || bc.fastMode != fastMode) {
+ logger.warn("BackgroundCleaner already initialized with fastDir="
+ bc.fastDir
+ + ", fastMode=" + bc.fastMode + "; ignoring fastDir=" +
fastDir
+ + ", fastMode=" + fastMode + " from this subproject.");
+ }
+ return bc;
+ }
+
+ /**
+ * Scans the fast directory for leftover directories from previous
(possibly killed) builds
+ * and queues them for background deletion. This restores the cleanup
behavior that was
+ * present in the singleton pattern of version 3.5.0 but was lost when
switching to
+ * per-subproject instances.
+ *
+ * <p><b>Limitation:</b> leftovers are always deleted with {@code
force=false}.
+ * Because the previous build's configuration is not persisted, we cannot
know
+ * whether it used {@code force=true}. As a consequence, read-only files
that
+ * survived a killed build will not be force-deleted here; they will
remain until
+ * the user runs a new clean with {@code force=true}.</p>
+ */
+ private void scanForLeftovers() {
+ if (Files.isDirectory(fastDir)) {
+ try (DirectoryStream<Path> stream =
Files.newDirectoryStream(fastDir)) {
+ for (Path child : stream) {
+ if (Files.isDirectory(child)) {
+ logger.debug("Cleaning leftover directory from
previous build: " + child);
+ executor.submit(() -> deleteInBackground(child, false,
true));
+ }
+ }
+ } catch (IOException e) {
+ logger.debug("Failed to scan for leftover directories in " +
fastDir + ": " + e);
Review Comment:
Replace `+ ": " + e` by `+ '.', e` in order to report the exception in the
log.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]