/*
* Licensed 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.aries.quiesce.manager.impl;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.apache.aries.quiesce.manager.QuiesceCallback;
import org.apache.aries.quiesce.manager.QuiesceManager;
import org.apache.aries.quiesce.participant.QuiesceParticipant;
import org.apache.aries.util.nls.MessageUtil;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class QuiesceManagerImpl implements QuiesceManager {
/** Logger */
private static final Logger LOGGER = LoggerFactory.getLogger(QuiesceManagerImpl.class.getName());
/** MessageUtil */
private static final MessageUtil MESSAGES = MessageUtil.createMessageUtil(QuiesceManagerImpl.class, "org.apache.aries.quiesce.manager.nls.quiesceMessages");
/** The default timeout to use */
private static int defaultTimeout = 60000;
/** The container's {@link BundleContext} */
private BundleContext bundleContext = null;
/** The thread pool to execute timeout commands */
private ScheduledExecutorService timeoutExecutor = Executors.newScheduledThreadPool(10, new ThreadFactory() {
public Thread newThread(Runnable r) {
Thread t = new Thread(r, "Quiesce Manager Timeout Thread");
t.setDaemon(true);
return t;
}
});
/** The thread pool to execute quiesce commands */
private ExecutorService executor = new ThreadPoolExecutor(0, 10, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),new ThreadFactory() {
public Thread newThread(Runnable arg0) {
Thread t = new Thread(arg0, "Quiesce Manager Thread");
t.setDaemon(true);
return t;
}
});
/** The map of bundles that are currently being quiesced */
private static ConcurrentHashMap<Bundle, Bundle> bundleMap = new ConcurrentHashMap<Bundle, Bundle>();
public QuiesceManagerImpl(BundleContext bc) {
bundleContext = bc;
}
/**
* Attempts to quiesce all bundles in the list. After the timeout has elapsed,
* or if successfully quiesced before that, the bundles are stopped. This method
* is non-blocking. Calling objects wishing to track the state of the bundles
* need to listen for the resulting stop events.
*/
public void quiesce(long timeout, List<Bundle> bundles) {
quiesceWithFuture(timeout, bundles);
}
public Future<?> quiesceWithFuture(List<Bundle> bundlesToQuiesce) {
return quiesceWithFuture(defaultTimeout, bundlesToQuiesce);
}
public Future<?> quiesceWithFuture(long timeout, List<Bundle> bundles) {
QuiesceFuture result = new QuiesceFuture();
if (bundles != null && !!!bundles.isEmpty()) {
//check that bundle b is not already quiescing
Iterator<Bundle> it = bundles.iterator();
Set<Bundle> bundlesToQuiesce = new HashSet<Bundle>();
while(it.hasNext()) {
Bundle b = it.next();
Bundle priorBundle = bundleMap.putIfAbsent(b, b);
if (priorBundle == null) {
bundlesToQuiesce.add(b);
}else{
LOGGER.warn(MESSAGES.getMessage("already.quiescing.bundle", b.getSymbolicName() + '/' + b.getVersion()));
}
}
Runnable command = new BundleQuiescer(bundlesToQuiesce, timeout, result, bundleMap);
executor.execute(command);
return result;
} else {
result.registerDone();
}
return result;
}
private static class QuiesceFuture implements Future<Object> {
private CountDownLatch latch = new CountDownLatch(1);
public boolean cancel(boolean mayInterruptIfRunning) {
throw new UnsupportedOperationException(MESSAGES.getMessage("quiesce.cannot.be.canceled"));
}
public Object get() throws InterruptedException, ExecutionException {
latch.await();
return null;
}
public Object get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
if (!!!latch.await(timeout, unit))
throw new TimeoutException();
return null;
}
public boolean isCancelled() {
return false;
}
public boolean isDone() {
return latch.getCount() == 0;
}
public void registerDone() {
if (!!!isDone()) {
latch.countDown();
}
}
}
/**
* Attempts to quiesce all bundles in the list, using the default timeout.
* After the timeout has elapsed, or if successfully quiesced before that,
* the bundles are stopped. This method is non-blocking. Calling objects
* wishing to track the state of the bundles need to listen for the
* resulting stop events.
*/
public void quiesce(List<Bundle> bundlesToQuiesce) {
quiesce(defaultTimeout, bundlesToQuiesce);
}
/**
* Stop a bundle that was to be quiesced. This happens either when all the participants
* are finished or when the timeout has occurred.
*
* The set of all bundles to quiesce is used to track stops, so that they do not occur twice.
* @param bundleToStop
* @param bundlesToStop
* @return
*/
private static boolean stopBundle(Bundle bundleToStop, Set<Bundle> bundlesToStop) {
try {
synchronized (bundlesToStop) {
if (bundlesToStop.remove(bundleToStop)) {
bundleToStop.stop();
bundleMap.remove(bundleToStop);
}
}
} catch (BundleException be) {
return false;
}
return true;
}
private static boolean stillQuiescing(Bundle bundleToStop) {
return bundleMap.containsKey(bundleToStop);
}
/**
* BundleQuiescer is used for each bundle to quiesce. It creates a callback object for each
* participant. Well-behaved participants will be non-blocking on their quiesce method.
* When all callbacks for the participants have completed, this thread will get an
* interrupt, so it sleeps until it hits the timeout. When complete it stops the bundle
* and removes the bundles from the list of those that are being quiesced.
*/
private class BundleQuiescer implements Runnable {
private final Set<Bundle> bundlesToQuiesce;
private final long timeout;
private final QuiesceFuture future;
public BundleQuiescer(Set<Bundle> bundlesToQuiesce, long timeout, QuiesceFuture future, ConcurrentHashMap<Bundle, Bundle> bundleMap) {
this.bundlesToQuiesce = new HashSet<Bundle>(bundlesToQuiesce);
this.timeout = timeout;
this.future = future;
}
public void run() {
try {
if (bundleContext != null) {
ServiceReference[] serviceRefs = bundleContext.getServiceReferences(QuiesceParticipant.class.getName(), null);
if (serviceRefs != null) {
List<QuiesceParticipant> participants = new ArrayList<QuiesceParticipant>();
final List<QuiesceCallbackImpl> callbacks = new ArrayList<QuiesceCallbackImpl>();
List<Bundle> copyOfBundles = new ArrayList<Bundle>(bundlesToQuiesce);
ScheduledFuture<?> timeoutFuture = timeoutExecutor.schedule(new Runnable() {
public void run() {
try {
synchronized (bundlesToQuiesce) {
for (Bundle b : new ArrayList<Bundle>(bundlesToQuiesce)) {
LOGGER.warn(MESSAGES.getMessage("quiesce.failed", b.getSymbolicName() + '/' + b.getVersion()));
stopBundle(b, bundlesToQuiesce);
}
}
} finally {
future.registerDone();
LOGGER.debug("Quiesce complete");
}
}
}, timeout, TimeUnit.MILLISECONDS);
//Create callback objects for all participants
for( ServiceReference sr : serviceRefs ) {
QuiesceParticipant participant = (QuiesceParticipant) bundleContext.getService(sr);
participants.add(participant);
callbacks.add(new QuiesceCallbackImpl(bundlesToQuiesce, callbacks, future, timeoutFuture));
}
//Quiesce each participant and wait for an interrupt from a callback
//object when all are quiesced, or the timeout to be reached
for( int i=0; i<participants.size(); i++ ) {
QuiesceParticipant participant = participants.get(i);
QuiesceCallbackImpl callback = callbacks.get(i);
participant.quiesce(callback, copyOfBundles);
}
}else{
for (Bundle b : bundlesToQuiesce) {
stopBundle(b, bundlesToQuiesce);
}
future.registerDone();
}
}
} catch (InvalidSyntaxException e) {
LOGGER.warn(MESSAGES.getMessage("null.is.invalid.filter"));
for (Bundle b : bundlesToQuiesce) {
stopBundle(b, bundlesToQuiesce);
}
future.registerDone();
}
}
}
/**
* Callback object provided for each participant for each quiesce call
* from the quiesce manager.
*/
private static class QuiesceCallbackImpl implements QuiesceCallback {
//Must be a copy
private final Set<Bundle> toQuiesce;
// Must not be a copy
private final Set<Bundle> toQuiesceShared;
//Must not be a copy
private final List<QuiesceCallbackImpl> allCallbacks;
//Timer so we can cancel the alarm if all done
private final QuiesceFuture future;
//The cleanup action that runs at timeout
private final ScheduledFuture<?> timeoutFuture;
public QuiesceCallbackImpl(Set<Bundle> toQuiesce, List<QuiesceCallbackImpl> allCallbacks, QuiesceFuture future, ScheduledFuture<?> timeoutFuture)
{
this.toQuiesce = new HashSet<Bundle>(toQuiesce);
this.toQuiesceShared = toQuiesce;
this.allCallbacks = allCallbacks;
this.future = future;
this.timeoutFuture = timeoutFuture;
}
/**
* Removes the bundles from the list of those to quiesce.
* If the list is now empty, this callback object is finished (i.e.
* the participant linked to this object has quiesced all the bundles
* requested).
*
* If all other participants have also completed, then the
* calling BundleQuieser thread is interrupted.
*/
public void bundleQuiesced(Bundle... bundlesQuiesced) {
boolean timeoutOccurred = false;
synchronized (allCallbacks) {
for(Bundle b : bundlesQuiesced) {
if(QuiesceManagerImpl.stillQuiescing(b)) {
if(toQuiesce.remove(b)) {
if(checkOthers(b)){
QuiesceManagerImpl.stopBundle(b, toQuiesceShared);
if(allCallbacksComplete()){
future.registerDone();
timeoutFuture.cancel(false);
LOGGER.debug("Quiesce complete");
}
}
}
} else {
timeoutOccurred = true;
break;
}
}
if (timeoutOccurred) {
Iterator<QuiesceCallbackImpl> it = allCallbacks.iterator();
while (it.hasNext()) {
it.next().toQuiesce.clear();
}
}
}
}
private boolean checkOthers(Bundle b) {
boolean allDone = true;
Iterator<QuiesceCallbackImpl> it = allCallbacks.iterator();
while (allDone && it.hasNext()) {
allDone = !!!it.next().toQuiesce.contains(b);
}
return allDone;
}
private boolean allCallbacksComplete() {
boolean allDone = true;
Iterator<QuiesceCallbackImpl> it = allCallbacks.iterator();
while (allDone && it.hasNext()) {
QuiesceCallbackImpl next = it.next();
if (!!!next.toQuiesce.isEmpty()) allDone = false;
}
return allDone;
}
}
}