/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF 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.apache.brooklyn.util.core.mutex; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import org.apache.brooklyn.util.exceptions.Exceptions; import com.google.common.collect.ImmutableList; /** a subclass of {@link Semaphore} * which tracks who created and released the semaphores, * and which requires the same thread to release as created it. */ public class SemaphoreWithOwners extends Semaphore { public SemaphoreWithOwners(String name) { this(name, 1, true); } public SemaphoreWithOwners(String name, int permits, boolean fair) { super(permits, fair); this.name = name; } private static final long serialVersionUID = -5303474637353009454L; final private List<Thread> owningThreads = new ArrayList<Thread>(); final private Set<Thread> requestingThreads = new LinkedHashSet<Thread>(); @Override public void acquire() throws InterruptedException { try { onRequesting(); super.acquire(); onAcquired(1); } finally { onRequestFinished(); } } @Override public void acquire(int permits) throws InterruptedException { try { onRequesting(); super.acquire(permits); onAcquired(permits); } finally { onRequestFinished(); } } @Override public void acquireUninterruptibly() { try { onRequesting(); super.acquireUninterruptibly(); onAcquired(1); } finally { onRequestFinished(); } } @Override public void acquireUninterruptibly(int permits) { try { onRequesting(); super.acquireUninterruptibly(permits); onAcquired(permits); } finally { onRequestFinished(); } } public void acquireUnchecked() { try { acquire(); } catch (InterruptedException e) { throw Exceptions.propagate(e); } } public void acquireUnchecked(int numPermits) { try { acquire(numPermits); } catch (InterruptedException e) { throw Exceptions.propagate(e); } } @Override public boolean tryAcquire() { try { onRequesting(); if (super.tryAcquire()) { onAcquired(1); return true; } return false; } finally { onRequestFinished(); } } @Override public boolean tryAcquire(int permits) { try { onRequesting(); if (super.tryAcquire(permits)) { onAcquired(permits); return true; } return false; } finally { onRequestFinished(); } } @Override public boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException { try { onRequesting(); if (super.tryAcquire(permits, timeout, unit)) { onAcquired(permits); return true; } return false; } finally { onRequestFinished(); } } @Override public boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException { try { onRequesting(); if (super.tryAcquire(timeout, unit)) { onAcquired(1); return true; } return false; } finally { onRequestFinished(); } } /** invoked when a caller successfully acquires a mutex, before {@link #onRequestFinished()} */ protected synchronized void onAcquired(int permits) { for (int i=0; i<permits; i++) owningThreads.add(Thread.currentThread()); } /** invoked when a caller is about to request a semaphore (before it might block); * guaranteed to call {@link #onRequestFinished()} after the blocking, * with a call to {@link #onAcquired(int)} beforehand if the acquisition was successful */ protected synchronized void onRequesting() { requestingThreads.add(Thread.currentThread()); } /** invoked when a caller has completed requesting a mutex, whether successful, aborted, or interrupted */ protected synchronized void onRequestFinished() { requestingThreads.remove(Thread.currentThread()); } @Override public void release() { super.release(); onReleased(1); } @Override public void release(int permits) { super.release(permits); onReleased(permits); } /** invoked when a caller has released permits */ protected synchronized void onReleased(int permits) { boolean result = true; for (int i=0; i<permits; i++) result = owningThreads.remove(Thread.currentThread()) & result; if (!result) throw new IllegalStateException("Thread "+Thread.currentThread()+" which released "+this+" did not own it."); } /** true iff there are any owners or any requesters (callers blocked trying to acquire) */ public synchronized boolean isInUse() { return !owningThreads.isEmpty() || !requestingThreads.isEmpty(); } /** true iff the calling thread is one of the owners */ public synchronized boolean isCallingThreadAnOwner() { return owningThreads.contains(Thread.currentThread()); } private final String name; /** constructor-time supplied name */ public String getName() { return name; } private String description; public void setDescription(String description) { this.description = description; } /** caller supplied description */ public String getDescription() { return description; } /** unmodifiable view of threads owning the permits; threads with multiple permits listed multiply */ public synchronized List<Thread> getOwningThreads() { return ImmutableList.<Thread>copyOf(owningThreads); } /** unmodifiable view of threads requesting access (blocked or briefly trying to acquire); * this is guaranteed to be cleared _after_ getOwners * (synchronizing on this class while reading both fields will give canonical access) */ public synchronized List<Thread> getRequestingThreads() { return ImmutableList.<Thread>copyOf(requestingThreads); } @Override public synchronized String toString() { return super.toString()+"["+name+"; description="+description+"; owning="+owningThreads+"; requesting="+requestingThreads+"]"; } /** Indicate that the calling thread is going to acquire or tryAcquire, * in order to set up the semaphore's isInUse() value appropriately for certain checks. * It *must* do so after invoking this method. */ public void indicateCallingThreadWillRequest() { requestingThreads.add(Thread.currentThread()); } }