/* * Copyright 2015 Groupon, Inc * Copyright 2015 The Billing Project, LLC * * The Billing Project licenses this file to you under the Apache License, version 2.0 * (the "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package org.killbill.commons.locker; import org.killbill.commons.request.Request; import org.killbill.commons.request.RequestData; import java.util.HashMap; import java.util.Map; public class ReentrantLock { private final Map<String, LockHolder> lockTable; public ReentrantLock() { this.lockTable = new HashMap<String, LockHolder>(); } public enum ReentrantLockState { FREE, HELD_OWNER, HELD_NOT_OWNER } public static class TryAcquireLockState { private final ReentrantLockState lockState; private final GlobalLock originalLock; public TryAcquireLockState(final ReentrantLockState lockState) { this(lockState, null); } public TryAcquireLockState(final ReentrantLockState lockState, final GlobalLock originalLock) { this.lockState = lockState; this.originalLock = originalLock; } public ReentrantLockState getLockState() { return lockState; } public GlobalLock getOriginalLock() { return originalLock; } } /** * Atomically increment the refCount lock if we are already the owner of that lock. * * @param lockName * @return the ReentrantLockState: lock is FREE, or we already hold it (and incremented the refCount) or it held by somebody else */ public TryAcquireLockState tryAcquireLockForExistingOwner(final String lockName) { synchronized (lockTable) { final LockHolder lockHolder = lockTable.get(lockName); if (lockHolder == null) { return new TryAcquireLockState(ReentrantLockState.FREE); } final String maybeNullRequestId = getRequestId(); if (maybeNullRequestId == null || !lockHolder.getRequestId().equals(maybeNullRequestId)) { return new TryAcquireLockState(ReentrantLockState.HELD_NOT_OWNER); } else { // Increment value before we return while we hold the lockTable lock. lockHolder.increment(); return new TryAcquireLockState(ReentrantLockState.HELD_OWNER, lockHolder.getOriginalLock()); } } } /** * Create a new LockHolder. This is done *after* the distributed lock was acquired. * * @param lockName */ public void createLock(final String lockName, final GlobalLock originalLock) { final String requestId = getRequestId(); if (requestId == null) { return; } synchronized (lockTable) { LockHolder lockHolder = lockTable.get(lockName); if (lockHolder != null) { throw new IllegalStateException(String.format("ReentrantLock createLock %s : lock already current request = %s, owner request = %s", lockName, requestId, lockHolder.getRequestId())); } if (lockHolder == null) { lockHolder = new LockHolder(requestId, originalLock); lockTable.put(lockName, lockHolder); } lockHolder.increment(); } } /** * Release a lock. This is always called when the resources are feed * * @param lockName * @return true if nobody still holds the reentrant lock (and thefeore distributed lock can be freed) */ public boolean releaseLock(final String lockName) { // In case there no requestId set, this was not a 'reentrant' lock, so nothing to do but we need to return true // so distributed lock can be released // final String requestId = getRequestId(); if (requestId == null) { return true; } synchronized (lockTable) { final LockHolder lockHolder = lockTable.get(lockName); if (lockHolder == null) { throw new IllegalStateException(String.format("ReentrantLock releaseLock %s : cannot find lock in the table, current request = %s", lockName, requestId)); } if (!lockHolder.getRequestId().equals(requestId)) { throw new IllegalStateException(String.format("ReentrantLock releaseLock %s : current request = %s, owner request = %s", lockName, requestId, lockHolder.getRequestId())); } final boolean free = lockHolder.decrement(); if (free) { lockTable.remove(lockName); } return free; } } private String getRequestId() { final RequestData requestData = Request.getPerThreadRequestData(); return requestData != null ? requestData.getRequestId() : null; } private static class LockHolder { private final String requestId; private final GlobalLock originalLock; private int refCount; public LockHolder(final String requestId, final GlobalLock originalLock) { this.requestId = requestId; this.originalLock = originalLock; this.refCount = 0; } public void increment() { refCount++; } public boolean decrement() { return --refCount == 0; } public String getRequestId() { return requestId; } public GlobalLock getOriginalLock() { return originalLock; } } }