/******************************************************************************* * Copyright (c) 2011, 2015 Wind River Systems, Inc. and others. All rights reserved. * This program and the accompanying materials are made available under the terms * of the Eclipse Public License v1.0 which accompanies this distribution, and is * available at http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Wind River Systems - initial API and implementation *******************************************************************************/ package org.eclipse.tcf.te.tcf.core.concurrent; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Timer; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.MultiStatus; import org.eclipse.tcf.te.runtime.interfaces.callback.ICallback; import org.eclipse.tcf.te.tcf.core.activator.CoreBundleActivator; import org.eclipse.tcf.te.tcf.core.nls.Messages; /** * A helper class used to synchronize multiple threads. It is used * to join multiple threads which collaborate to create the pre-condition * of the callback code. * <p> * A callback monitor maintains a map containing a set of locks. * The collaborating threads should unlock one of its own lock in * it and wake up the callback if all the locks in the map is opened. * <p> * The following is an example: * <pre> * class Thread1 extends Thread { * CallbackMonitor monitor; * public Thread1(CallbackMonitor monitor){ * this.monitor = monitor; * } * public void run() { * // Do the work * ... * // Unlock this thread. * monitor.unlock(this) * } * } * class Thread2 extends Thread { * CallbackMonitor monitor; * public Thread2(CallbackMonitor monitor){ * this.monitor = monitor; * } * public void run() { * // Do the work * ... * // Unlock this thread. * monitor.unlock(this) * } * } * ... * public void collaborate() { * Runnable callback = new Runnable() { * public void run() { * // Do something which must be done after all the threads end. * ... * } * }; * CallbackMonitor monitor = new CallbackMonitor(callback); * Thread1 thread1 = new Thread1(monitor); * Thread2 thread2 = new Thread2(monitor); * ... * monitor.lock(thread1, thread2, ...); * thread1.start(); * thread2.start(); * ... * } * </pre> * <p> * The above creates multiple threads which lock on the monitor and * invoke unlock when they end. The keys they used can be anything which * are unique among the threads. Once all threads end, the callback defined * in the method will be invoked and do the thing which requires to be done * after the end of these threads. * <p> * <b>Note:</b><em>The threads which require collaboration on the callback * monitor should be started only after all the locks corresponding to them * are added. </em> * <p> * For example, the above threads are started after the monitor locks all the threads: * <pre> * monitor.lock(thread1, thread2, ...); * thread1.start(); * thread2.start(); * ... * </pre> */ public class CallbackMonitor { // The default timeout value is one minute. private static final long DEFAULT_TIMEOUT = 60 * 1000L; // The callback which is invoked after all the locks are unlocked. private ICallback callback; // The lock map containing the keys and the corresponding running results. private Map<Object, IStatus> locks; /** * Create a callback monitor with the specified callback with a default timeout. * * @param callback The callback to be invoked after all the locks being unlocked. */ public CallbackMonitor(ICallback callback) { this(callback, DEFAULT_TIMEOUT); } /** * Create a callback monitor with the specified callback with a timeout. If * the timeout is zero, then it will block forever until all locks are released. * * @param callback The callback to be invoked after all the locks being unlocked. * @param timeout The timeout value. */ public CallbackMonitor(ICallback callback, long timeout) { Assert.isNotNull(callback); this.callback = callback; this.locks = Collections.synchronizedMap(new HashMap<Object, IStatus>()); if (timeout > 0) { new Timer().schedule(new MonitorTask(callback, timeout), timeout, timeout); } } /** * Create a callback monitor with the specified callback and the keys with a default * timeout. * * @param callback The callback to be invoked after all the locks being unlocked. * @param keys The keys to lock and unlock the locks. */ public CallbackMonitor(ICallback callback, Object...keys) { this(callback, DEFAULT_TIMEOUT, keys); } /** * Create a callback monitor with the specified callback and the keys and a timeout. If * the timeout is zero, then it will block forever until all locks are released. * * @param callback The callback to be invoked after all the locks being unlocked. * @param keys The keys to lock and unlock the locks. * @param timeout The timeout value. */ public CallbackMonitor(ICallback callback, long timeout, Object... keys) { Assert.isNotNull(callback); this.callback = callback; this.locks = Collections.synchronizedMap(new HashMap<Object, IStatus>()); lock(keys); if (timeout > 0) { new Timer().schedule(new MonitorTask(callback, timeout), timeout, timeout); } } /** * Add multiple locks with the specified keys. * * @param keys The keys whose locks are added. */ public void lock(Object... keys) { for(Object key : keys) { Assert.isNotNull(key); this.locks.put(key, null); } } /** * Add a lock with the specified key. * * @param key The key whose lock is added. */ public void lock(Object key) { Assert.isNotNull(key); this.locks.put(key, null); } /** * Unlock the lock with the specified key and status * check if all the locks have been unlocked. If all the * locks have been unlocked, then invoke the callback. * * @param key The key to unlock its lock. */ public void unlock(Object key, IStatus status) { Assert.isNotNull(key); Assert.isNotNull(status); locks.put(key, status); IStatus current = getCurrentStatus(); synchronized (callback) { if (current != null && !callback.isDone()) { callback.done(this, current); } } } /** * Check if all the locks are unlocked and return a running status. * * @return a MultiStatus object describing running result or null if not completed yet. */ private synchronized IStatus getCurrentStatus() { List<IStatus> list = new ArrayList<IStatus>(); synchronized (locks) { for (Entry<Object, IStatus> entry : locks.entrySet()) { IStatus status = entry.getValue(); if (status == null) return null; list.add(status); } } IStatus[] children = list.toArray(new IStatus[list.size()]); return new MultiStatus(CoreBundleActivator.getUniqueIdentifier(), 0, children, Messages.CallbackMonitor_AllTasksFinished, null); } }