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}