All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.osgi.internal.serviceregistry.ServiceUse Maven / Gradle / Ivy

There is a newer version: 1.9.22.1
Show newest version
/*******************************************************************************
 * 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(); } } }