ParentLeaseHold.java

package network.ike.plugin.ws;

import org.apache.maven.api.plugin.MojoException;
import org.apache.maven.api.plugin.Log;

import java.io.File;
import java.nio.file.Path;
import java.util.Optional;

/**
 * The short-hold of the <em>parent</em> working set's lease during a
 * sibling finish — the last open thread of the local-origin model
 * (IKE-Network/ike-issues#992, settled scope of #1002, delivered as
 * #1005's {@code ws:} half).
 *
 * <p>A sibling finish lands in the parent: the absorb fast-forwards the
 * parent's refs, which makes the finish a <em>writer of the parent</em>,
 * and single-writer is per working set. So the finish confirms the
 * parent's lease before landing — free or expired acquires silently,
 * already-mine simply confirms, and live on another machine refuses with
 * the takeover left to the human, the one rule every surface shares.
 *
 * <p>"Short" means the hold gives back exactly what it took: a lease this
 * machine already held stays held; one acquired fresh for the landing is
 * released when the hold closes, success or failure, so a finished (or
 * failed) sibling never leaves its parent pinned to this machine.
 * Inert wherever the lease machinery is ({@link WorkingSetLease}).
 */
final class ParentLeaseHold implements AutoCloseable {

    private final Path parent;
    private final boolean acquiredFresh;
    private final Log log;

    private ParentLeaseHold(Path parent, boolean acquiredFresh, Log log) {
        this.parent = parent;
        this.acquiredFresh = acquiredFresh;
        this.log = log;
    }

    /**
     * Confirms the parent's lease for a finish that is about to land in
     * it.
     *
     * @param workingSetRoot the sibling working set the finish runs in
     * @param log            the goal's log
     * @return the hold; inert when the working set has no local parent or
     *         the lease machinery is absent
     * @throws MojoException if another machine holds the parent live —
     *                       landing there would collide with its writer
     */
    static ParentLeaseHold acquire(File workingSetRoot, Log log)
            throws MojoException {
        Optional<File> parent = SiblingFinish.localParent(workingSetRoot);
        if (parent.isEmpty()) {
            return new ParentLeaseHold(null, false, log);
        }
        Path parentPath = parent.get().toPath();
        boolean alreadyMine = WorkingSetLease.status(parentPath)
                .map(status -> status.line().contains(": MINE"))
                .orElse(false);
        WorkingSetLease.Decision decision =
                WorkingSetLease.confirm(parentPath);
        return switch (decision.verdict()) {
            case NOT_APPLICABLE -> new ParentLeaseHold(null, false, log);
            case HELD -> {
                log.info("  Parent lease: confirmed for " + decision.detail()
                        + (alreadyMine ? "" : " (short-hold — released after"
                                + " the landing)"));
                yield new ParentLeaseHold(parentPath, !alreadyMine, log);
            }
            case FENCED -> throw new MojoException(
                    "The finish lands in the parent working set, and another "
                            + "machine holds it live; landing here would "
                            + "collide with its writer.\n" + decision.detail());
        };
    }

    /**
     * Gives back what the hold took: releases the parent's lease when it
     * was acquired fresh for this landing, and only then. Best-effort —
     * a failed release is logged, never thrown; the record ages out.
     */
    @Override
    public void close() {
        if (parent == null || !acquiredFresh) {
            return;
        }
        if (WorkingSetLease.release(parent)) {
            log.info("  Parent lease: short-hold released");
        } else {
            log.warn("  Parent lease: short-hold release failed; the record "
                    + "will age out at its ttl");
        }
    }
}