blob: 9484f447efdf0c9f299eb8254bb1048306721f47 [file] [log] [blame]
/*******************************************************************************
* 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);
}
}