Skip to content

Default file-lock named-lock factory (2.0) writes .locks into the local repository, left behind on Windows #2017

Description

@ascheman

Affected version

Maven Resolver 2.0.x — the default changed in the 2.0 line and is still present in the latest
release 2.0.21 (verified 2.0.13, 2.0.20, 2.0.21). Resolver 1.9.x is unaffected. Surfaced via
Maven 3.10.0-rc-1 (bundles 2.0.20) and Maven 4.0.0-rc-5 (bundles 2.0.13); the current Maven
master bundles 2.0.21, maven-4.0.x bundles 2.0.20. Next resolver release: 2.0.22.

Bug description

Summary

Since Resolver 2.0, the default named-lock factory is file-lock
(DefaultNamedLockFactorySelector.DEFAULT_FACTORY_NAME = FileLockNamedLockFactory.NAME),
whereas 1.9.x effectively used in-memory locks. file-lock writes a .locks directory
inside the local repository base directory (BasedirNameMapper.DEFAULT_LOCKS_DIR = ".locks").

Two consequences follow for any consumer (e.g. Maven), on a plain resolve/install:

  1. A .locks directory now appears in the local repository on every OS (empty on
    POSIX, populated on Windows).
  2. On Windows the 0-byte lock files are not cleaned up and remain. When the local
    repository is also used as a deploy/staging tree (as Sonatype's third-party
    central-publishing-maven-plugin does under target/), those .locks files get bundled
    and the Central Portal rejects the bundle (a .locks entry has no .pom).

With resolver 1.9.x (Maven 3.9.x) no lock files were produced at all. So this is a behavior
change introduced by the 2.0 default.

Reported downstream on users@maven.apache.org:
https://lists.apache.org/thread/1trdtdct2glycrzqo6xhbryns5o5pxno

Root cause

1. file-lock is the 2.0 default; the .locks dir lives inside the local repo.
maven-resolver-impl/.../internal/impl/named/DefaultNamedLockFactorySelector.java:

public static final String DEFAULT_FACTORY_NAME = FileLockNamedLockFactory.NAME; // "file-lock"

maven-resolver-impl/.../internal/impl/synccontext/named/BasedirNameMapper.java:

public static final String DEFAULT_LOCKS_DIR = ".locks";

The base directory defaults to the local repository root, so the lock files are written into
the very tree that consumers may treat as content. DefaultNamedLockFactorySelector did not
exist in 1.9.x, whose default was in-memory — consistent with 1.9.x producing no .locks.

Note: Maven's shipped distribution does not override this back — its
apache-maven/src/assembly/maven/conf/maven-system.properties sets no
aether.syncContext.named.factory on master, maven-4.0.x or 3.10.x. (A rwlock-local
override exists only as a maven-cli test fixture, not in the distribution.) So the resolver
default is what users get.

2. The files are left behind only on Windows.
maven-resolver-named-locks/.../providers/FileLockNamedLockFactory.java:

private static final boolean IS_WINDOWS =
    System.getProperty("os.name", "unknown").startsWith("Windows");

private static final boolean DELETE_LOCK_FILES =
    Boolean.parseBoolean(System.getProperty(
        "aether.named.file-lock.deleteLockFiles", Boolean.toString(!IS_WINDOWS)));
  • Linux/macOS: the lock file is opened with StandardOpenOption.DELETE_ON_CLOSE and
    removed on close — the .locks directory ends up empty.
  • Windows: DELETE_LOCK_FILES defaults to false (a deliberate tweak, since
    DELETE_ON_CLOSE causes concurrency issues on Windows) → the 0-byte .locks files remain.

So the default change (.locks created inside the local repo) is OS-independent; the
leftover files that break deploy staging are the Windows-specific consequence.

Verification (end-to-end, no third-party plugin)

A plain mvn install in default configuration into an isolated local repository creates the
.locks directory — proving it is the resolver default at work, no plugin involved. A core IT
(MavenITghFileLockDefaultLocksTest) asserts a default run creates no .locks directory;
it fails on every released Maven that bundles resolver 2.0. CI matrix — all six jobs fail at
the assertion:

ubuntu windows macOS
Maven 3.10.0-rc-1 (resolver 2.0.20) Failures: 1 Failures: 1 Failures: 1
Maven 4.0.0-rc-5 (resolver 2.0.13) Failures: 1 Failures: 1 Failures: 1

CI run: https://github.com/aschemaven/maven-integration-testing/actions/runs/30391734694

The Windows leftover files also reproduce on Linux/macOS by forcing the Windows path with
-Daether.named.file-lock.deleteLockFiles=false (168 leftover 0-byte files in a small build).

Workaround for consumers

-Daether.syncContext.named.factory=rwlock-local

Suggested direction (maintainers decide)

Options, roughly in order of "fixes the root cause":

  1. Do not place file-lock lock files inside the local repository — use a location that
    is never treated as content (e.g. a temp/state dir), so consumers can't bundle them.
  2. Clean up the lock files on Windows too (or make .locks never contain deployable-looking
    entries).
  3. Reconsider file-lock as the default for the common single-process case.

(A consumer-side mitigation — Maven defaulting aether.syncContext.named.factory=rwlock-local
— treats the symptom, not the cause.)

Environment

  • Maven Resolver 2.0.13, 2.0.20, 2.0.21 (affected — DEFAULT_FACTORY_NAME is still
    file-lock in 2.0.21); 1.9.27 unaffected. Next release: 2.0.22.
  • Consumers observed: Maven 3.10.0-rc-1, 4.0.0-rc-5, current maven-4.0.x (resolver 2.0.20)
    and master (resolver 2.0.21).
  • OS: .locks directory created on all of Linux/macOS/Windows; leftover .locks files on
    Windows (the fatal case for deploy staging).
  • Downstream where it surfaced: org.sonatype.central:central-publishing-maven-plugin:0.11.0
    publishing to central.sonatype.com.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions