001// --------------------------------------------------------------------------------
002// Copyright 2002-2026 Echo Three, LLC
003//
004// Licensed under the Apache License, Version 2.0 (the "License");
005// you may not use this file except in compliance with the License.
006// You may obtain a copy of the License at
007//
008//     http://www.apache.org/licenses/LICENSE-2.0
009//
010// Unless required by applicable law or agreed to in writing, software
011// distributed under the License is distributed on an "AS IS" BASIS,
012// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013// See the License for the specific language governing permissions and
014// limitations under the License.
015// --------------------------------------------------------------------------------
016
017package com.echothree.util.server.persistence.mutex;
018
019/**
020 * A named, connection-scoped database mutex.
021 *
022 * <p>Locks use the injected Session's connection. Always release in a finally
023 * block after successful acquisition, within the same session; committing or
024 * rolling back does not release the lock. Repeated acquisition by the same
025 * database session succeeds and must be matched by an equal number of releases;
026 * these locks do not exclude other callers sharing that database session.
027 *
028 * <p>Inject the generic interface to select the implementation for the session's
029 * database flavor, then run protected work only after successful acquisition:
030 *
031 * <pre>{@code
032 * @Inject
033 * DatabaseMutex databaseMutex;
034 *
035 * public void performProtectedWork() {
036 *     if(databaseMutex.acquireLock("lockName")) {
037 *         try {
038 *             // Protected work
039 *         } finally {
040 *             if(!databaseMutex.releaseLock("lockName")) {
041 *                 // Handle an unsuccessful release.
042 *             }
043 *         }
044 *     } else {
045 *         // Lock was not acquired; skip or retry the work.
046 *     }
047 * }
048 * }</pre>
049 */
050public interface DatabaseMutex {
051
052    /** Default acquisition timeout, in seconds. */
053    int DEFAULT_TIMEOUT_SECONDS = 15;
054
055    /**
056     * Attempts to acquire the named lock using {@link #DEFAULT_TIMEOUT_SECONDS}.
057     *
058     * @param lockName the name of the lock
059     * @return true only when the lock was acquired
060     */
061    default boolean acquireLock(String lockName) {
062        return acquireLock(lockName, DEFAULT_TIMEOUT_SECONDS);
063    }
064
065    /**
066     * Attempts to acquire the named lock within the specified timeout.
067     *
068     * @param lockName the name of the lock
069     * @param timeoutSeconds zero to try once, positive to wait up to that many
070     * seconds, or negative to wait indefinitely
071     * @return true only when the lock was acquired; false on timeout or a null
072     * database result. Postgres also returns false when interrupted, preserving
073     * the interrupt flag. SQL execution errors propagate to the caller.
074     */
075    boolean acquireLock(String lockName, int timeoutSeconds);
076
077    /**
078     * Releases one acquisition of the named lock on the session's connection.
079     *
080     * @param lockName the name of the lock
081     * @return true only when the database confirms release; false when the lock
082     * is not owned by this session, does not exist, or the result is null.
083     * SQL execution errors propagate to the caller.
084     */
085    boolean releaseLock(String lockName);
086
087}