org.eclipse.osgi.internal.serviceregistry.ServiceUse Maven / Gradle / Ivy
Show all versions of aspectjtools Show documentation
/*******************************************************************************
* Copyright (c) 2003, 2022 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Hannes Wellmann - Add deadlock-detecting and auto-closable ServiceUseLock
*******************************************************************************/
package org.eclipse.osgi.internal.serviceregistry;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import org.eclipse.osgi.internal.debug.Debug;
import org.eclipse.osgi.internal.framework.BundleContextImpl;
import org.eclipse.osgi.internal.messages.Msg;
import org.eclipse.osgi.util.NLS;
import org.osgi.framework.ServiceException;
/**
* This class represents the use of a service by a bundle. One is created for each
* service acquired by a bundle.
*
*
* This class manages a singleton service.
*
* @ThreadSafe
*/
public class ServiceUse {
/**
* Custom ServiceException type to indicate a deadlock occurred during service
* registration.
*/
public static final int DEADLOCK = 1001;
/** BundleContext associated with this service use */
final BundleContextImpl context;
/** ServiceDescription of the registered service */
final ServiceRegistrationImpl registration;
final Debug debug;
/** bundle's use count for this service */
/* @GuardedBy("getLock()") */
private int useCount;
/**
* ServiceUseLock for this service use. Use the @{@link #lock()} method to lock
* the lock and obtain an {@link AutoCloseable} object which is used to unlock
* the lock.
*/
private final ServiceUseLock lock = new ServiceUseLock();
/**
* Constructs a service use encapsulating the service object.
*
* @param context bundle getting the service
* @param registration ServiceRegistration of the service
*/
ServiceUse(BundleContextImpl context, ServiceRegistrationImpl registration) {
this.useCount = 0;
this.registration = registration;
this.context = context;
this.debug = context.getContainer().getConfiguration().getDebug();
}
/**
* Get a service's service object and increment the use count.
*
* @return The service object.
*/
/* @GuardedBy("getLock()") */
S getService() {
assert getLock().isHeldByCurrentThread();
if (debug.DEBUG_SERVICES) {
Debug.println("[" + Thread.currentThread().getName() + "] getService[factory=" + registration.getBundle() //$NON-NLS-1$ //$NON-NLS-2$
+ "](" + context.getBundleImpl() + "," + registration + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
incrementUse();
return registration.getServiceObject();
}
/**
* Unget a service's service object.
*
*
* Decrements the use count if the service was being used.
*
* @return true if the service was ungotten; otherwise false.
*/
/* @GuardedBy("getLock()") */
boolean ungetService() {
assert getLock().isHeldByCurrentThread();
if (!inUse()) {
return false;
}
if (debug.DEBUG_SERVICES) {
Debug.println("[" + Thread.currentThread().getName() + "] ungetService[factory=" + registration.getBundle() //$NON-NLS-1$ //$NON-NLS-2$
+ "](" + context.getBundleImpl() + "," + registration + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
decrementUse();
return true;
}
/**
* Return the service object for this service use.
*
* @return The service object.
*/
/* @GuardedBy("getLock()") */
S getCachedService() {
return registration.getServiceObject();
}
/**
* Get a new service object for the service.
*
*
* By default, this returns the result of {@link #getService()}.
*
* @return The service object.
*/
/* @GuardedBy("getLock()") */
S newServiceObject() {
return getService();
}
/**
* Release a service object for the service.
*
*
* By default, this returns the result of {@link #ungetService()}.
*
* @param service The service object to release.
* @return true if the service was released; otherwise false.
* @throws IllegalArgumentException If the specified service was not
* provided by this object.
*/
/* @GuardedBy("getLock()") */
boolean releaseServiceObject(final S service) {
if ((service == null) || (service != getCachedService())) {
throw new IllegalArgumentException(Msg.SERVICE_OBJECTS_UNGET_ARGUMENT_EXCEPTION);
}
if (debug.DEBUG_SERVICES) {
Debug.println("[" + Thread.currentThread().getName() + "] releaseServiceObject[factory=" //$NON-NLS-1$ //$NON-NLS-2$
+ registration.getBundle() + "](" + context.getBundleImpl() + "," + registration + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
return ungetService();
}
/**
* Release all uses of the service and reset the use count to zero.
*/
/* @GuardedBy("getLock()") */
void release() {
assert getLock().isHeldByCurrentThread();
resetUse();
}
/**
* Is this service use using any services?
*
* @return true if no services are being used and this service use can be discarded.
*/
/* @GuardedBy("getLock()") */
boolean isEmpty() {
assert getLock().isHeldByCurrentThread();
return !inUse();
}
/**
* Is the use count non zero?
*
* @return true if the use count is greater than zero.
*/
/* @GuardedBy("getLock()") */
boolean inUse() {
return useCount > 0;
}
/**
* Incrementing the use count.
*/
/* @GuardedBy("getLock()") */
void incrementUse() {
if (useCount == Integer.MAX_VALUE) {
throw new ServiceException(Msg.SERVICE_USE_OVERFLOW);
}
useCount++;
}
/**
* Decrementing the use count.
*/
/* @GuardedBy("getLock()") */
void decrementUse() {
assert inUse();
useCount--;
}
/**
* Reset the use count to zero.
*/
/* @GuardedBy("getLock()") */
void resetUse() {
useCount = 0;
}
/**
* The ServiceUseLock for managing access to this ServiceUse.
*
* Use {@link #lock()} to lock the ServiceUseLock in a try-with-resources
* statement.
*
* @return The ServiceUseLock for this ServiceUse.
*/
ServiceUseLock getLock() {
return lock;
}
/**
* Acquires the {@link ServiceUseLock} of this ServiceUse.
*
* If this ServiceUse is locked by another thread then the current thread lies
* dormant until the lock has been acquired.
*
* @return The unlocker, which is {@link AutoCloseable}, to unlock the
* ServiceUseLock of this ServiceUse.
* @throws ServiceException If a deadlock with another ServiceUseLock is
* detected.
*/
ServiceUseLock lock() {
Thread awaitingThread = null;
boolean interrupted = false;
try {
final ServiceUseLock useLock = getLock(); // local var to avoid multiple getfields
while (true) {
try {
if (useLock.tryLock(100_000_000L, TimeUnit.NANOSECONDS)) { // 100ms (but prevent conversion)
return useLock;
}
awaitingThread = Thread.currentThread();
checkDeadLock(awaitingThread, useLock);
} catch (InterruptedException e) {
interrupted = true;
// Clear interrupted status and try again to lock, just like a plain
// synchronized. Re-interrupted before returning to the caller.
}
}
} finally {
if (awaitingThread != null) {
registration.getAwaitedUseLocks().remove(awaitingThread);
}
if (interrupted) {
Thread.currentThread().interrupt();
}
}
}
private void checkDeadLock(final Thread currentThread, final ServiceUseLock currentLock) {
final ConcurrentMap awaitedUseLocks = registration.getAwaitedUseLocks();
awaitedUseLocks.put(currentThread, currentLock);
ServiceUseLock useLock = currentLock;
// Check if current thread is in the cycle of mutually awaiting thread-lock
// pairs, but prevent infinite loop if current thread awaits a dead-locked lock
// but is itself not in the cycle.
int maxLocks = awaitedUseLocks.size();
for (int i = 0; i < maxLocks; i++) {
Thread owner = useLock.getOwner();
if (owner == currentThread) {
throw new ServiceException(NLS.bind(Msg.SERVICE_USE_DEADLOCK, currentLock), DEADLOCK);
}
if (owner == null || (useLock = awaitedUseLocks.get(owner)) == null) {
break; // lock could be released in the meantime
}
}
// Not (yet) a dead-lock. Lock was probably regularly hold by another thread.
// Race conditions are not an issue here. A deadlock is a static situation and
// if we closely missed the other thread putting its awaited lock it will be
// noticed in the next loop-pass.
}
/**
* ReentrantLock subclass that allows for {@link AutoCloseable} unlocking.
*
* This lock is unlocked if its {@code close()} method is invoked.
* ServiceUseLock objects can therefore can be used as a resource in a
* try-with-resources statement.
*
* Also exposes {@link #getOwner()} and has an enhanced {@link #toString()}.
*
* @see ServiceUse#lock()
*/
static class ServiceUseLock extends ReentrantLock implements AutoCloseable {
private static final long serialVersionUID = 1L;
/**
* Unlock this lock.
*
* This method is not idempotent and should be called only once for each lock
* acquisition.
*
*
* @see #unlock()
*/
@Override
public void close() {
unlock();
}
@Override
protected Thread getOwner() {
return super.getOwner();
}
/**
* Returns a lock state description for this lock. This adds additional
* information over the default implementation when the lock is held.
*
* @return The lock state description.
*/
@SuppressWarnings("nls")
@Override
public String toString() {
Thread o = getOwner();
if (o != null) {
try {
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
ThreadInfo threadInfo = threadMXBean.getThreadInfo(o.getId(), Integer.MAX_VALUE);
StackTraceElement[] trace = threadInfo.getStackTrace();
StringBuilder sb = new StringBuilder(super.toString()).append(", Details:\n");
if (o.isDaemon()) {
sb.append("daemon ");
}
sb.append("prio=").append(o.getPriority()).append(" id=").append(o.getId()).append(" ")
.append(o.getState());
for (StackTraceElement traceElement : trace) {
sb.append("\tat ").append(traceElement).append("\n");
}
return sb.toString();
} catch (Exception e) {
// do nothing and fall back to just the default, thread might be gone
}
}
return super.toString();
}
}
}