Mercurial > projects > dwt-addons
changeset 122:9d0585bcb7aa
Add core.jobs package
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/internal/jobs/Deadlock.d Tue Aug 12 02:34:21 2008 +0200 @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2003, 2006 IBM Corporation 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: + * IBM - Initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.core.internal.jobs.Deadlock; + +import tango.core.Thread; + +import dwt.dwthelper.utils; + +import dwtx.core.runtime.jobs.ISchedulingRule; + +/** + * The deadlock class stores information about a deadlock that just occurred. + * It contains an array of the threads that were involved in the deadlock + * as well as the thread that was chosen to be suspended and an array of locks + * held by that thread that are going to be suspended to resolve the deadlock. + */ +class Deadlock { + //all the threads which are involved in the deadlock + private Thread[] threads; + //the thread whose locks will be suspended to resolve deadlock + private Thread candidate; + //the locks that will be suspended + private ISchedulingRule[] locks; + + public this(Thread[] threads, ISchedulingRule[] locks, Thread candidate) { + this.threads = threads; + this.locks = locks; + this.candidate = candidate; + } + + public ISchedulingRule[] getLocks() { + return locks; + } + + public Thread getCandidate() { + return candidate; + } + + public Thread[] getThreads() { + return threads; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/internal/jobs/DeadlockDetector.d Tue Aug 12 02:34:21 2008 +0200 @@ -0,0 +1,721 @@ +/******************************************************************************* + * Copyright (c) 2003, 2006 IBM Corporation 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: + * IBM Corporation - initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.core.internal.jobs.DeadlockDetector; + +import tango.core.Thread; +import tango.io.Stdout; +import tango.text.convert.Format; + +import dwt.dwthelper.utils; +import dwtx.dwtxhelper.Collection; + +import dwtx.core.internal.runtime.RuntimeLog; +import dwtx.core.runtime.Assert; +import dwtx.core.runtime.IStatus; +import dwtx.core.runtime.MultiStatus; +import dwtx.core.runtime.Status; +import dwtx.core.runtime.jobs.ILock; +import dwtx.core.runtime.jobs.ISchedulingRule; +import dwtx.core.internal.jobs.Deadlock; +import dwtx.core.internal.jobs.JobManager; + +/** + * Stores all the relationships between locks (rules are also considered locks), + * and the threads that own them. All the relationships are stored in a 2D integer array. + * The rows in the array are threads, while the columns are locks. + * Two corresponding arrayLists store the actual threads and locks. + * The index of a thread in the first arrayList is the index of the row in the graph. + * The index of a lock in the second arrayList is the index of the column in the graph. + * An entry greater than 0 in the graph is the number of times a thread in the entry's row + * acquired the lock in the entry's column. + * An entry of -1 means that the thread is waiting to acquire the lock. + * An entry of 0 means that the thread and the lock have no relationship. + * + * The difference between rules and locks is that locks can be suspended, while + * rules are implicit locks and as such cannot be suspended. + * To resolve deadlock, the graph will first try to find a thread that only owns + * locks. Failing that, it will find a thread in the deadlock that owns at least + * one lock and suspend it. + * + * Deadlock can only occur among locks, or among locks in combination with rules. + * Deadlock among rules only is impossible. Therefore, in any deadlock one can always + * find a thread that owns at least one lock that can be suspended. + * + * The implementation of the graph assumes that a thread can only own 1 rule at + * any one time. It can acquire that rule several times, but a thread cannot + * acquire 2 non-conflicting rules at the same time. + * + * The implementation of the graph will sometimes also find and resolve bogus deadlocks. + * graph: assuming this rule hierarchy: + * R2 R3 L1 R1 + * J1 1 0 0 / \ + * J2 0 1 -1 R2 R3 + * J3 -1 0 1 + * + * If in the above situation job4 decides to acquire rule1, then the graph will transform + * to the following: + * R2 R3 R1 L1 + * J1 1 0 1 0 + * J2 1 1 1 -1 + * J3 -1 0 0 1 + * J4 0 0 -1 0 + * + * and the graph will assume that job2 and job3 are deadlocked and suspend lock1 of job3. + * The reason the deadlock is bogus is that the deadlock is unlikely to actually happen (the threads + * are currently not deadlocked, but might deadlock later on when it is too late to detect it) + * Therefore, in order to make sure that no deadlock is possible, + * the deadlock will still be resolved at this point. + */ +class DeadlockDetector { + private static int NO_STATE = 0; + //state variables in the graph + private static int WAITING_FOR_LOCK = -1; + //empty matrix + private static const int[][] EMPTY_MATRIX = null; + //matrix of relationships between threads and locks + private int[][] graph = EMPTY_MATRIX; + //index is column in adjacency matrix for the lock + private final ArrayList locks; + //index is row in adjacency matrix for the thread + private final ArrayList lockThreads; + //whether the graph needs to be resized + private bool resize = false; + + public this(){ + locks = new ArrayList(); + lockThreads = new ArrayList(); + } + + /** + * Recursively check if any of the threads that prevent the current thread from running + * are actually deadlocked with the current thread. + * Add the threads that form deadlock to the deadlockedThreads list. + */ + private bool addCycleThreads(ArrayList deadlockedThreads, Thread next) { + //get the thread that block the given thread from running + Thread[] blocking = blockingThreads(next); + //if the thread is not blocked by other threads, then it is not part of a deadlock + if (blocking.length is 0) + return false; + bool inCycle = false; + for (int i = 0; i < blocking.length; i++) { + //if we have already visited the given thread, then we found a cycle + if (deadlockedThreads.contains(blocking[i])) { + inCycle = true; + } else { + //otherwise, add the thread to our list and recurse deeper + deadlockedThreads.add(blocking[i]); + //if the thread is not part of a cycle, remove it from the list + if (addCycleThreads(deadlockedThreads, blocking[i])) + inCycle = true; + else + deadlockedThreads.remove(blocking[i]); + } + } + return inCycle; + } + + /** + * Get the thread(s) that own the lock this thread is waiting for. + */ + private Thread[] blockingThreads(Thread current) { + //find the lock this thread is waiting for + ISchedulingRule lock = cast(ISchedulingRule) getWaitingLock(current); + return getThreadsOwningLock(lock); + } + + /** + * Check that the addition of a waiting thread did not produce deadlock. + * If deadlock is detected return true, else return false. + */ + private bool checkWaitCycles(int[] waitingThreads, int lockIndex) { + /** + * find the lock that this thread is waiting for + * recursively check if this is a cycle (i.e. a thread waiting on itself) + */ + for (int i = 0; i < graph.length; i++) { + if (graph[i][lockIndex] > NO_STATE) { + if (waitingThreads[i] > NO_STATE) { + return true; + } + //keep track that we already visited this thread + waitingThreads[i]++; + for (int j = 0; j < graph[i].length; j++) { + if (graph[i][j] is WAITING_FOR_LOCK) { + if (checkWaitCycles(waitingThreads, j)) + return true; + } + } + //this thread is not involved in a cycle yet, so remove the visited flag + waitingThreads[i]--; + } + } + return false; + } + + /** + * Returns true IFF the matrix contains a row for the given thread. + * (meaning the given thread either owns locks or is waiting for locks) + */ + bool contains(Thread t) { + return lockThreads.contains(t); + } + + /** + * A new rule was just added to the graph. + * Find a rule it conflicts with and update the new rule with the number of times + * it was acquired implicitly when threads acquired conflicting rule. + */ + private void fillPresentEntries(ISchedulingRule newLock, int lockIndex) { + //fill in the entries for the new rule from rules it conflicts with + for (int j = 0; j < locks.size(); j++) { + if ((j !is lockIndex) && (newLock.isConflicting(cast(ISchedulingRule) locks.get(j)))) { + for (int i = 0; i < graph.length; i++) { + if ((graph[i][j] > NO_STATE) && (graph[i][lockIndex] is NO_STATE)) + graph[i][lockIndex] = graph[i][j]; + } + } + } + //now back fill the entries for rules the current rule conflicts with + for (int j = 0; j < locks.size(); j++) { + if ((j !is lockIndex) && (newLock.isConflicting(cast(ISchedulingRule) locks.get(j)))) { + for (int i = 0; i < graph.length; i++) { + if ((graph[i][lockIndex] > NO_STATE) && (graph[i][j] is NO_STATE)) + graph[i][j] = graph[i][lockIndex]; + } + } + } + } + + /** + * Returns all the locks owned by the given thread + */ + private Object[] getOwnedLocks(Thread current) { + ArrayList ownedLocks = new ArrayList(1); + int index = indexOf(current, false); + + for (int j = 0; j < graph[index].length; j++) { + if (graph[index][j] > NO_STATE) + ownedLocks.add(locks.get(j)); + } + if (ownedLocks.size() is 0) + Assert.isLegal(false, "A thread with no locks is part of a deadlock."); //$NON-NLS-1$ + return ownedLocks.toArray(); + } + + /** + * Returns an array of threads that form the deadlock (usually 2). + */ + private Thread[] getThreadsInDeadlock(Thread cause) { + ArrayList deadlockedThreads = new ArrayList(2); + /** + * if the thread that caused deadlock doesn't own any locks, then it is not part + * of the deadlock (it just caused it because of a rule it tried to acquire) + */ + if (ownsLocks(cause)) + deadlockedThreads.add(cause); + addCycleThreads(deadlockedThreads, cause); + return arraycast!(Thread)( deadlockedThreads.toArray()); + } + + /** + * Returns the thread(s) that own the given lock. + */ + private Thread[] getThreadsOwningLock(ISchedulingRule rule) { + if (rule is null) + return new Thread[0]; + int lockIndex = indexOf(rule, false); + ArrayList blocking = new ArrayList(1); + for (int i = 0; i < graph.length; i++) { + if (graph[i][lockIndex] > NO_STATE) + blocking.add(lockThreads.get(i)); + } + if ((blocking.size() is 0) && (JobManager.DEBUG_LOCKS)) + Stdout.formatln(Format("Lock {} is involved in deadlock but is not owned by any thread.", rule )); //$NON-NLS-1$ //$NON-NLS-2$ + if ((blocking.size() > 1) && (cast(ILock)rule ) && (JobManager.DEBUG_LOCKS)) + Stdout.formatln(Format("Lock {} is owned by more than 1 thread, but it is not a rule.", rule )); //$NON-NLS-1$ //$NON-NLS-2$ + return arraycast!(Thread)( blocking.toArray()); + } + + /** + * Returns the lock the given thread is waiting for. + */ + private Object getWaitingLock(Thread current) { + int index = indexOf(current, false); + //find the lock that this thread is waiting for + for (int j = 0; j < graph[index].length; j++) { + if (graph[index][j] is WAITING_FOR_LOCK) + return locks.get(j); + } + //it can happen that a thread is not waiting for any lock (it is not really part of the deadlock) + return null; + } + + /** + * Returns the index of the given lock in the lock array. If the lock is + * not present in the array, it is added to the end. + */ + private int indexOf(ISchedulingRule lock, bool add) { + int index = locks.indexOf(cast(Object)lock); + if ((index < 0) && add) { + locks.add(cast(Object)lock); + resize = true; + index = locks.size() - 1; + } + return index; + } + + /** + * Returns the index of the given thread in the thread array. If the thread + * is not present in the array, it is added to the end. + */ + private int indexOf(Thread owner, bool add) { + int index = lockThreads.indexOf(owner); + if ((index < 0) && add) { + lockThreads.add(owner); + resize = true; + index = lockThreads.size() - 1; + } + return index; + } + + /** + * Returns true IFF the adjacency matrix is empty. + */ + bool isEmpty() { + return (locks.size() is 0) && (lockThreads.size() is 0) && (graph.length is 0); + } + + /** + * The given lock was acquired by the given thread. + */ + void lockAcquired(Thread owner, ISchedulingRule lock) { + int lockIndex = indexOf(lock, true); + int threadIndex = indexOf(owner, true); + if (resize) + resizeGraph(); + if (graph[threadIndex][lockIndex] is WAITING_FOR_LOCK) + graph[threadIndex][lockIndex] = NO_STATE; + /** + * acquire all locks that conflict with the given lock + * or conflict with a lock the given lock will acquire implicitly + * (locks are acquired implicitly when a conflicting lock is acquired) + */ + ArrayList conflicting = new ArrayList(1); + //only need two passes through all the locks to pick up all conflicting rules + int NUM_PASSES = 2; + conflicting.add(cast(Object)lock); + graph[threadIndex][lockIndex]++; + for (int i = 0; i < NUM_PASSES; i++) { + for (int k = 0; k < conflicting.size(); k++) { + ISchedulingRule current = cast(ISchedulingRule) conflicting.get(k); + for (int j = 0; j < locks.size(); j++) { + ISchedulingRule possible = cast(ISchedulingRule) locks.get(j); + if (current.isConflicting(possible) && !conflicting.contains(cast(Object)possible)) { + conflicting.add(cast(Object)possible); + graph[threadIndex][j]++; + } + } + } + } + } + + /** + * The given lock was released by the given thread. Update the graph. + */ + void lockReleased(Thread owner, ISchedulingRule lock) { + int lockIndex = indexOf(lock, false); + int threadIndex = indexOf(owner, false); + //make sure the lock and thread exist in the graph + if (threadIndex < 0) { + if (JobManager.DEBUG_LOCKS) + Stdout.formatln("[lockReleased] Lock {} was already released by thread {}", lock, owner.name()); //$NON-NLS-1$ //$NON-NLS-2$ + return; + } + if (lockIndex < 0) { + if (JobManager.DEBUG_LOCKS) + Stdout.formatln("[lockReleased] Thread {} already released lock {}", owner.name(), lock); //$NON-NLS-1$ //$NON-NLS-2$ + return; + } + //if this lock was suspended, set it to NO_STATE + if ((cast(ILock)lock ) && (graph[threadIndex][lockIndex] is WAITING_FOR_LOCK)) { + graph[threadIndex][lockIndex] = NO_STATE; + return; + } + //release all locks that conflict with the given lock + //or release all rules that are owned by the given thread, if we are releasing a rule + for (int j = 0; j < graph[threadIndex].length; j++) { + if ((lock.isConflicting(cast(ISchedulingRule) locks.get(j))) || (!(cast(ILock)lock ) && !(cast(ILock)locks.get(j)) && (graph[threadIndex][j] > NO_STATE))) { + if (graph[threadIndex][j] is NO_STATE) { + if (JobManager.DEBUG_LOCKS) + Stdout.formatln("[lockReleased] More releases than acquires for thread {} and lock {}", owner.name(), lock); //$NON-NLS-1$ //$NON-NLS-2$ + } else { + graph[threadIndex][j]--; + } + } + } + //if this thread just released the given lock, try to simplify the graph + if (graph[threadIndex][lockIndex] is NO_STATE) + reduceGraph(threadIndex, lock); + } + + /** + * The given scheduling rule is no longer used because the job that invoked it is done. + * Release this rule regardless of how many times it was acquired. + */ + void lockReleasedCompletely(Thread owner, ISchedulingRule rule) { + int ruleIndex = indexOf(rule, false); + int threadIndex = indexOf(owner, false); + //need to make sure that the given thread and rule were not already removed from the graph + if (threadIndex < 0) { + if (JobManager.DEBUG_LOCKS) + Stdout.formatln("[lockReleasedCompletely] Lock {} was already released by thread {}", rule, owner.name()); //$NON-NLS-1$ //$NON-NLS-2$ + return; + } + if (ruleIndex < 0) { + if (JobManager.DEBUG_LOCKS) + Stdout.formatln("[lockReleasedCompletely] Thread {} already released lock {}", owner.name(), rule); //$NON-NLS-1$ //$NON-NLS-2$ + return; + } + /** + * set all rules that are owned by the given thread to NO_STATE + * (not just rules that conflict with the rule we are releasing) + * if we are releasing a lock, then only update the one entry for the lock + */ + for (int j = 0; j < graph[threadIndex].length; j++) { + if (!(cast(ILock)locks.get(j) ) && (graph[threadIndex][j] > NO_STATE)) + graph[threadIndex][j] = NO_STATE; + } + reduceGraph(threadIndex, rule); + } + + /** + * The given thread could not get the given lock and is waiting for it. + * Update the graph. + */ + Deadlock lockWaitStart(Thread client, ISchedulingRule lock) { + setToWait(client, lock, false); + int lockIndex = indexOf(lock, false); + int[] temp = new int[lockThreads.size()]; + //check if the addition of the waiting thread caused deadlock + if (!checkWaitCycles(temp, lockIndex)) + return null; + //there is a deadlock in the graph + Thread[] threads = getThreadsInDeadlock(client); + Thread candidate = resolutionCandidate(threads); + ISchedulingRule[] locksToSuspend = realLocksForThread(candidate); + Deadlock deadlock = new Deadlock(threads, locksToSuspend, candidate); + //find a thread whose locks can be suspended to resolve the deadlock + if (JobManager.DEBUG_LOCKS) + reportDeadlock(deadlock); + if (JobManager.DEBUG_DEADLOCK) + throw new IllegalStateException(Format("Deadlock detected. Caused by thread {}.", client.name())); //$NON-NLS-1$ + // Update the graph to indicate that the locks will now be suspended. + // To indicate that the lock will be suspended, we set the thread to wait for the lock. + // When the lock is forced to be released, the entry will be cleared. + for (int i = 0; i < locksToSuspend.length; i++) + setToWait(deadlock.getCandidate(), locksToSuspend[i], true); + return deadlock; + } + + /** + * The given thread has stopped waiting for the given lock. + * Update the graph. + */ + void lockWaitStop(Thread owner, ISchedulingRule lock) { + int lockIndex = indexOf(lock, false); + int threadIndex = indexOf(owner, false); + //make sure the thread and lock exist in the graph + if (threadIndex < 0) { + if (JobManager.DEBUG_LOCKS) + Stdout.formatln("Thread {} was already removed.", owner.name() ); //$NON-NLS-1$ //$NON-NLS-2$ + return; + } + if (lockIndex < 0) { + if (JobManager.DEBUG_LOCKS) + Stdout.formatln("Lock {} was already removed.", lock ); //$NON-NLS-1$ //$NON-NLS-2$ + return; + } + if (graph[threadIndex][lockIndex] !is WAITING_FOR_LOCK) + Assert.isTrue(false, Format("Thread {} was not waiting for lock {} so it could not time out.", owner.name(), (cast(Object)lock).toString())); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + graph[threadIndex][lockIndex] = NO_STATE; + reduceGraph(threadIndex, lock); + } + + /** + * Returns true IFF the given thread owns a single lock + */ + private bool ownsLocks(Thread cause) { + int threadIndex = indexOf(cause, false); + for (int j = 0; j < graph[threadIndex].length; j++) { + if (graph[threadIndex][j] > NO_STATE) + return true; + } + return false; + } + + /** + * Returns true IFF the given thread owns a single real lock. + * A real lock is a lock that can be suspended. + */ + private bool ownsRealLocks(Thread owner) { + int threadIndex = indexOf(owner, false); + for (int j = 0; j < graph[threadIndex].length; j++) { + if (graph[threadIndex][j] > NO_STATE) { + Object lock = locks.get(j); + if (cast(ILock)lock ) + return true; + } + } + return false; + } + + /** + * Return true IFF this thread owns rule locks (i.e. implicit locks which + * cannot be suspended) + */ + private bool ownsRuleLocks(Thread owner) { + int threadIndex = indexOf(owner, false); + for (int j = 0; j < graph[threadIndex].length; j++) { + if (graph[threadIndex][j] > NO_STATE) { + Object lock = locks.get(j); + if (!(cast(ILock)lock )) + return true; + } + } + return false; + } + + /** + * Returns an array of real locks that are owned by the given thread. + * Real locks are locks that implement the ILock interface and can be suspended. + */ + private ISchedulingRule[] realLocksForThread(Thread owner) { + int threadIndex = indexOf(owner, false); + ArrayList ownedLocks = new ArrayList(1); + for (int j = 0; j < graph[threadIndex].length; j++) { + if ((graph[threadIndex][j] > NO_STATE) && (cast(ILock)locks.get(j) )) + ownedLocks.add(locks.get(j)); + } + if (ownedLocks.size() is 0) + Assert.isLegal(false, "A thread with no real locks was chosen to resolve deadlock."); //$NON-NLS-1$ + return arraycast!(ISchedulingRule)( ownedLocks.toArray()); + } + + /** + * The matrix has been simplified. Check if any unnecessary rows or columns + * can be removed. + */ + private void reduceGraph(int row, ISchedulingRule lock) { + int numLocks = locks.size(); + bool[] emptyColumns = new bool[numLocks]; + + /** + * find all columns that could possibly be empty + * (consist of locks which conflict with the given lock, or of locks which are rules) + */ + for (int j = 0; j < numLocks; j++) { + if ((lock.isConflicting(cast(ISchedulingRule) locks.get(j))) || !(cast(ILock)locks.get(j))) + emptyColumns[j] = true; + } + + bool rowEmpty = true; + int numEmpty = 0; + //check if the given row is empty + for (int j = 0; j < graph[row].length; j++) { + if (graph[row][j] !is NO_STATE) { + rowEmpty = false; + break; + } + } + /** + * Check if the possibly empty columns are actually empty. + * If a column is actually empty, remove the corresponding lock from the list of locks + * Start at the last column so that when locks are removed from the list, + * the index of the remaining locks is unchanged. Store the number of empty columns. + */ + for (int j = emptyColumns.length - 1; j >= 0; j--) { + for (int i = 0; i < graph.length; i++) { + if (emptyColumns[j] && (graph[i][j] !is NO_STATE)) { + emptyColumns[j] = false; + break; + } + } + if (emptyColumns[j]) { + locks.remove(j); + numEmpty++; + } + } + //if no columns or rows are empty, return right away + if ((numEmpty is 0) && (!rowEmpty)) + return; + + if (rowEmpty) + lockThreads.remove(row); + + //new graph (the list of locks and the list of threads are already updated) + final int numThreads = lockThreads.size(); + numLocks = locks.size(); + //optimize empty graph case + if (numThreads is 0 && numLocks is 0) { + graph = EMPTY_MATRIX; + return; + } + int[][] tempGraph = new int[][](numThreads,numLocks); + + //the number of rows we need to skip to get the correct entry from the old graph + int numRowsSkipped = 0; + for (int i = 0; i < graph.length - numRowsSkipped; i++) { + if ((i is row) && rowEmpty) { + numRowsSkipped++; + //check if we need to skip the last row + if (i >= graph.length - numRowsSkipped) + break; + } + //the number of columns we need to skip to get the correct entry from the old graph + //needs to be reset for every new row + int numColsSkipped = 0; + for (int j = 0; j < graph[i].length - numColsSkipped; j++) { + while (emptyColumns[j + numColsSkipped]) { + numColsSkipped++; + //check if we need to skip the last column + if (j >= graph[i].length - numColsSkipped) + break; + } + //need to break out of the outer loop + if (j >= graph[i].length - numColsSkipped) + break; + tempGraph[i][j] = graph[i + numRowsSkipped][j + numColsSkipped]; + } + } + graph = tempGraph; + Assert.isTrue(numThreads is graph.length, "Rows and threads don't match."); //$NON-NLS-1$ + Assert.isTrue(numLocks is ((graph.length > 0) ? graph[0].length : 0), "Columns and locks don't match."); //$NON-NLS-1$ + } + + /** + * Adds a 'deadlock detected' message to the log with a stack trace. + */ + private void reportDeadlock(Deadlock deadlock) { + String msg = "Deadlock detected. All locks owned by thread " ~ deadlock.getCandidate().name() ~ " will be suspended."; //$NON-NLS-1$ //$NON-NLS-2$ + MultiStatus main = new MultiStatus(JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, msg, new IllegalStateException()); + Thread[] threads = deadlock.getThreads(); + for (int i = 0; i < threads.length; i++) { + Object[] ownedLocks = getOwnedLocks(threads[i]); + Object waitLock = getWaitingLock(threads[i]); + StringBuffer buf = new StringBuffer("Thread "); //$NON-NLS-1$ + buf.append(threads[i].name()); + buf.append(" has locks: "); //$NON-NLS-1$ + for (int j = 0; j < ownedLocks.length; j++) { + buf.append(Format("{}",ownedLocks[j])); + buf.append((j < ownedLocks.length - 1) ? ", " : " "); //$NON-NLS-1$ //$NON-NLS-2$ + } + buf.append("and is waiting for lock "); //$NON-NLS-1$ + buf.append(Format("{}",waitLock)); + Status child = new Status(IStatus.ERROR, JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, buf.toString(), null); + main.add(child); + } + RuntimeLog.log(main); + } + + /** + * The number of threads/locks in the graph has changed. Update the + * underlying matrix. + */ + private void resizeGraph() { + // a new row and/or a new column was added to the graph. + // since new rows/columns are always added to the end, just transfer + // old entries to the new graph, with the same indices. + final int newRows = lockThreads.size(); + final int newCols = locks.size(); + //optimize 0x0 and 1x1 matrices + if (newRows is 0 && newCols is 0) { + graph = EMPTY_MATRIX; + return; + } + int[][] tempGraph = new int[][](newRows,newCols); + for (int i = 0; i < graph.length; i++) + System.arraycopy(graph[i], 0, tempGraph[i], 0, graph[i].length); + graph = tempGraph; + resize = false; + } + + /** + * Get the thread whose locks can be suspended. (i.e. all locks it owns are + * actual locks and not rules). Return the first thread in the array by default. + */ + private Thread resolutionCandidate(Thread[] candidates) { + //first look for a candidate that has no scheduling rules + for (int i = 0; i < candidates.length; i++) { + if (!ownsRuleLocks(candidates[i])) + return candidates[i]; + } + //next look for any candidate with a real lock (a lock that can be suspended) + for (int i = 0; i < candidates.length; i++) { + if (ownsRealLocks(candidates[i])) + return candidates[i]; + } + //unnecessary, return the first entry in the array by default + return candidates[0]; + } + + /** + * The given thread is waiting for the given lock. Update the graph. + */ + private void setToWait(Thread owner, ISchedulingRule lock, bool suspend) { + bool needTransfer = false; + /** + * if we are adding an entry where a thread is waiting on a scheduling rule, + * then we need to transfer all positive entries for a conflicting rule to the + * newly added rule in order to synchronize the graph. + */ + if (!suspend && !(cast(ILock)lock)) + needTransfer = true; + int lockIndex = indexOf(lock, !suspend); + int threadIndex = indexOf(owner, !suspend); + if (resize) + resizeGraph(); + + graph[threadIndex][lockIndex] = WAITING_FOR_LOCK; + if (needTransfer) + fillPresentEntries(lock, lockIndex); + } + + /** + * Prints out the current matrix to standard output. + * Only used for debugging. + */ + public String toDebugString() { + StringBuffer sb = new StringBuffer(); + sb.append(" :: \n"); //$NON-NLS-1$ + for (int j = 0; j < locks.size(); j++) { + sb.append(" "); + sb.append( locks.get(j).toString ); + sb.append(","); + } + sb.append("\n"); + for (int i = 0; i < graph.length; i++) { + sb.append(" "); + sb.append( (cast(Thread) lockThreads.get(i)).name() ); + sb.append(" : "); + for (int j = 0; j < graph[i].length; j++) { + sb.append(" "); + sb.append(Integer.toString(graph[i][j])); //$NON-NLS-1$ + sb.append(","); + } + sb.append("\n"); + } + sb.append("-------\n"); //$NON-NLS-1$ + return sb.toString(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/internal/jobs/ImplicitJobs.d Tue Aug 12 02:34:21 2008 +0200 @@ -0,0 +1,279 @@ +/******************************************************************************* + * Copyright (c) 2003, 2006 IBM Corporation 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: + * IBM - Initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.core.internal.jobs.ImplicitJobs; + +import tango.text.convert.Format; +import tango.core.Thread; +import tango.io.Stdout; +import dwt.dwthelper.utils; +import dwtx.dwtxhelper.Collection; + +import dwtx.core.internal.runtime.RuntimeLog; +import dwtx.core.runtime.Assert; +import dwtx.core.runtime.IProgressMonitor; +import dwtx.core.runtime.IStatus; +import dwtx.core.runtime.Status; +import dwtx.core.runtime.jobs.ISchedulingRule; +import dwtx.core.runtime.jobs.Job; + +import dwtx.core.internal.jobs.JobManager; +import dwtx.core.internal.jobs.ThreadJob; +import dwtx.core.internal.jobs.InternalJob; + +/** + * Implicit jobs are jobs that are running by virtue of a JobManager.begin/end + * pair. They act like normal jobs, except they are tied to an arbitrary thread + * of the client's choosing, and they can be nested. + */ +class ImplicitJobs { + /** + * Cached unused instance that can be reused + */ + private ThreadJob jobCache = null; + protected JobManager manager; + + /** + * Set of suspended scheduling rules. + */ + private const Set suspendedRules; + + /** + * Maps (Thread->ThreadJob), threads to the currently running job for that + * thread. + */ + private final Map threadJobs; + + this(JobManager manager) { + this.manager = manager; + suspendedRules = new HashSet(20); + threadJobs = new HashMap(20); + } + + /* (Non-javadoc) + * @see IJobManager#beginRule + */ + void begin(ISchedulingRule rule, IProgressMonitor monitor, bool suspend) { + if (JobManager.DEBUG_BEGIN_END) + JobManager.debug_(Format("Begin rule: {}", rule)); //$NON-NLS-1$ + final Thread getThis = Thread.getThis(); + ThreadJob threadJob; + synchronized (this) { + threadJob = cast(ThreadJob) threadJobs.get(getThis); + if (threadJob !is null) { + //nested rule, just push on stack and return + threadJob.push(rule); + return; + } + //no need to schedule a thread job for a null rule + if (rule is null) + return; + //create a thread job for this thread, use the rule from the real job if it has one + Job realJob = manager.currentJob(); + if (realJob !is null && realJob.getRule() !is null) + threadJob = newThreadJob(realJob.getRule()); + else { + threadJob = newThreadJob(rule); + threadJob.acquireRule = true; + } + //don't acquire rule if it is a suspended rule + if (isSuspended(rule)) + threadJob.acquireRule = false; + //indicate if it is a system job to ensure isBlocking works correctly + threadJob.setRealJob(realJob); + threadJob.setThread(getThis); + } + try { + threadJob.push(rule); + //join the thread job outside sync block + if (threadJob.acquireRule) { + //no need to re-acquire any locks because the thread did not wait to get this lock + if (manager.runNow_package(threadJob)) + manager.getLockManager().addLockThread(Thread.getThis(), rule); + else + threadJob = threadJob.joinRun(monitor); + } + } finally { + //remember this thread job - only do this + //after the rule is acquired because it is ok for this thread to acquire + //and release other rules while waiting. + synchronized (this) { + threadJobs.put(getThis, threadJob); + if (suspend) + suspendedRules.add(cast(Object)rule); + } + if (threadJob.isBlocked) { + threadJob.isBlocked = false; + manager.reportUnblocked(monitor); + } + } + } + + /* (Non-javadoc) + * @see IJobManager#endRule + */ + synchronized void end(ISchedulingRule rule, bool resume) { + if (JobManager.DEBUG_BEGIN_END) + JobManager.debug_(Format("End rule: {}", rule)); //$NON-NLS-1$ + ThreadJob threadJob = cast(ThreadJob) threadJobs.get(Thread.getThis()); + if (threadJob is null) + Assert.isLegal(rule is null, Format("endRule without matching beginRule: {}", rule)); //$NON-NLS-1$ + else if (threadJob.pop(rule)) { + endThreadJob(threadJob, resume); + } + } + + /** + * Called when a worker thread has finished running a job. At this + * point, the worker thread must not own any scheduling rules + * @param lastJob The last job to run in this thread + */ + void endJob(InternalJob lastJob) { + final Thread getThis = Thread.getThis(); + IStatus error; + synchronized (this) { + ThreadJob threadJob = cast(ThreadJob) threadJobs.get(getThis); + if (threadJob is null) { + if (lastJob.getRule() !is null) + notifyWaitingThreadJobs(); + return; + } + String msg = Format("Worker thread ended job: {}, but still holds rule: {}", lastJob, threadJob ); //$NON-NLS-1$ //$NON-NLS-2$ + error = new Status(IStatus.ERROR, JobManager.PI_JOBS, 1, msg, null); + //end the thread job + endThreadJob(threadJob, false); + } + try { + RuntimeLog.log(error); + } catch (RuntimeException e) { + //failed to log, so print to console instead + Stderr.formatln("{}", error.getMessage()); + } + } + + private void endThreadJob(ThreadJob threadJob, bool resume) { + Thread getThis = Thread.getThis(); + //clean up when last rule scope exits + threadJobs.remove(getThis); + ISchedulingRule rule = threadJob.getRule(); + if (resume && rule !is null) + suspendedRules.remove(cast(Object)rule); + //if this job had a rule, then we are essentially releasing a lock + //note it is safe to do this even if the acquire was aborted + if (threadJob.acquireRule) { + manager.getLockManager().removeLockThread(getThis, rule); + notifyWaitingThreadJobs(); + } + //if the job was started, we need to notify job manager to end it + if (threadJob.isRunning()) + manager.endJob_package(threadJob, Status.OK_STATUS, false); + recycle(threadJob); + } + + /** + * Returns true if this rule has been suspended, and false otherwise. + */ + private bool isSuspended(ISchedulingRule rule) { + if (suspendedRules.size() is 0) + return false; + for (Iterator it = suspendedRules.iterator(); it.hasNext();) + if ((cast(ISchedulingRule) it.next()).contains(rule)) + return true; + return false; + } + + /** + * Returns a new or reused ThreadJob instance. + */ + private ThreadJob newThreadJob(ISchedulingRule rule) { + if (jobCache !is null) { + ThreadJob job = jobCache; + job.setRule(rule); + job.acquireRule = job.isRunning_ = false; + job.realJob = null; + jobCache = null; + return job; + } + return new ThreadJob(manager, rule); + } + + /** + * A job has just finished that was holding a scheduling rule, and the + * scheduling rule is now free. Wake any blocked thread jobs so they can + * compete for the newly freed lock + */ + private void notifyWaitingThreadJobs() { + synchronized (ThreadJob.mutex) { + ThreadJob.condition.notifyAll(); + } + } + + /** + * Indicates that a thread job is no longer in use and can be reused. + */ + private void recycle(ThreadJob job) { + if (jobCache is null && job.recycle()) + jobCache = job; + } + + /** + * Implements IJobManager#resume(ISchedulingRule) + * @param rule + */ + void resume(ISchedulingRule rule) { + //resume happens as a consequence of freeing the last rule in the stack + end(rule, true); + if (JobManager.DEBUG_BEGIN_END) + JobManager.debug_(Format("Resume rule: {}", rule)); //$NON-NLS-1$ + } + + /** + * Implements IJobManager#suspend(ISchedulingRule, IProgressMonitor) + * @param rule + * @param monitor + */ + void suspend(ISchedulingRule rule, IProgressMonitor monitor) { + if (JobManager.DEBUG_BEGIN_END) + JobManager.debug_(Format("Suspend rule: {}", rule)); //$NON-NLS-1$ + //the suspend job will be remembered once the rule is acquired + begin(rule, monitor, true); + } + + /** + * Implements IJobManager#transferRule(ISchedulingRule, Thread) + */ + synchronized void transfer(ISchedulingRule rule, Thread destinationThread) { + //nothing to do for null + if (rule is null) + return; + Thread getThis = Thread.getThis(); + //nothing to do if transferring to the same thread + if (getThis is destinationThread) + return; + //ensure destination thread doesn't already have a rule + ThreadJob job = cast(ThreadJob) threadJobs.get(destinationThread); + Assert.isLegal(job is null); + //ensure calling thread owns the job being transferred + job = cast(ThreadJob) threadJobs.get(getThis); + Assert.isNotNull(job); + Assert.isLegal(job.getRule() is rule); + //transfer the thread job without ending it + job.setThread(destinationThread); + threadJobs.remove(getThis); + threadJobs.put(destinationThread, job); + //transfer lock + if (job.acquireRule) { + manager.getLockManager().removeLockThread(getThis, rule); + manager.getLockManager().addLockThread(destinationThread, rule); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/internal/jobs/InternalJob.d Tue Aug 12 02:34:21 2008 +0200 @@ -0,0 +1,628 @@ +/******************************************************************************* + * Copyright (c) 2003, 2007 IBM Corporation 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: + * IBM - Initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.core.internal.jobs.InternalJob; + +import dwt.dwthelper.utils; +import dwtx.dwtxhelper.Collection; + +import dwtx.core.runtime.Assert; +import dwtx.core.runtime.IProgressMonitor; +import dwtx.core.runtime.IStatus; +import dwtx.core.runtime.ListenerList; +import dwtx.core.runtime.PlatformObject; +import dwtx.core.runtime.QualifiedName; +import dwtx.core.runtime.jobs.IJobChangeListener; +import dwtx.core.runtime.jobs.ISchedulingRule; +import dwtx.core.runtime.jobs.Job; +import dwtx.core.runtime.jobs.MultiRule; + +import dwtx.core.internal.jobs.JobManager; +import dwtx.core.internal.jobs.ObjectMap; + +import tango.core.Thread; +import tango.text.convert.Format; + +/** + * Internal implementation class for jobs. Clients must not implement this class + * directly. All jobs must be subclasses of the API <code>dwtx.core.runtime.jobs.Job</code> class. + */ +public abstract class InternalJob : PlatformObject, Comparable { + /** + * Job state code (value 16) indicating that a job has been removed from + * the wait queue and is about to start running. From an API point of view, + * this is the same as RUNNING. + */ + static const int ABOUT_TO_RUN = 0x10; + + /** + * Job state code (value 32) indicating that a job has passed scheduling + * precondition checks and is about to be added to the wait queue. From an API point of view, + * this is the same as WAITING. + */ + static const int ABOUT_TO_SCHEDULE = 0x20; + /** + * Job state code (value 8) indicating that a job is blocked by another currently + * running job. From an API point of view, this is the same as WAITING. + */ + static const int BLOCKED = 0x08; + + //flag mask bits + private static const int M_STATE = 0xFF; + private static const int M_SYSTEM = 0x0100; + private static const int M_USER = 0x0200; + + /* + * flag on a job indicating that it was about to run, but has been canceled + */ + private static const int M_ABOUT_TO_RUN_CANCELED = 0x0400; + + private static JobManager manager_; + protected static JobManager manager(){ + if( manager_ is null ){ + synchronized( InternalJob.classinfo ){ + if( manager_ is null ){ + manager_ = JobManager.getInstance(); + } + } + } + return manager_; + } + private static int nextJobNumber = 0; + + /** + * Start time constant indicating a job should be started at + * a time in the infinite future, causing it to sleep forever. + */ + static const long T_INFINITE = Long.MAX_VALUE; + /** + * Start time constant indicating that the job has no start time. + */ + static const long T_NONE = -1; + + private /+volatile+/ int flags = Job.NONE; + private const int jobNumber; + private ListenerList listeners = null; + private IProgressMonitor monitor; + private String name; + /** + * The job ahead of me in a queue or list. + */ + private InternalJob next_; + /** + * The job behind me in a queue or list. + */ + private InternalJob previous_; + private int priority = Job.LONG; + /** + * Arbitrary properties (key,value) pairs, attached + * to a job instance by a third party. + */ + private ObjectMap properties; + private IStatus result; + private ISchedulingRule schedulingRule; + /** + * If the job is waiting, this represents the time the job should start by. + * If this job is sleeping, this represents the time the job should wake up. + * If this job is running, this represents the delay automatic rescheduling, + * or -1 if the job should not be rescheduled. + */ + private long startTime; + + /** + * Stamp added when a job is added to the wait queue. Used to ensure + * jobs in the wait queue maintain their insertion order even if they are + * removed from the wait queue temporarily while blocked + */ + private long waitQueueStamp = T_NONE; + + /* + * The thread that is currently running this job + */ + private /+volatile+/ Thread thread = null; + + protected this(String name) { + Assert.isNotNull(name); + jobNumber = nextJobNumber++; + this.name = name; + } + + /* (non-Javadoc) + * @see Job#addJobListener(IJobChangeListener) + */ + protected void addJobChangeListener(IJobChangeListener listener) { + if (listeners is null) + listeners = new ListenerList(ListenerList.IDENTITY); + listeners.add(cast(Object)listener); + } + package void addJobChangeListener_package(IJobChangeListener listener) { + addJobChangeListener(listener); + } + + /** + * Adds an entry at the end of the list of which this item is the head. + */ + final void addLast(InternalJob entry) { + InternalJob last = this; + //find the end of the queue + while (last.previous_ !is null) + last = last.previous_; + //add the new entry to the end of the queue + last.previous_ = entry; + entry.next_ = last; + entry.previous_ = null; + } + + /* (non-Javadoc) + * @see Job#belongsTo(Object) + */ + protected bool belongsTo(Object family) { + return false; + } + package bool belongsTo_package(Object family) { + return belongsTo(family); + } + + /* (non-Javadoc) + * @see Job#cancel() + */ + protected package bool cancel() { + return manager.cancel_package(this); + } + + /* (non-Javadoc) + * @see Job#canceling() + */ + protected package void canceling() { + //default implementation does nothing + } + + /* (on-Javadoc) + * @see java.lang.Comparable#compareTo(java.lang.Object) + */ + public final int compareTo(Object otherJob) { + return (cast(InternalJob) otherJob).startTime >= startTime ? 1 : -1; + } + + /* (non-Javadoc) + * @see Job#done(IStatus) + */ + protected void done(IStatus endResult) { + manager.endJob_package(this, endResult, true); + } + + /** + * Returns the job listeners that are only listening to this job. Returns + * <code>null</code> if this job has no listeners. + */ + final ListenerList getListeners() { + return listeners; + } + + /* (non-Javadoc) + * @see Job#getName() + */ + protected String getName() { + return name; + } + package String getName_package() { + return name; + } + + /* (non-Javadoc) + * @see Job#getPriority() + */ + protected package int getPriority() { + return priority; + } + + /** + * Returns the job's progress monitor, or null if it is not running. + */ + final IProgressMonitor getProgressMonitor() { + return monitor; + } + + /* (non-Javadoc) + * @see Job#getProperty + */ + protected Object getProperty(QualifiedName key) { + // thread safety: (Concurrency001 - copy on write) + Map temp = properties; + if (temp is null) + return null; + return temp.get(key); + } + + /* (non-Javadoc) + * @see Job#getResult + */ + protected IStatus getResult() { + return result; + } + + /* (non-Javadoc) + * @see Job#getRule + */ + protected package ISchedulingRule getRule() { + return schedulingRule; + } + package ISchedulingRule getRule_package() { + return getRule(); + } + + /** + * Returns the time that this job should be started, awakened, or + * rescheduled, depending on the current state. + * @return time in milliseconds + */ + final long getStartTime() { + return startTime; + } + + /* (non-Javadoc) + * @see Job#getState() + */ + protected int getState() { + int state = flags & M_STATE; + switch (state) { + //blocked state is equivalent to waiting state for clients + case BLOCKED : + return Job.WAITING; + case ABOUT_TO_RUN : + return Job.RUNNING; + case ABOUT_TO_SCHEDULE : + return Job.WAITING; + default : + return state; + } + } + package int getState_package() { + return getState(); + } + + /* (non-javadoc) + * @see Job.getThread + */ + protected Thread getThread() { + return thread; + } + package Thread getThread_package() { + return getThread(); + } + + /** + * Returns the raw job state, including internal states no exposed as API. + */ + final int internalGetState() { + return flags & M_STATE; + } + + /** + * Must be called from JobManager#setPriority + */ + final void internalSetPriority(int newPriority) { + this.priority = newPriority; + } + + /** + * Must be called from JobManager#setRule + */ + final void internalSetRule(ISchedulingRule rule) { + this.schedulingRule = rule; + } + + /** + * Must be called from JobManager#changeState + */ + final void internalSetState(int i) { + flags = (flags & ~M_STATE) | i; + } + + /** + * Returns whether this job was canceled when it was about to run + */ + final bool isAboutToRunCanceled() { + return (flags & M_ABOUT_TO_RUN_CANCELED) !is 0; + } + + /* (non-Javadoc) + * @see Job#isBlocking() + */ + protected bool isBlocking() { + return manager.isBlocking_package(this); + } + + /** + * Returns true if this job conflicts with the given job, and false otherwise. + */ + final bool isConflicting(InternalJob otherJob) { + ISchedulingRule otherRule = otherJob.getRule(); + if (schedulingRule is null || otherRule is null) + return false; + //if one of the rules is a compound rule, it must be asked the question. + if (schedulingRule.classinfo is MultiRule.classinfo) + return schedulingRule.isConflicting(otherRule); + return otherRule.isConflicting(schedulingRule); + } + + /* (non-javadoc) + * @see Job.isSystem() + */ + protected bool isSystem() { + return (flags & M_SYSTEM) !is 0; + } + package bool isSystem_package() { + return isSystem(); + } + + /* (non-javadoc) + * @see Job.isUser() + */ + protected bool isUser() { + return (flags & M_USER) !is 0; + } + + /* (non-Javadoc) + * @see Job#join() + */ + protected void join() { + manager.join_package(this); + } + + /** + * Returns the next_ entry (ahead of this one) in the list, or null if there is no next_ entry + */ + final InternalJob next() { + return next_; + } + + /** + * Returns the previous_ entry (behind this one) in the list, or null if there is no previous_ entry + */ + final InternalJob previous() { + return previous_; + } + + /** + * Removes this entry from any list it belongs to. Returns the receiver. + */ + final InternalJob remove() { + if (next_ !is null) + next_.setPrevious(previous_); + if (previous_ !is null) + previous_.setNext(next_); + next_ = previous_ = null; + return this; + } + + /* (non-Javadoc) + * @see Job#removeJobListener(IJobChangeListener) + */ + protected void removeJobChangeListener(IJobChangeListener listener) { + if (listeners !is null) + listeners.remove(cast(Object)listener); + } + package void removeJobChangeListener_package(IJobChangeListener listener) { + removeJobChangeListener(listener); + } + + /* (non-Javadoc) + * @see Job#run(IProgressMonitor) + */ + protected abstract IStatus run(IProgressMonitor progressMonitor); + package IStatus run_package(IProgressMonitor progressMonitor){ + return run(progressMonitor); + } + + /* (non-Javadoc) + * @see Job#schedule(long) + */ + protected void schedule(long delay) { + if (shouldSchedule()) + manager.schedule_package(this, delay, false); + } + package void schedule_package(long delay) { + schedule(delay); + } + + /** + * Sets whether this job was canceled when it was about to run + */ + final void setAboutToRunCanceled(bool value) { + flags = value ? flags | M_ABOUT_TO_RUN_CANCELED : flags & ~M_ABOUT_TO_RUN_CANCELED; + + } + + /* (non-Javadoc) + * @see Job#setName(String) + */ + protected void setName(String name) { + Assert.isNotNull(name); + this.name = name; + } + + /** + * Sets the next_ entry in this linked list of jobs. + * @param entry + */ + final void setNext(InternalJob entry) { + this.next_ = entry; + } + + /** + * Sets the previous_ entry in this linked list of jobs. + * @param entry + */ + final void setPrevious(InternalJob entry) { + this.previous_ = entry; + } + + /* (non-Javadoc) + * @see Job#setPriority(int) + */ + protected void setPriority(int newPriority) { + switch (newPriority) { + case Job.INTERACTIVE : + case Job.SHORT : + case Job.LONG : + case Job.BUILD : + case Job.DECORATE : + manager.setPriority_package(this, newPriority); + break; + default : + throw new IllegalArgumentException(Integer.toString(newPriority)); + } + } + + /* (non-Javadoc) + * @see Job#setProgressGroup(IProgressMonitor, int) + */ + protected void setProgressGroup(IProgressMonitor group, int ticks) { + Assert.isNotNull(cast(Object)group); + IProgressMonitor pm = manager.createMonitor_package(this, group, ticks); + if (pm !is null) + setProgressMonitor(pm); + } + + /** + * Sets the progress monitor to use for the next_ execution of this job, + * or for clearing the monitor when a job completes. + * @param monitor a progress monitor + */ + final void setProgressMonitor(IProgressMonitor monitor) { + this.monitor = monitor; + } + + /* (non-Javadoc) + * @see Job#setProperty(QualifiedName,Object) + */ + protected void setProperty(QualifiedName key, Object value) { + // thread safety: (Concurrency001 - copy on write) + if (value is null) { + if (properties is null) + return; + ObjectMap temp = cast(ObjectMap) properties.clone(); + temp.remove(key); + if (temp.isEmpty()) + properties = null; + else + properties = temp; + } else { + ObjectMap temp = properties; + if (temp is null) + temp = new ObjectMap(5); + else + temp = cast(ObjectMap) properties.clone(); + temp.put(key, value); + properties = temp; + } + } + + /** + * Sets or clears the result of an execution of this job. + * @param result a result status, or <code>null</code> + */ + final void setResult(IStatus result) { + this.result = result; + } + + /* (non-Javadoc) + * @see Job#setRule(ISchedulingRule) + */ + protected void setRule(ISchedulingRule rule) { + manager.setRule(this, rule); + } + + /** + * Sets a time to start, wake up, or schedule this job, + * depending on the current state + * @param time a time in milliseconds + */ + final void setStartTime(long time) { + startTime = time; + } + + /* (non-javadoc) + * @see Job.setSystem + */ + protected void setSystem(bool value) { + if (getState() !is Job.NONE) + throw new IllegalStateException(); + flags = value ? flags | M_SYSTEM : flags & ~M_SYSTEM; + } + + /* (non-javadoc) + * @see Job.setThread + */ + protected void setThread(Thread thread) { + this.thread = thread; + } + package void setThread_package(Thread thread) { + setThread(thread); + } + + /* (non-javadoc) + * @see Job.setUser + */ + protected void setUser(bool value) { + if (getState() !is Job.NONE) + throw new IllegalStateException(); + flags = value ? flags | M_USER : flags & ~M_USER; + } + + /* (Non-javadoc) + * @see Job#shouldSchedule + */ + protected bool shouldSchedule() { + return true; + } + package bool shouldSchedule_package() { + return shouldSchedule(); + } + + /* (non-Javadoc) + * @see Job#sleep() + */ + protected bool sleep() { + return manager.sleep_package(this); + } + + /* (non-Javadoc) + * Prints a string-based representation of this job instance. + * For debugging purposes only. + */ + public String toString() { + return Format( "{}({})", getName(), jobNumber ); //$NON-NLS-1$//$NON-NLS-2$ + } + + /* (non-Javadoc) + * @see Job#wakeUp(long) + */ + protected void wakeUp(long delay) { + manager.wakeUp_package(this, delay); + } + + /** + * @param waitQueueStamp The waitQueueStamp to set. + */ + void setWaitQueueStamp(long waitQueueStamp) { + this.waitQueueStamp = waitQueueStamp; + } + + /** + * @return Returns the waitQueueStamp. + */ + long getWaitQueueStamp() { + return waitQueueStamp; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/internal/jobs/JobActivator.d Tue Aug 12 02:34:21 2008 +0200 @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2005, 2006 IBM Corporation 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: + * IBM Corporation - initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.core.internal.jobs.JobActivator; + +import dwt.dwthelper.utils; + +// import org.osgi.framework.BundleActivator; +// import org.osgi.framework.BundleContext; +// import org.osgi.framework.ServiceRegistration; + +import dwtx.core.runtime.jobs.IJobManager; + +/** + * The Jobs plugin class. + */ +public class JobActivator /+: BundleActivator+/ { + + /** + * Eclipse property. Set to <code>false</code> to avoid registering JobManager + * as an OSGi service. + */ + private static const String PROP_REGISTER_JOB_SERVICE = "eclipse.service.jobs"; //$NON-NLS-1$ +/++ + /** + * The bundle associated this plug-in + */ + private static BundleContext bundleContext; + + /** + * This plugin provides a JobManager service. + */ + private ServiceRegistration jobManagerService = null; + + /** + * This method is called upon plug-in activation + */ + public void start(BundleContext context) throws Exception { + bundleContext = context; + JobOSGiUtils.getDefault().openServices(); + + bool shouldRegister = !"false".equalsIgnoreCase(context.getProperty(PROP_REGISTER_JOB_SERVICE)); //$NON-NLS-1$ + if (shouldRegister) + registerServices(); + } + + /** + * This method is called when the plug-in is stopped + */ + public void stop(BundleContext context) throws Exception { + unregisterServices(); + JobManager.shutdown(); + JobOSGiUtils.getDefault().closeServices(); + bundleContext = null; + } + + static BundleContext getContext() { + return bundleContext; + } + + private void registerServices() { + jobManagerService = bundleContext.registerService(IJobManager.class.getName(), JobManager.getInstance(), new Hashtable()); + } + + private void unregisterServices() { + if (jobManagerService !is null) { + jobManagerService.unregister(); + jobManagerService = null; + } + } +++/ +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/internal/jobs/JobChangeEvent.d Tue Aug 12 02:34:21 2008 +0200 @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2003, 2005 IBM Corporation 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: + * IBM - Initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.core.internal.jobs.JobChangeEvent; + +import dwt.dwthelper.utils; + +import dwtx.core.runtime.IStatus; +import dwtx.core.runtime.jobs.IJobChangeEvent; +import dwtx.core.runtime.jobs.Job; + +public class JobChangeEvent : IJobChangeEvent { + /** + * The job on which this event occurred. + */ + Job job = null; + /** + * The result returned by the job's run method, or <code>null</code> if + * not applicable. + */ + IStatus result = null; + /** + * The amount of time to wait after scheduling the job before it should be run, + * or <code>-1</code> if not applicable for this type of event. + */ + long delay = -1; + /** + * Whether this job is being immediately rescheduled. + */ + bool reschedule = false; + + /* (non-Javadoc) + * Method declared on IJobChangeEvent + */ + public long getDelay() { + return delay; + } + + /* (non-Javadoc) + * Method declared on IJobChangeEvent + */ + public Job getJob() { + return job; + } + + /* (non-Javadoc) + * Method declared on IJobChangeEvent + */ + public IStatus getResult() { + return result; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/internal/jobs/JobListeners.d Tue Aug 12 02:34:21 2008 +0200 @@ -0,0 +1,192 @@ +/******************************************************************************* + * Copyright (c) 2003, 2006 IBM Corporation 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: + * IBM - Initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.core.internal.jobs.JobListeners; + +import dwt.dwthelper.utils; + +import dwtx.core.internal.runtime.RuntimeLog; +import dwtx.core.runtime.IStatus; +import dwtx.core.runtime.ListenerList; +import dwtx.core.runtime.OperationCanceledException; +import dwtx.core.runtime.Status; +import dwtx.core.runtime.jobs.IJobChangeEvent; +import dwtx.core.runtime.jobs.IJobChangeListener; +import dwtx.core.runtime.jobs.Job; +import dwtx.osgi.util.NLS; +import dwtx.core.internal.jobs.JobChangeEvent; +import dwtx.core.internal.jobs.InternalJob; +import dwtx.core.internal.jobs.JobOSGiUtils; +import dwtx.core.internal.jobs.JobManager; +import dwtx.core.internal.jobs.JobMessages; + +/** + * Responsible for notifying all job listeners about job lifecycle events. Uses a + * specialized iterator to ensure the complex iteration logic is contained in one place. + */ +class JobListeners { + interface IListenerDoit { + public void notify(IJobChangeListener listener, IJobChangeEvent event); + } + + private const IListenerDoit aboutToRun_; + private const IListenerDoit awake_; + private const IListenerDoit done_; + private const IListenerDoit running_; + private const IListenerDoit scheduled_; + private const IListenerDoit sleeping_; + /** + * The global job listeners. + */ + protected const ListenerList global; + + this(){ + aboutToRun_ = new class IListenerDoit { + public void notify(IJobChangeListener listener, IJobChangeEvent event) { + listener.aboutToRun(event); + } + }; + awake_ = new class IListenerDoit { + public void notify(IJobChangeListener listener, IJobChangeEvent event) { + listener.awake(event); + } + }; + done_ = new class IListenerDoit { + public void notify(IJobChangeListener listener, IJobChangeEvent event) { + listener.done(event); + } + }; + running_ = new class IListenerDoit { + public void notify(IJobChangeListener listener, IJobChangeEvent event) { + listener.running(event); + } + }; + scheduled_ = new class IListenerDoit { + public void notify(IJobChangeListener listener, IJobChangeEvent event) { + listener.scheduled(event); + } + }; + sleeping_ = new class IListenerDoit { + public void notify(IJobChangeListener listener, IJobChangeEvent event) { + listener.sleeping(event); + } + }; + global = new ListenerList(ListenerList.IDENTITY); + } + + /** + * TODO Could use an instance pool to re-use old event objects + */ + static JobChangeEvent newEvent(Job job) { + JobChangeEvent instance = new JobChangeEvent(); + instance.job = job; + return instance; + } + + static JobChangeEvent newEvent(Job job, IStatus result) { + JobChangeEvent instance = new JobChangeEvent(); + instance.job = job; + instance.result = result; + return instance; + } + + static JobChangeEvent newEvent(Job job, long delay) { + JobChangeEvent instance = new JobChangeEvent(); + instance.job = job; + instance.delay = delay; + return instance; + } + + /** + * Process the given doit for all global listeners and all local listeners + * on the given job. + */ + private void doNotify(IListenerDoit doit, IJobChangeEvent event) { + //notify all global listeners + Object[] listeners = global.getListeners(); + int size = listeners.length; + for (int i = 0; i < size; i++) { + try { + if (listeners[i] !is null) + doit.notify(cast(IJobChangeListener) listeners[i], event); + } catch (Exception e) { + handleException(listeners[i], e); +// } catch (LinkageError e) { +// handleException(listeners[i], e); + } + } + //notify all local listeners + ListenerList list = (cast(InternalJob) event.getJob()).getListeners(); + listeners = list is null ? null : list.getListeners(); + if (listeners is null) + return; + size = listeners.length; + for (int i = 0; i < size; i++) { + try { + if (listeners[i] !is null) + doit.notify(cast(IJobChangeListener) listeners[i], event); + } catch (Exception e) { + handleException(listeners[i], e); +// } catch (LinkageError e) { +// handleException(listeners[i], e); + } + } + } + + private void handleException(Object listener, Exception e) { + //this code is roughly copied from InternalPlatform.run(ISafeRunnable), + //but in-lined here for performance reasons + if (cast(OperationCanceledException)e ) + return; + String pluginId = JobOSGiUtils.getDefault().getBundleId(listener); + if (pluginId is null) + pluginId = JobManager.PI_JOBS; + String message = NLS.bind(JobMessages.meta_pluginProblems, pluginId); + RuntimeLog.log(new Status(IStatus.ERROR, pluginId, JobManager.PLUGIN_ERROR, message, e)); + } + + public void add(IJobChangeListener listener) { + global.add(cast(Object)listener); + } + + public void remove(IJobChangeListener listener) { + global.remove(cast(Object)listener); + } + + public void aboutToRun(Job job) { + doNotify(aboutToRun_, newEvent(job)); + } + + public void awake(Job job) { + doNotify(awake_, newEvent(job)); + } + + public void done(Job job, IStatus result, bool reschedule) { + JobChangeEvent event = newEvent(job, result); + event.reschedule = reschedule; + doNotify(done_, event); + } + + public void running(Job job) { + doNotify(running_, newEvent(job)); + } + + public void scheduled(Job job, long delay, bool reschedule) { + JobChangeEvent event = newEvent(job, delay); + event.reschedule = reschedule; + doNotify(scheduled_, event); + } + + public void sleeping(Job job) { + doNotify(sleeping_, newEvent(job)); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/internal/jobs/JobManager.d Tue Aug 12 02:34:21 2008 +0200 @@ -0,0 +1,1358 @@ +/******************************************************************************* + * Copyright (c) 2003, 2007 IBM Corporation 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: + * IBM Corporation - initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.core.internal.jobs.JobManager; + +import dwt.dwthelper.utils; +import dwtx.dwtxhelper.Collection; +import tango.io.Stdout; +import tango.text.convert.Format; +import tango.time.WallClock; +import tango.time.Time; +import tango.core.Thread; +import tango.text.convert.Format; + +//don't use ICU because this is used for debugging only (see bug 135785) +// import java.text.DateFormat; +// import java.text.FieldPosition; +// import java.text.SimpleDateFormat; + +import dwtx.core.internal.runtime.RuntimeLog; +import dwtx.core.runtime.Assert; +import dwtx.core.runtime.IProgressMonitor; +import dwtx.core.runtime.IProgressMonitorWithBlocking; +import dwtx.core.runtime.IStatus; +import dwtx.core.runtime.NullProgressMonitor; +import dwtx.core.runtime.OperationCanceledException; +import dwtx.core.runtime.Status; +import dwtx.core.runtime.jobs.IJobChangeEvent; +import dwtx.core.runtime.jobs.IJobChangeListener; +import dwtx.core.runtime.jobs.IJobManager; +import dwtx.core.runtime.jobs.ILock; +import dwtx.core.runtime.jobs.ISchedulingRule; +import dwtx.core.runtime.jobs.Job; +import dwtx.core.runtime.jobs.JobChangeAdapter; +import dwtx.core.runtime.jobs.LockListener; +import dwtx.core.runtime.jobs.ProgressProvider; +import dwtx.osgi.util.NLS; + +import dwtx.core.internal.jobs.ImplicitJobs; +import dwtx.core.internal.jobs.WorkerPool; +import dwtx.core.internal.jobs.JobListeners; +import dwtx.core.internal.jobs.LockManager; +import dwtx.core.internal.jobs.JobQueue; +import dwtx.core.internal.jobs.InternalJob; +import dwtx.core.internal.jobs.ThreadJob; +import dwtx.core.internal.jobs.JobOSGiUtils; +import dwtx.core.internal.jobs.Worker; +import dwtx.core.internal.jobs.Semaphore; +import dwtx.core.internal.jobs.JobChangeEvent; +import dwtx.core.internal.jobs.JobMessages; +import dwtx.core.internal.jobs.JobStatus; + +/** + * Implementation of API type IJobManager + * + * Implementation note: all the data structures of this class are protected + * by a single lock object held as a private field in this class. The JobManager + * instance itself is not used because this class is publicly reachable, and third + * party clients may try to synchronize on it. + * + * The WorkerPool class uses its own monitor for synchronizing its data + * structures. To avoid deadlock between the two classes, the JobManager + * must NEVER call the worker pool while its own monitor is held. + */ +public class JobManager : IJobManager { + + /** + * The unique identifier constant of this plug-in. + */ + public static const String PI_JOBS = "dwtx.core.jobs"; //$NON-NLS-1$ + + /** + * Status code constant indicating an error occurred while running a plug-in. + * For backward compatibility with Platform.PLUGIN_ERROR left at (value = 2). + */ + public static const int PLUGIN_ERROR = 2; + + private static const String OPTION_DEADLOCK_ERROR = PI_JOBS ~ "/jobs/errorondeadlock"; //$NON-NLS-1$ + private static const String OPTION_DEBUG_BEGIN_END = PI_JOBS ~ "/jobs/beginend"; //$NON-NLS-1$ + private static const String OPTION_DEBUG_JOBS = PI_JOBS ~ "/jobs"; //$NON-NLS-1$ + private static const String OPTION_DEBUG_JOBS_TIMING = PI_JOBS ~ "/jobs/timing"; //$NON-NLS-1$ + private static const String OPTION_LOCKS = PI_JOBS ~ "/jobs/locks"; //$NON-NLS-1$ + private static const String OPTION_SHUTDOWN = PI_JOBS ~ "/jobs/shutdown"; //$NON-NLS-1$ + + static bool DEBUG = false; + static bool DEBUG_BEGIN_END = false; + static bool DEBUG_DEADLOCK = false; + static bool DEBUG_LOCKS = false; + static bool DEBUG_TIMING = false; + static bool DEBUG_SHUTDOWN = false; +// private static DateFormat DEBUG_FORMAT; + + /** + * The singleton job manager instance. It must be a singleton because + * all job instances maintain a reference (as an optimization) and have no way + * of updating it. + */ + private static JobManager instance = null; + /** + * Scheduling rule used for validation of client-defined rules. + */ + private static ISchedulingRule nullRule; + private static void initNullRule(){ + if( nullRule !is null ) return; + nullRule = new class ISchedulingRule { + public bool contains(ISchedulingRule rule) { + return rule is this; + } + + public bool isConflicting(ISchedulingRule rule) { + return rule is this; + } + }; + } + + /** + * True if this manager is active, and false otherwise. A job manager + * starts out active, and becomes inactive if it has been shutdown + * and not restarted. + */ + private /+volatile+/ bool active = true; + + const ImplicitJobs implicitJobs; + + private const JobListeners jobListeners; + + /** + * The lock for synchronizing all activity in the job manager. To avoid deadlock, + * this lock must never be held for extended periods, and must never be + * held while third party code is being called. + */ + private const Object lock; + + private const LockManager lockManager; + + /** + * The pool of worker threads. + */ + private WorkerPool pool; + + private ProgressProvider progressProvider = null; + /** + * Jobs that are currently running. Should only be modified from changeState + */ + private const HashSet running; + + /** + * Jobs that are sleeping. Some sleeping jobs are scheduled to wake + * up at a given start time, while others will sleep indefinitely until woken. + * Should only be modified from changeState + */ + private const JobQueue sleeping; + /** + * True if this manager has been suspended, and false otherwise. A job manager + * starts out not suspended, and becomes suspended when <code>suspend</code> + * is invoked. Once suspended, no jobs will start running until <code>resume</code> + * is called. + */ + private bool suspended = false; + + /** + * jobs that are waiting to be run. Should only be modified from changeState + */ + private const JobQueue waiting; + + /** + * Counter to record wait queue insertion order. + */ + private long waitQueueCounter; + + public static void debug_(String msg) { + StringBuffer msgBuf = new StringBuffer(msg.length + 40); + if (DEBUG_TIMING) { + //lazy initialize to avoid overhead when not debugging +// if (DEBUG_FORMAT is null) +// DEBUG_FORMAT = new SimpleDateFormat("HH:mm:ss.SSS"); //$NON-NLS-1$ +// DEBUG_FORMAT.format(new Date(), msgBuf, new FieldPosition(0)); + auto time = WallClock.now(); + msgBuf.append(Format("{:d2}:{:d2}:{:d2}:{:d3}", + time.time.span.hours, + time.time.span.minutes, + time.time.span.seconds, + time.time.span.millis )); + msgBuf.append('-'); + } + msgBuf.append('['); + msgBuf.append(Thread.getThis().toString()); + msgBuf.append(']'); + msgBuf.append(msg); + Stdout.formatln( "{}", msgBuf.toString()); + } + + /** + * Returns the job manager singleton. For internal use only. + */ + static synchronized JobManager getInstance() { + if (instance is null) + new JobManager(); + return instance; + } + + /** + * For debugging purposes only + */ + private static String printJobName(Job job) { + if (cast(ThreadJob)job ) { + Job realJob = (cast(ThreadJob) job).realJob; + if (realJob !is null) + return realJob.classinfo.name; + return Format("ThreadJob on rule: {}", job.getRule()); //$NON-NLS-1$ + } + return job.classinfo.name; + } + + /** + * For debugging purposes only + */ + public static String printState(int state) { + switch (state) { + case Job.NONE : + return "NONE"; //$NON-NLS-1$ + case Job.WAITING : + return "WAITING"; //$NON-NLS-1$ + case Job.SLEEPING : + return "SLEEPING"; //$NON-NLS-1$ + case Job.RUNNING : + return "RUNNING"; //$NON-NLS-1$ + case InternalJob.BLOCKED : + return "BLOCKED"; //$NON-NLS-1$ + case InternalJob.ABOUT_TO_RUN : + return "ABOUT_TO_RUN"; //$NON-NLS-1$ + case InternalJob.ABOUT_TO_SCHEDULE : + return "ABOUT_TO_SCHEDULE";//$NON-NLS-1$ + } + return "UNKNOWN"; //$NON-NLS-1$ + } + + /** + * Note that although this method is not API, clients have historically used + * it to force jobs shutdown in cases where OSGi shutdown does not occur. + * For this reason, this method should be considered near-API and should not + * be changed if at all possible. + */ + public static void shutdown() { + if (instance !is null) { + instance.doShutdown(); + instance = null; + } + } + + private this() { + // DWT instance init + implicitJobs = new ImplicitJobs(this); + jobListeners = new JobListeners(); + lock = new Object(); + lockManager = new LockManager(); + + instance = this; + + initDebugOptions(); + synchronized (lock) { + waiting = new JobQueue(false); + sleeping = new JobQueue(true); + running = new HashSet(10); + pool = new WorkerPool(this); + } + pool.setDaemon(JobOSGiUtils.getDefault().useDaemonThreads()); + } + + /* (non-Javadoc) + * @see dwtx.core.runtime.jobs.IJobManager#addJobListener(dwtx.core.runtime.jobs.IJobChangeListener) + */ + public void addJobChangeListener(IJobChangeListener listener) { + jobListeners.add(listener); + } + + /* (non-Javadoc) + * @see dwtx.core.runtime.jobs.IJobManager#beginRule(dwtx.core.runtime.jobs.ISchedulingRule, dwtx.core.runtime.IProgressMonitor) + */ + public void beginRule(ISchedulingRule rule, IProgressMonitor monitor) { + validateRule(rule); + implicitJobs.begin(rule, monitorFor(monitor), false); + } + + /** + * Cancels a job + */ + protected bool cancel(InternalJob job) { + IProgressMonitor monitor = null; + synchronized (lock) { + switch (job.getState_package()) { + case Job.NONE : + return true; + case Job.RUNNING : + //cannot cancel a job that has already started (as opposed to ABOUT_TO_RUN) + if (job.internalGetState() is Job.RUNNING) { + monitor = job.getProgressMonitor(); + break; + } + //signal that the job should be canceled before it gets a chance to run + job.setAboutToRunCanceled(true); + return true; + default : + changeState(job, Job.NONE); + } + } + //call monitor outside sync block + if (monitor !is null) { + if (!monitor.isCanceled()) { + monitor.setCanceled(true); + job.canceling(); + } + return false; + } + //only notify listeners if the job was waiting or sleeping + jobListeners.done(cast(Job) job, Status.CANCEL_STATUS, false); + return true; + } + package bool cancel_package(InternalJob job) { + return cancel(job); + } + + /* (non-Javadoc) + * @see dwtx.core.runtime.jobs.IJobManager#cancel(java.lang.String) + */ + public void cancel(Object family) { + //don't synchronize because cancel calls listeners + for (Iterator it = select(family).iterator(); it.hasNext();) + cancel(cast(InternalJob) it.next()); + } + + /** + * Atomically updates the state of a job, adding or removing from the + * necessary queues or sets. + */ + private void changeState(InternalJob job, int newState) { + bool blockedJobs = false; + synchronized (lock) { + int oldState = job.internalGetState(); + switch (oldState) { + case Job.NONE : + case InternalJob.ABOUT_TO_SCHEDULE : + break; + case InternalJob.BLOCKED : + //remove this job from the linked list of blocked jobs + job.remove(); + break; + case Job.WAITING : + try { + waiting.remove(job); + } catch (RuntimeException e) { + Assert.isLegal(false, "Tried to remove a job that wasn't in the queue"); //$NON-NLS-1$ + } + break; + case Job.SLEEPING : + try { + sleeping.remove(job); + } catch (RuntimeException e) { + Assert.isLegal(false, "Tried to remove a job that wasn't in the queue"); //$NON-NLS-1$ + } + break; + case Job.RUNNING : + case InternalJob.ABOUT_TO_RUN : + running.remove(job); + //add any blocked jobs back to the wait queue + InternalJob blocked = job.previous(); + job.remove(); + blockedJobs = blocked !is null; + while (blocked !is null) { + InternalJob previous = blocked.previous(); + changeState(blocked, Job.WAITING); + blocked = previous; + } + break; + default : + Assert.isLegal(false, Format("Invalid job state: {}, state: {}", job, oldState)); //$NON-NLS-1$ //$NON-NLS-2$ + } + job.internalSetState(newState); + switch (newState) { + case Job.NONE : + job.setStartTime(InternalJob.T_NONE); + job.setWaitQueueStamp(InternalJob.T_NONE); + case InternalJob.BLOCKED : + break; + case Job.WAITING : + waiting.enqueue(job); + break; + case Job.SLEEPING : + try { + sleeping.enqueue(job); + } catch (RuntimeException e) { + throw new RuntimeException(Format("Error changing from state: ", oldState)); //$NON-NLS-1$ + } + break; + case Job.RUNNING : + case InternalJob.ABOUT_TO_RUN : + job.setStartTime(InternalJob.T_NONE); + job.setWaitQueueStamp(InternalJob.T_NONE); + running.add(job); + break; + case InternalJob.ABOUT_TO_SCHEDULE : + break; + default : + Assert.isLegal(false, Format("Invalid job state: {}, state: {}", job, newState)); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + //notify queue outside sync block + if (blockedJobs) + pool.jobQueued(); + } + + /** + * Returns a new progress monitor for this job, belonging to the given + * progress group. Returns null if it is not a valid time to set the job's group. + */ + protected IProgressMonitor createMonitor(InternalJob job, IProgressMonitor group, int ticks) { + synchronized (lock) { + //group must be set before the job is scheduled + //this includes the ABOUT_TO_SCHEDULE state, during which it is still + //valid to set the progress monitor + if (job.getState_package() !is Job.NONE) + return null; + IProgressMonitor monitor = null; + if (progressProvider !is null) + monitor = progressProvider.createMonitor(cast(Job) job, group, ticks); + if (monitor is null) + monitor = new NullProgressMonitor(); + return monitor; + } + } + package IProgressMonitor createMonitor_package(InternalJob job, IProgressMonitor group, int ticks) { + return createMonitor(job, group, ticks); + } + + /** + * Returns a new progress monitor for this job. Never returns null. + */ + private IProgressMonitor createMonitor(Job job) { + IProgressMonitor monitor = null; + if (progressProvider !is null) + monitor = progressProvider.createMonitor(job); + if (monitor is null) + monitor = new NullProgressMonitor(); + return monitor; + } + + /* (non-Javadoc) + * @see dwtx.core.runtime.jobs.IJobManager#createProgressGroup() + */ + public IProgressMonitor createProgressGroup() { + if (progressProvider !is null) + return progressProvider.createProgressGroup(); + return new NullProgressMonitor(); + } + + /* (non-Javadoc) + * @see dwtx.core.runtime.jobs.IJobManager#currentJob() + */ + public Job currentJob() { + Thread current = Thread.getThis(); + if (cast(Worker)current ) + return (cast(Worker) current).currentJob(); + synchronized (lock) { + for (Iterator it = running.iterator(); it.hasNext();) { + Job job = cast(Job) it.next(); + if (job.getThread() is current) + return job; + } + } + return null; + } + + /** + * Returns the delay in milliseconds that a job with a given priority can + * tolerate waiting. + */ + private long delayFor(int priority) { + //these values may need to be tweaked based on machine speed + switch (priority) { + case Job.INTERACTIVE : + return 0L; + case Job.SHORT : + return 50L; + case Job.LONG : + return 100L; + case Job.BUILD : + return 500L; + case Job.DECORATE : + return 1000L; + default : + Assert.isTrue(false, Format("Job has invalid priority: {}", priority)); //$NON-NLS-1$ + return 0; + } + } + + /** + * Performs the scheduling of a job. Does not perform any notifications. + */ + private void doSchedule(InternalJob job, long delay) { + synchronized (lock) { + //if it's a decoration job with no rule, don't run it right now if the system is busy + if (job.getPriority() is Job.DECORATE && job.getRule() is null) { + long minDelay = running.size() * 100; + delay = Math.max(delay, minDelay); + } + if (delay > 0) { + job.setStartTime(System.currentTimeMillis() + delay); + changeState(job, Job.SLEEPING); + } else { + job.setStartTime(System.currentTimeMillis() + delayFor(job.getPriority())); + job.setWaitQueueStamp(waitQueueCounter++); + changeState(job, Job.WAITING); + } + } + } + + /** + * Shuts down the job manager. Currently running jobs will be told + * to stop, but worker threads may still continue processing. + * (note: This implemented IJobManager.shutdown which was removed + * due to problems caused by premature shutdown) + */ + private void doShutdown() { + Job[] toCancel = null; + synchronized (lock) { + if (active) { + active = false; + //cancel all running jobs + toCancel = arraycast!(Job)( running.toArray()); + //clean up + sleeping.clear(); + waiting.clear(); + running.clear(); + } + } + + // Give running jobs a chance to finish. Wait 0.1 seconds for up to 3 times. + if (toCancel !is null && toCancel.length > 0) { + for (int i = 0; i < toCancel.length; i++) { + cancel(cast(InternalJob)toCancel[i]); // cancel jobs outside sync block to avoid deadlock + } + + for (int waitAttempts = 0; waitAttempts < 3; waitAttempts++) { + Thread.yield(); + synchronized (lock) { + if (running.isEmpty()) + break; + } + if (DEBUG_SHUTDOWN) { + JobManager.debug_(Format("Shutdown - job wait cycle #{}", (waitAttempts + 1))); //$NON-NLS-1$ + Job[] stillRunning = null; + synchronized (lock) { + stillRunning = arraycast!(Job)( running.toArray()); + } + if (stillRunning !is null) { + for (int j = 0; j < stillRunning.length; j++) { + JobManager.debug_(Format("\tJob: {}", printJobName(stillRunning[j]))); //$NON-NLS-1$ + } + } + } + try { + Thread.sleep(0.100); + } catch (InterruptedException e) { + //ignore + } + Thread.yield(); + } + + synchronized (lock) { // retrieve list of the jobs that are still running + toCancel = arraycast!(Job)( running.toArray()); + } + } + + if (toCancel !is null) { + for (int i = 0; i < toCancel.length; i++) { + String jobName = printJobName(toCancel[i]); + //this doesn't need to be translated because it's just being logged + String msg = "Job found still running after platform shutdown. Jobs should be canceled by the plugin that scheduled them during shutdown: " ~ jobName; //$NON-NLS-1$ + RuntimeLog.log(new Status(IStatus.WARNING, JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, msg, null)); + + // TODO the RuntimeLog.log in its current implementation won't produce a log + // during this stage of shutdown. For now add a standard error output. + // One the logging story is improved, the System.err output below can be removed: + Stderr.formatln("{}", msg); + } + } + + pool.shutdown_package(); + } + + /** + * Indicates that a job was running, and has now finished. Note that this method + * can be called under OutOfMemoryError conditions and thus must be paranoid + * about allocating objects. + */ + protected void endJob(InternalJob job, IStatus result, bool notify) { + long rescheduleDelay = InternalJob.T_NONE; + synchronized (lock) { + //if the job is finishing asynchronously, there is nothing more to do for now + if (result is Job.ASYNC_FINISH) + return; + //if job is not known then it cannot be done + if (job.getState_package() is Job.NONE) + return; + if (JobManager.DEBUG && notify) + JobManager.debug_(Format("Ending job: {}", job)); //$NON-NLS-1$ + job.setResult(result); + job.setProgressMonitor(null); + job.setThread_package(null); + rescheduleDelay = job.getStartTime(); + changeState(job, Job.NONE); + } + //notify listeners outside sync block + final bool reschedule = active && rescheduleDelay > InternalJob.T_NONE && job.shouldSchedule_package(); + if (notify) + jobListeners.done(cast(Job) job, result, reschedule); + //reschedule the job if requested and we are still active + if (reschedule) + schedule(job, rescheduleDelay, reschedule); + } + package void endJob_package(InternalJob job, IStatus result, bool notify) { + endJob(job, result, notify); + } + + /* (non-Javadoc) + * @see dwtx.core.runtime.jobs.IJobManager#endRule(dwtx.core.runtime.jobs.ISchedulingRule) + */ + public void endRule(ISchedulingRule rule) { + implicitJobs.end(rule, false); + } + + /* (non-Javadoc) + * @see dwtx.core.runtime.jobs.IJobManager#find(java.lang.String) + */ + public Job[] find(Object family) { + List members = select(family); + return arraycast!(Job)( members.toArray()); + } + + /** + * Returns a running or blocked job whose scheduling rule conflicts with the + * scheduling rule of the given waiting job. Returns null if there are no + * conflicting jobs. A job can only run if there are no running jobs and no blocked + * jobs whose scheduling rule conflicts with its rule. + */ + protected InternalJob findBlockingJob(InternalJob waitingJob) { + if (waitingJob.getRule() is null) + return null; + synchronized (lock) { + if (running.isEmpty()) + return null; + //check the running jobs + bool hasBlockedJobs = false; + for (Iterator it = running.iterator(); it.hasNext();) { + InternalJob job = cast(InternalJob) it.next(); + if (waitingJob.isConflicting(job)) + return job; + if (!hasBlockedJobs) + hasBlockedJobs = job.previous() !is null; + } + //there are no blocked jobs, so we are done + if (!hasBlockedJobs) + return null; + //check all jobs blocked by running jobs + for (Iterator it = running.iterator(); it.hasNext();) { + InternalJob job = cast(InternalJob) it.next(); + while (true) { + job = job.previous(); + if (job is null) + break; + if (waitingJob.isConflicting(job)) + return job; + } + } + } + return null; + } + package InternalJob findBlockingJob_package(InternalJob waitingJob) { + return findBlockingJob(waitingJob); + } + + public LockManager getLockManager() { + return lockManager; + } + + private void initDebugOptions() { + DEBUG = JobOSGiUtils.getDefault().getBooleanDebugOption(OPTION_DEBUG_JOBS, false); + DEBUG_BEGIN_END = JobOSGiUtils.getDefault().getBooleanDebugOption(OPTION_DEBUG_BEGIN_END, false); + DEBUG_DEADLOCK = JobOSGiUtils.getDefault().getBooleanDebugOption(OPTION_DEADLOCK_ERROR, false); + DEBUG_LOCKS = JobOSGiUtils.getDefault().getBooleanDebugOption(OPTION_LOCKS, false); + DEBUG_TIMING = JobOSGiUtils.getDefault().getBooleanDebugOption(OPTION_DEBUG_JOBS_TIMING, false); + DEBUG_SHUTDOWN = JobOSGiUtils.getDefault().getBooleanDebugOption(OPTION_SHUTDOWN, false); + } + + /** + * Returns whether the job manager is active (has not been shutdown). + */ + protected bool isActive() { + return active; + } + package bool isActive_package() { + return isActive(); + } + + /** + * Returns true if the given job is blocking the execution of a non-system + * job. + */ + protected bool isBlocking(InternalJob runningJob) { + synchronized (lock) { + // if this job isn't running, it can't be blocking anyone + if (runningJob.getState_package() !is Job.RUNNING) + return false; + // if any job is queued behind this one, it is blocked by it + InternalJob previous = runningJob.previous(); + while (previous !is null) { + // ignore jobs of lower priority (higher priority value means lower priority) + if (previous.getPriority() < runningJob.getPriority()) { + if (!previous.isSystem_package()) + return true; + // implicit jobs should interrupt unless they act on behalf of system jobs + if (cast(ThreadJob)previous && (cast(ThreadJob) previous).shouldInterrupt()) + return true; + } + previous = previous.previous(); + } + // none found + return false; + } + } + package bool isBlocking_package(InternalJob runningJob) { + return isBlocking(runningJob); + } + + /* (non-Javadoc) + * @see dwtx.core.runtime.jobs.IJobManager#isIdle() + */ + public bool isIdle() { + synchronized (lock) { + return running.isEmpty() && waiting.isEmpty(); + } + } + + /* (non-Javadoc) + * @see dwtx.core.runtime.jobs.IJobManager#isSuspended() + */ + public bool isSuspended() { + synchronized (lock) { + return suspended; + } + } + + /* (non-Javadoc) + * @see dwtx.core.runtime.jobs.Job#job(dwtx.core.runtime.jobs.Job) + */ + protected void join(InternalJob job) { + IJobChangeListener listener; + Semaphore barrier; + synchronized (lock) { + int state = job.getState_package(); + if (state is Job.NONE) + return; + //don't join a waiting or sleeping job when suspended (deadlock risk) + if (suspended && state !is Job.RUNNING) + return; + //it's an error for a job to join itself + if (state is Job.RUNNING && job.getThread_package() is Thread.getThis()) + throw new IllegalStateException("Job attempted to join itself"); //$NON-NLS-1$ + //the semaphore will be released when the job is done + barrier = new Semaphore(null); + listener = new class(barrier) JobChangeAdapter { + Semaphore barrier_; + this( Semaphore a ){ + barrier_ = a; + } + public void done(IJobChangeEvent event) { + barrier_.release(); + } + }; + job.addJobChangeListener_package(listener); + //compute set of all jobs that must run before this one + //add a listener that removes jobs from the blocking set when they finish + } + //wait until listener notifies this thread. + try { + while (true) { + //notify hook to service pending syncExecs before falling asleep + lockManager.aboutToWait(job.getThread_package()); + try { + if (barrier.acquire(Long.MAX_VALUE)) + break; + } catch (InterruptedException e) { + //loop and keep trying + } + } + } finally { + lockManager.aboutToRelease(); + job.removeJobChangeListener_package(listener); + } + } + package void join_package(InternalJob job) { + join(job); + } + + /* (non-Javadoc) + * @see IJobManager#join(String, IProgressMonitor) + */ + public void join(Object family_, IProgressMonitor monitor) { + monitor = monitorFor(monitor); + IJobChangeListener listener = null; + Set jobs_; + int jobCount; + Job blocking = null; + synchronized (lock) { + //don't join a waiting or sleeping job when suspended (deadlock risk) + int states = suspended ? Job.RUNNING : Job.RUNNING | Job.WAITING | Job.SLEEPING; + jobs_ = Collections.synchronizedSet(new HashSet(select(family_, states))); + jobCount = jobs_.size(); + if (jobCount > 0) { + //if there is only one blocking job, use it in the blockage callback below + if (jobCount is 1) + blocking = cast(Job) jobs_.iterator().next(); + listener = new class(family_, jobs_ )JobChangeAdapter { + Object family; + Set jobs; + this(Object a, Set b){ + family = a; + jobs = b; + } + public void done(IJobChangeEvent event) { + //don't remove from list if job is being rescheduled + if (!(cast(JobChangeEvent) event).reschedule) + jobs.remove(event.getJob()); + } + + //update the list of jobs if new ones are added during the join + public void scheduled(IJobChangeEvent event) { + //don't add to list if job is being rescheduled + if ((cast(JobChangeEvent) event).reschedule) + return; + Job job = event.getJob(); + if (job.belongsTo(family)) + jobs.add(job); + } + }; + addJobChangeListener(listener); + } + } + if (jobCount is 0) { + //use up the monitor outside synchronized block because monitors call untrusted code + monitor.beginTask(JobMessages.jobs_blocked0, 1); + monitor.done(); + return; + } + //spin until all jobs are completed + try { + monitor.beginTask(JobMessages.jobs_blocked0, jobCount); + monitor.subTask(NLS.bind(JobMessages.jobs_waitFamSub, Integer.toString(jobCount))); + reportBlocked(monitor, blocking); + int jobsLeft; + int reportedWorkDone = 0; + while ((jobsLeft = jobs_.size()) > 0) { + //don't let there be negative work done if new jobs have + //been added since the join began + int actualWorkDone = Math.max(0, jobCount - jobsLeft); + if (reportedWorkDone < actualWorkDone) { + monitor.worked(actualWorkDone - reportedWorkDone); + reportedWorkDone = actualWorkDone; + monitor.subTask(NLS.bind(JobMessages.jobs_waitFamSub, Integer.toString(jobsLeft))); + } + implMissing(__FILE__, __LINE__ ); +// DWT +// if (Thread.interrupted()) +// throw new InterruptedException(); + if (monitor.isCanceled()) + throw new OperationCanceledException(); + //notify hook to service pending syncExecs before falling asleep + lockManager.aboutToWait(null); + Thread.sleep(0.100); + } + } finally { + lockManager.aboutToRelease(); + removeJobChangeListener(listener); + reportUnblocked(monitor); + monitor.done(); + } + } + + /** + * Returns a non-null progress monitor instance. If the monitor is null, + * returns the default monitor supplied by the progress provider, or a + * NullProgressMonitor if no default monitor is available. + */ + private IProgressMonitor monitorFor(IProgressMonitor monitor) { + if (monitor is null || (cast(NullProgressMonitor)monitor )) { + if (progressProvider !is null) { + try { + monitor = progressProvider.getDefaultMonitor(); + } catch (Exception e) { + String msg = NLS.bind(JobMessages.meta_pluginProblems, JobManager.PI_JOBS); + RuntimeLog.log(new Status(IStatus.ERROR, JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, msg, e)); + } + } + } + + if (monitor is null) + return new NullProgressMonitor(); + return monitor; + } + + /* (non-Javadoc) + * @see IJobManager#newLock(java.lang.String) + */ + public ILock newLock() { + return lockManager.newLock(); + } + + /** + * Removes and returns the first waiting job in the queue. Returns null if there + * are no items waiting in the queue. If an item is removed from the queue, + * it is moved to the running jobs list. + */ + private Job nextJob() { + synchronized (lock) { + //do nothing if the job manager is suspended + if (suspended) + return null; + //tickle the sleep queue to see if anyone wakes up + long now = System.currentTimeMillis(); + InternalJob job = sleeping.peek(); + while (job !is null && job.getStartTime() < now) { + job.setStartTime(now + delayFor(job.getPriority())); + job.setWaitQueueStamp(waitQueueCounter++); + changeState(job, Job.WAITING); + job = sleeping.peek(); + } + //process the wait queue until we find a job whose rules are satisfied. + while ((job = waiting.peek()) !is null) { + InternalJob blocker = findBlockingJob(job); + if (blocker is null) + break; + //queue this job after the job that's blocking it + changeState(job, InternalJob.BLOCKED); + //assert job does not already belong to some other data structure + Assert.isTrue(job.next() is null); + Assert.isTrue(job.previous() is null); + blocker.addLast(job); + } + //the job to run must be in the running list before we exit + //the sync block, otherwise two jobs with conflicting rules could start at once + if (job !is null) { + changeState(job, InternalJob.ABOUT_TO_RUN); + if (JobManager.DEBUG) + JobManager.debug_(Format("Starting job: {}", job)); //$NON-NLS-1$ + } + return cast(Job) job; + } + } + + /* (non-Javadoc) + * @see dwtx.core.runtime.jobs.IJobManager#removeJobListener(dwtx.core.runtime.jobs.IJobChangeListener) + */ + public void removeJobChangeListener(IJobChangeListener listener) { + jobListeners.remove(listener); + } + + /** + * Report to the progress monitor that this thread is blocked, supplying + * an information message, and if possible the job that is causing the blockage. + * Important: An invocation of this method MUST be followed eventually be + * an invocation of reportUnblocked. + * @param monitor The monitor to report blocking to + * @param blockingJob The job that is blocking this thread, or <code>null</code> + * @see #reportUnblocked + */ + final void reportBlocked(IProgressMonitor monitor, InternalJob blockingJob) { + if (!(cast(IProgressMonitorWithBlocking)monitor )) + return; + IStatus reason; + if (blockingJob is null || cast(ThreadJob)blockingJob || blockingJob.isSystem_package()) { + reason = new Status(IStatus.INFO, JobManager.PI_JOBS, 1, JobMessages.jobs_blocked0, null); + } else { + String msg = NLS.bind(JobMessages.jobs_blocked1, blockingJob.getName_package()); + reason = new JobStatus(IStatus.INFO, cast(Job) blockingJob, msg); + } + (cast(IProgressMonitorWithBlocking) monitor).setBlocked(reason); + } + + /** + * Reports that this thread was blocked, but is no longer blocked and is able + * to proceed. + * @param monitor The monitor to report unblocking to. + * @see #reportBlocked + */ + final void reportUnblocked(IProgressMonitor monitor) { + if (cast(IProgressMonitorWithBlocking)monitor ) + (cast(IProgressMonitorWithBlocking) monitor).clearBlocked(); + } + + /*(non-Javadoc) + * @see dwtx.core.runtime.jobs.IJobManager#resume() + */ + public final void resume() { + synchronized (lock) { + suspended = false; + //poke the job pool + pool.jobQueued(); + } + } + + /** (non-Javadoc) + * @deprecated this method should not be used + * @see dwtx.core.runtime.jobs.IJobManager#resume(dwtx.core.runtime.jobs.ISchedulingRule) + */ + public final void resume(ISchedulingRule rule) { + implicitJobs.resume(rule); + } + + /** + * Attempts to immediately start a given job. Returns true if the job was + * successfully started, and false if it could not be started immediately + * due to a currently running job with a conflicting rule. Listeners will never + * be notified of jobs that are run in this way. + */ + protected bool runNow(InternalJob job) { + synchronized (lock) { + //cannot start if there is a conflicting job + if (findBlockingJob(job) !is null) + return false; + changeState(job, Job.RUNNING); + job.setProgressMonitor(new NullProgressMonitor()); + job.run_package(null); + } + return true; + } + package bool runNow_package(InternalJob job) { + return runNow(job); + } + + /* (non-Javadoc) + * @see dwtx.core.runtime.jobs.Job#schedule(long) + */ + protected void schedule(InternalJob job, long delay, bool reschedule) { + if (!active) + throw new IllegalStateException("Job manager has been shut down."); //$NON-NLS-1$ + Assert.isNotNull(job, "Job is null"); //$NON-NLS-1$ + Assert.isLegal(delay >= 0, "Scheduling delay is negative"); //$NON-NLS-1$ + synchronized (lock) { + //if the job is already running, set it to be rescheduled when done + if (job.getState_package() is Job.RUNNING) { + job.setStartTime(delay); + return; + } + //can't schedule a job that is waiting or sleeping + if (job.internalGetState() !is Job.NONE) + return; + if (JobManager.DEBUG) + JobManager.debug_(Format("Scheduling job: {}", job)); //$NON-NLS-1$ + //remember that we are about to schedule the job + //to prevent multiple schedule attempts from succeeding (bug 68452) + changeState(job, InternalJob.ABOUT_TO_SCHEDULE); + } + //notify listeners outside sync block + jobListeners.scheduled(cast(Job) job, delay, reschedule); + //schedule the job + doSchedule(job, delay); + //call the pool outside sync block to avoid deadlock + pool.jobQueued(); + } + package void schedule_package(InternalJob job, long delay, bool reschedule) { + schedule(job, delay, reschedule); + } + + /** + * Adds all family members in the list of jobs to the collection + */ + private void select(List members, Object family, InternalJob firstJob, int stateMask) { + if (firstJob is null) + return; + InternalJob job = firstJob; + do { + //note that job state cannot be NONE at this point + if ((family is null || job.belongsTo_package(family)) && ((job.getState_package() & stateMask) !is 0)) + members.add(job); + job = job.previous(); + } while (job !is null && job !is firstJob); + } + + /** + * Returns a list of all jobs known to the job manager that belong to the given family. + */ + private List select(Object family) { + return select(family, Job.WAITING | Job.SLEEPING | Job.RUNNING); + } + + /** + * Returns a list of all jobs known to the job manager that belong to the given + * family and are in one of the provided states. + */ + private List select(Object family, int stateMask) { + List members = new ArrayList(); + synchronized (lock) { + if ((stateMask & Job.RUNNING) !is 0) { + for (Iterator it = running.iterator(); it.hasNext();) { + select(members, family, cast(InternalJob) it.next(), stateMask); + } + } + if ((stateMask & Job.WAITING) !is 0) + select(members, family, waiting.peek(), stateMask); + if ((stateMask & Job.SLEEPING) !is 0) + select(members, family, sleeping.peek(), stateMask); + } + return members; + } + + /* (non-Javadoc) + * @see IJobManager#setLockListener(LockListener) + */ + public void setLockListener(LockListener listener) { + lockManager.setLockListener(listener); + } + + /** + * Changes a job priority. + */ + protected void setPriority(InternalJob job, int newPriority) { + synchronized (lock) { + int oldPriority = job.getPriority(); + if (oldPriority is newPriority) + return; + job.internalSetPriority(newPriority); + //if the job is waiting to run, re-shuffle the queue + if (job.getState_package() is Job.WAITING) { + long oldStart = job.getStartTime(); + job.setStartTime(oldStart + (delayFor(newPriority) - delayFor(oldPriority))); + waiting.resort(job); + } + } + } + package void setPriority_package(InternalJob job, int newPriority) { + setPriority(job, newPriority); + } + + /* (non-Javadoc) + * @see IJobManager#setProgressProvider(IProgressProvider) + */ + public void setProgressProvider(ProgressProvider provider) { + progressProvider = provider; + } + + /* (non-Javadoc) + * @see Job#setRule + */ + public void setRule(InternalJob job, ISchedulingRule rule) { + synchronized (lock) { + //cannot change the rule of a job that is already running + Assert.isLegal(job.getState_package() is Job.NONE); + validateRule(rule); + job.internalSetRule(rule); + } + } + + /** + * Puts a job to sleep. Returns true if the job was successfully put to sleep. + */ + protected bool sleep(InternalJob job) { + synchronized (lock) { + switch (job.getState_package()) { + case Job.RUNNING : + //cannot be paused if it is already running (as opposed to ABOUT_TO_RUN) + if (job.internalGetState() is Job.RUNNING) + return false; + //job hasn't started running yet (aboutToRun listener) + break; + case Job.SLEEPING : + //update the job wake time + job.setStartTime(InternalJob.T_INFINITE); + //change state again to re-shuffle the sleep queue + changeState(job, Job.SLEEPING); + return true; + case Job.NONE : + return true; + case Job.WAITING : + //put the job to sleep + break; + } + job.setStartTime(InternalJob.T_INFINITE); + changeState(job, Job.SLEEPING); + } + jobListeners.sleeping(cast(Job) job); + return true; + } + package bool sleep_package(InternalJob job) { + return sleep(job); + } + + /* (non-Javadoc) + * @see IJobManager#sleep(String) + */ + public void sleep(Object family) { + //don't synchronize because sleep calls listeners + for (Iterator it = select(family).iterator(); it.hasNext();) { + sleep(cast(InternalJob) it.next()); + } + } + + /** + * Returns the estimated time in milliseconds before the next job is scheduled + * to wake up. The result may be negative. Returns InternalJob.T_INFINITE if + * there are no sleeping or waiting jobs. + */ + protected long sleepHint() { + synchronized (lock) { + //wait forever if job manager is suspended + if (suspended) + return InternalJob.T_INFINITE; + if (!waiting.isEmpty()) + return 0L; + //return the anticipated time that the next sleeping job will wake + InternalJob next = sleeping.peek(); + if (next is null) + return InternalJob.T_INFINITE; + return next.getStartTime() - System.currentTimeMillis(); + } + } + package long sleepHint_package() { + return sleepHint(); + } + /** + * Returns the next job to be run, or null if no jobs are waiting to run. + * The worker must call endJob when the job is finished running. + */ + protected Job startJob() { + Job job = null; + while (true) { + job = nextJob(); + if (job is null) + return null; + //must perform this outside sync block because it is third party code + if (job.shouldRun()) { + //check for listener veto + jobListeners.aboutToRun(job); + //listeners may have canceled or put the job to sleep + synchronized (lock) { + if (job.getState() is Job.RUNNING) { + InternalJob internal = job; + if (internal.isAboutToRunCanceled()) { + internal.setAboutToRunCanceled(false); + //fall through and end the job below + } else { + internal.setProgressMonitor(createMonitor(job)); + //change from ABOUT_TO_RUN to RUNNING + internal.internalSetState(Job.RUNNING); + break; + } + } + } + } + if (job.getState() !is Job.SLEEPING) { + //job has been vetoed or canceled, so mark it as done + endJob(job, Status.CANCEL_STATUS, true); + continue; + } + } + jobListeners.running(job); + return job; + + } + package Job startJob_package() { + return startJob(); + } + + /* non-Javadoc) + * @see dwtx.core.runtime.jobs.IJobManager#suspend() + */ + public final void suspend() { + synchronized (lock) { + suspended = true; + } + } + + /** (non-Javadoc) + * @deprecated this method should not be used + * @see dwtx.core.runtime.jobs.IJobManager#suspend(dwtx.core.runtime.jobs.ISchedulingRule, dwtx.core.runtime.IProgressMonitor) + */ + public final void suspend(ISchedulingRule rule, IProgressMonitor monitor) { + Assert.isNotNull(cast(Object)rule); + implicitJobs.suspend(rule, monitorFor(monitor)); + } + + /* non-Javadoc) + * @see dwtx.core.runtime.jobs.IJobManager#transferRule() + */ + public void transferRule(ISchedulingRule rule, Thread destinationThread) { + implicitJobs.transfer(rule, destinationThread); + } + + /** + * Validates that the given scheduling rule obeys the constraints of + * scheduling rules as described in the <code>ISchedulingRule</code> + * javadoc specification. + */ + private void validateRule(ISchedulingRule rule) { + //null rule always valid + if (rule is null) + return; + initNullRule(); + //contains method must be reflexive + Assert.isLegal(rule.contains(rule)); + //contains method must return false when given an unknown rule + Assert.isLegal(!rule.contains(nullRule)); + //isConflicting method must be reflexive + Assert.isLegal(rule.isConflicting(rule)); + //isConflicting method must return false when given an unknown rule + Assert.isLegal(!rule.isConflicting(nullRule)); + } + + /* (non-Javadoc) + * @see Job#wakeUp(long) + */ + protected void wakeUp(InternalJob job, long delay) { + Assert.isLegal(delay >= 0, "Scheduling delay is negative"); //$NON-NLS-1$ + synchronized (lock) { + //cannot wake up if it is not sleeping + if (job.getState_package() !is Job.SLEEPING) + return; + doSchedule(job, delay); + } + //call the pool outside sync block to avoid deadlock + pool.jobQueued(); + + //only notify of wake up if immediate + if (delay is 0) + jobListeners.awake(cast(Job) job); + } + package void wakeUp_package(InternalJob job, long delay) { + wakeUp(job, delay); + } + + /* (non-Javadoc) + * @see IJobFamily#wakeUp(String) + */ + public void wakeUp(Object family) { + //don't synchronize because wakeUp calls listeners + for (Iterator it = select(family).iterator(); it.hasNext();) { + wakeUp(cast(InternalJob) it.next(), 0L); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/internal/jobs/JobMessages.d Tue Aug 12 02:34:21 2008 +0200 @@ -0,0 +1,61 @@ +/********************************************************************** + * Copyright (c) 2005, 2006 IBM Corporation 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: + * IBM - Initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + **********************************************************************/ +module dwtx.core.internal.jobs.JobMessages; + +import tango.core.Thread; +import tango.io.Stdout; +import tango.time.WallClock; +import tango.text.convert.TimeStamp; + +import dwt.dwthelper.utils; + +import dwtx.osgi.util.NLS; + +/** + * Job plugin message catalog + */ +public class JobMessages : NLS { + private static const String BUNDLE_NAME = "dwtx.core.internal.jobs.messages"; //$NON-NLS-1$ + + // Job Manager and Locks + public static String jobs_blocked0 = "The user operation is waiting for background work to complete."; + public static String jobs_blocked1 = "The user operation is waiting for \"{0}\" to complete."; + public static String jobs_internalError = "An internal error occured during: \"{0}\"."; + public static String jobs_waitFamSub = "{0} work item(s) left."; + + // metadata + public static String meta_pluginProblems = "Problems occured when invoking code from plug-in: \"{0}\"."; + +// static this() { +// // load message values from bundle file +// reloadMessages(); +// } + + public static void reloadMessages() { +// NLS.initializeMessages(BUNDLE_NAME, import(BUNDLE_NAME~".properties")); + } + + /** + * Print a debug message to the console. + * Pre-pend the message with the current date and the name of the current thread. + */ + public static void message(String message) { + StringBuffer buffer = new StringBuffer(); + char[30] buf; + buffer.append(tango.text.convert.TimeStamp.format( buf, WallClock.now())); + buffer.append(" - ["); //$NON-NLS-1$ + buffer.append(Thread.getThis().name()); + buffer.append("] "); //$NON-NLS-1$ + buffer.append(message); + Stdout.formatln(buffer.toString()); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/internal/jobs/JobOSGiUtils.d Tue Aug 12 02:34:21 2008 +0200 @@ -0,0 +1,153 @@ +/******************************************************************************* + * Copyright (c) 2005, 2007 IBM Corporation 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: + * IBM Corporation - initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.core.internal.jobs.JobOSGiUtils; + +import dwt.dwthelper.utils; + +// import org.osgi.framework.Bundle; +// import org.osgi.framework.BundleContext; +// import org.osgi.service.packageadmin.PackageAdmin; +// import org.osgi.util.tracker.ServiceTracker; + +import dwtx.core.runtime.jobs.IJobManager; +// import dwtx.osgi.service.debug.DebugOptions; + +/** + * The class contains a set of helper methods for the runtime Jobs plugin. + * The following utility methods are supplied: + * - provides access to debug options + * - provides some bundle discovery functionality + * + * The closeServices() method should be called before the plugin is stopped. + * + * @since dwtx.core.jobs 3.2 + */ +class JobOSGiUtils { +// private ServiceTracker debugTracker = null; +// private ServiceTracker bundleTracker = null; + + private static /+final+/ JobOSGiUtils singleton; + + /** + * Accessor for the singleton instance + * @return The JobOSGiUtils instance + */ + public static synchronized JobOSGiUtils getDefault() { + if( singleton is null ){ + singleton = new JobOSGiUtils(); + } + return singleton; + } + + /** + * Private constructor to block instance creation. + */ + private this() { +// super(); + } + + void openServices() { + implMissing(__FILE__,__LINE__); +// BundleContext context = JobActivator.getContext(); +// if (context is null) { +// if (JobManager.DEBUG) +// JobMessages.message("JobsOSGiUtils called before plugin started"); //$NON-NLS-1$ +// return; +// } +// +// debugTracker = new ServiceTracker(context, DebugOptions.class.getName(), null); +// debugTracker.open(); +// +// bundleTracker = new ServiceTracker(context, PackageAdmin.class.getName(), null); +// bundleTracker.open(); + } + + void closeServices() { + implMissing(__FILE__,__LINE__); +// if (debugTracker !is null) { +// debugTracker.close(); +// debugTracker = null; +// } +// if (bundleTracker !is null) { +// bundleTracker.close(); +// bundleTracker = null; +// } + } + + public bool getBooleanDebugOption(String option, bool defaultValue) { + implMissing(__FILE__,__LINE__); + return false; +// if (debugTracker is null) { +// if (JobManager.DEBUG) +// JobMessages.message("Debug tracker is not set"); //$NON-NLS-1$ +// return defaultValue; +// } +// DebugOptions options = (DebugOptions) debugTracker.getService(); +// if (options !is null) { +// String value = options.getOption(option); +// if (value !is null) +// return value.equalsIgnoreCase("true"); //$NON-NLS-1$ +// } +// return defaultValue; + } + + /** + * Returns the bundle id of the bundle that contains the provided object, or + * <code>null</code> if the bundle could not be determined. + */ + public String getBundleId(Object object) { + implMissing(__FILE__,__LINE__); +// if (bundleTracker is null) { +// if (JobManager.DEBUG) +// JobMessages.message("Bundle tracker is not set"); //$NON-NLS-1$ +// return null; +// } +// PackageAdmin packageAdmin = (PackageAdmin) bundleTracker.getService(); +// if (object is null) +// return null; +// if (packageAdmin is null) +// return null; +// Bundle source = packageAdmin.getBundle(object.getClass()); +// if (source !is null && source.getSymbolicName() !is null) +// return source.getSymbolicName(); + return null; + } + + /** + * Calculates whether the job plugin should set worker threads to be daemon + * threads. When workers are daemon threads, the job plugin does not need + * to be explicitly shut down because the VM can exit while workers are still + * alive. + * @return <code>true</code> if all worker threads should be daemon threads, + * and <code>false</code> otherwise. + */ + bool useDaemonThreads() { + implMissing(__FILE__,__LINE__); + return false; +// BundleContext context = JobActivator.getContext(); +// if (context is null) { +// //we are running stand-alone, so consult global system property +// String value = System.getProperty(IJobManager.PROP_USE_DAEMON_THREADS); +// //default to use daemon threads if property is absent +// if (value is null) +// return true; +// return "true".equalsIgnoreCase(value); //$NON-NLS-1$ +// } +// //only use daemon threads if the property is defined +// final String value = context.getProperty(IJobManager.PROP_USE_DAEMON_THREADS); +// //if value is absent, don't use daemon threads to maintain legacy behaviour +// if (value is null) +// return false; +// return "true".equalsIgnoreCase(value); //$NON-NLS-1$ + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/internal/jobs/JobQueue.d Tue Aug 12 02:34:21 2008 +0200 @@ -0,0 +1,146 @@ +/******************************************************************************* + * Copyright (c) 2003, 2006 IBM Corporation 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: + * IBM - Initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.core.internal.jobs.JobQueue; + +import dwt.dwthelper.utils; + +import dwtx.core.runtime.Assert; +import dwtx.core.runtime.IProgressMonitor; +import dwtx.core.runtime.IStatus; +import dwtx.core.runtime.Status; + +import dwtx.core.internal.jobs.InternalJob; + +/** + * A linked list based priority queue. Either the elements in the queue must + * implement Comparable, or a Comparator must be provided. + */ +public class JobQueue { + /** + * The dummy entry sits between the head and the tail of the queue. + * dummy.previous() is the head, and dummy.next() is the tail. + */ + private final InternalJob dummy; + + /** + * If true, conflicting jobs will be allowed to overtake others in the + * queue that have lower priority. If false, higher priority jumps can only + * move up the queue by overtaking jobs that they don't conflict with. + */ + private bool allowConflictOvertaking; + + /** + * Create a new job queue. + */ + public this(bool allowConflictOvertaking) { + //compareTo on dummy is never called + dummy = new class("Queue-Head") InternalJob {//$NON-NLS-1$ + this( String name ){ + super(name); + } + public IStatus run(IProgressMonitor m) { + return Status.OK_STATUS; + } + }; + dummy.setNext(dummy); + dummy.setPrevious(dummy); + this.allowConflictOvertaking = allowConflictOvertaking; + } + + /** + * remove all elements + */ + public void clear() { + dummy.setNext(dummy); + dummy.setPrevious(dummy); + } + + /** + * Return and remove the element with highest priority, or null if empty. + */ + public InternalJob dequeue() { + InternalJob toRemove = dummy.previous(); + if (toRemove is dummy) + return null; + return toRemove.remove(); + } + + /** + * Adds an item to the queue + */ + public void enqueue(InternalJob newEntry) { + //assert new entry is does not already belong to some other data structure + Assert.isTrue(newEntry.next() is null); + Assert.isTrue(newEntry.previous() is null); + InternalJob tail = dummy.next(); + //overtake lower priority jobs. Only overtake conflicting jobs if allowed to + while (canOvertake(newEntry, tail)) + tail = tail.next(); + //new entry is smaller than tail + final InternalJob tailPrevious = tail.previous(); + newEntry.setNext(tail); + newEntry.setPrevious(tailPrevious); + tailPrevious.setNext(newEntry); + tail.setPrevious(newEntry); + } + + /** + * Returns whether the new entry to overtake the existing queue entry. + * @param newEntry The entry to be added to the queue + * @param queueEntry The existing queue entry + */ + private bool canOvertake(InternalJob newEntry, InternalJob queueEntry) { + //can never go past the end of the queue + if (queueEntry is dummy) + return false; + //if the new entry was already in the wait queue, ensure it is re-inserted in correct position (bug 211799) + if (newEntry.getWaitQueueStamp() > 0 && newEntry.getWaitQueueStamp() < queueEntry.getWaitQueueStamp()) + return true; + //if the new entry has lower priority, there is no need to overtake the existing entry + if (queueEntry.compareTo(newEntry) >= 0) + return false; + //the new entry has higher priority, but only overtake the existing entry if the queue allows it + return allowConflictOvertaking || !newEntry.isConflicting(queueEntry); + } + + /** + * Removes the given element from the queue. + */ + public void remove(InternalJob toRemove) { + toRemove.remove(); + //previous of toRemove might now bubble up + } + + /** + * The given object has changed priority. Reshuffle the heap until it is + * valid. + */ + public void resort(InternalJob entry) { + remove(entry); + enqueue(entry); + } + + /** + * Returns true if the queue is empty, and false otherwise. + */ + public bool isEmpty() { + return dummy.next() is dummy; + } + + /** + * Return greatest element without removing it, or null if empty + */ + public InternalJob peek() { + return dummy.previous() is dummy ? null : dummy.previous(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/internal/jobs/JobStatus.d Tue Aug 12 02:34:21 2008 +0200 @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2004, 2006 IBM Corporation 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: + * IBM - Initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.core.internal.jobs.JobStatus; + +import dwt.dwthelper.utils; + +import dwtx.core.runtime.Status; +import dwtx.core.runtime.jobs.IJobStatus; +import dwtx.core.runtime.jobs.Job; +import dwtx.core.internal.jobs.JobManager; + +/** + * Standard implementation of the IJobStatus interface. + */ +public class JobStatus : Status, IJobStatus { + private Job job; + + /** + * Creates a new job status with no interesting error code or exception. + * @param severity + * @param job + * @param message + */ + public this(int severity, Job job, String message) { + super(severity, JobManager.PI_JOBS, 1, message, null); + this.job = job; + } + + /* (non-Javadoc) + * @see dwtx.core.runtime.jobs.IJobStatus#getJob() + */ + public Job getJob() { + return job; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/internal/jobs/LockManager.d Tue Aug 12 02:34:21 2008 +0200 @@ -0,0 +1,301 @@ +/******************************************************************************* + * Copyright (c) 2003, 2006 IBM Corporation 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: + * IBM - Initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.core.internal.jobs.LockManager; + +import tango.core.Thread; +import dwt.dwthelper.utils; +import dwtx.dwtxhelper.Collection; + +import dwtx.core.internal.runtime.RuntimeLog; +import dwtx.core.runtime.CoreException; +import dwtx.core.runtime.IStatus; +import dwtx.core.runtime.MultiStatus; +import dwtx.core.runtime.Status; +import dwtx.core.runtime.jobs.ISchedulingRule; +import dwtx.core.runtime.jobs.LockListener; + +import dwtx.core.internal.jobs.OrderedLock; +import dwtx.core.internal.jobs.DeadlockDetector; +import dwtx.core.internal.jobs.Deadlock; +import dwtx.core.internal.jobs.JobManager; +import dwtx.core.internal.jobs.Worker; + +/** + * Stores the only reference to the graph that contains all the known + * relationships between locks, rules, and the threads that own them. + * Synchronizes all access to the graph on the only instance that exists in this class. + * + * Also stores the state of suspended locks so that they can be re-acquired with + * the proper lock depth. + */ +public class LockManager { + /** + * This class captures the state of suspended locks. + * Locks are suspended if deadlock is detected. + */ + private static class LockState { + private int depth; + private OrderedLock lock; + + /** + * Suspends ownership of the given lock, and returns the saved state. + */ + protected static LockState suspend(OrderedLock lock) { + LockState state = new LockState(); + state.lock = lock; + state.depth = lock.forceRelease_package(); + return state; + } + + /** + * Re-acquires a suspended lock and reverts to the correct lock depth. + */ + public void resume() { + //spin until the lock is successfully acquired + //NOTE: spinning here allows the UI thread to service pending syncExecs + //if the UI thread is waiting to acquire a lock. + while (true) { + try { + if (lock.acquire(Long.MAX_VALUE)) + break; + } catch (InterruptedException e) { + //ignore and loop + } + } + lock.setDepth_package(depth); + } + } + + //the lock listener for this lock manager + protected LockListener lockListener; + /* + * The internal data structure that stores all the relationships + * between the locks (or rules) and the threads that own them. + */ + private DeadlockDetector locks; + /* + * Stores thread - stack pairs where every entry in the stack is an array + * of locks that were suspended while the thread was acquiring more locks + * (a stack is needed because when a thread tries to re-aquire suspended locks, + * it can cause deadlock, and some locks it owns can be suspended again) + */ + private HashMap suspendedLocks; + + public this() { +// super(); + locks = new DeadlockDetector(); + suspendedLocks = new HashMap(); + } + + /* (non-Javadoc) + * Method declared on LockListener + */ + public void aboutToRelease() { + if (lockListener is null) + return; + try { + lockListener.aboutToRelease(); + } catch (Exception e) { + handleException(e); +// } catch (LinkageError e) { +// handleException(e); + } + } + + /* (non-Javadoc) + * Method declared on LockListener + */ + public bool aboutToWait(Thread lockOwner) { + if (lockListener is null) + return false; + try { + return lockListener.aboutToWait(lockOwner); + } catch (Exception e) { + handleException(e); +// } catch (LinkageError e) { +// handleException(e); + } + return false; + } + + /** + * This thread has just acquired a lock. Update graph. + */ + void addLockThread(Thread thread, ISchedulingRule lock) { + if (locks is null) + return; + try { + synchronized (locks) { + locks.lockAcquired(thread, lock); + } + } catch (Exception e) { + handleInternalError(e); + } + } + + /** + * This thread has just been refused a lock. Update graph and check for deadlock. + */ + void addLockWaitThread(Thread thread, ISchedulingRule lock) { + if (locks is null) + return; + try { + Deadlock found = null; + synchronized (locks) { + found = locks.lockWaitStart(thread, lock); + } + if (found is null) + return; + // if deadlock was detected, the found variable will contain all the information about it, + // including which locks to suspend for which thread to resolve the deadlock. + ISchedulingRule[] toSuspend = found.getLocks(); + LockState[] suspended = new LockState[toSuspend.length]; + for (int i = 0; i < toSuspend.length; i++) + suspended[i] = LockState.suspend(cast(OrderedLock) toSuspend[i]); + synchronized (suspendedLocks) { + Stack prevLocks = cast(Stack) suspendedLocks.get(found.getCandidate()); + if (prevLocks is null) + prevLocks = new Stack(); + prevLocks.push(new ArrayWrapperObject(suspended)); + suspendedLocks.put(found.getCandidate(), prevLocks); + } + } catch (Exception e) { + handleInternalError(e); + } + } + + /** + * Handles exceptions that occur while calling third party code from within the + * LockManager. This is essentially an in-lined version of Platform.run(ISafeRunnable) + */ + private static void handleException(Exception e) { + IStatus status; + if (cast(CoreException)e ) { + //logged message should not be translated + status = new MultiStatus(JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, "LockManager.handleException", e); //$NON-NLS-1$ + (cast(MultiStatus) status).merge((cast(CoreException) e).getStatus()); + } else { + status = new Status(IStatus.ERROR, JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, "LockManager.handleException", e); //$NON-NLS-1$ + } + RuntimeLog.log(status); + } + + /** + * There was an internal error in the deadlock detection code. Shut the entire + * thing down to prevent further errors. Recovery is too complex as it + * requires freezing all threads and inferring the present lock state. + */ + private void handleInternalError(Exception t) { + try { + handleException(t); + handleException(new Exception(locks.toDebugString())); + } catch (Exception e2) { + //ignore failure to log or to create the debug string + } + //discard the deadlock detector for good + locks = null; + } + + /** + * Returns true IFF the underlying graph is empty. + * For debugging purposes only. + */ + public bool isEmpty() { + return locks.isEmpty(); + } + + /** + * Returns true IFF this thread either owns, or is waiting for, any locks or rules. + */ + public bool isLockOwner() { + //all job threads have to be treated as lock owners because UI thread + //may try to join a job + Thread current = Thread.getThis(); + if (cast(Worker)current ) + return true; + if (locks is null) + return false; + synchronized (locks) { + return locks.contains(Thread.getThis()); + } + } + + /** + * Creates and returns a new lock. + */ + public synchronized OrderedLock newLock() { + return new OrderedLock(this); + } + + /** + * Releases all the acquires that were called on the given rule. Needs to be called only once. + */ + void removeLockCompletely(Thread thread, ISchedulingRule rule) { + if (locks is null) + return; + try { + synchronized (locks) { + locks.lockReleasedCompletely(thread, rule); + } + } catch (Exception e) { + handleInternalError(e); + } + } + + /** + * This thread has just released a lock. Update graph. + */ + void removeLockThread(Thread thread, ISchedulingRule lock) { + try { + synchronized (locks) { + locks.lockReleased(thread, lock); + } + } catch (Exception e) { + handleInternalError(e); + } + } + + /** + * This thread has just stopped waiting for a lock. Update graph. + */ + void removeLockWaitThread(Thread thread, ISchedulingRule lock) { + try { + synchronized (locks) { + locks.lockWaitStop(thread, lock); + } + } catch (Exception e) { + handleInternalError(e); + } + } + + /** + * Resumes all the locks that were suspended while this thread was waiting to acquire another lock. + */ + void resumeSuspendedLocks(Thread owner) { + LockState[] toResume; + synchronized (suspendedLocks) { + Stack prevLocks = cast(Stack) suspendedLocks.get(owner); + if (prevLocks is null) + return; + toResume = arrayFromObject!(LockState)( prevLocks.pop() ); + if (prevLocks.empty()) + suspendedLocks.remove(owner); + } + for (int i = 0; i < toResume.length; i++) + toResume[i].resume(); + } + + public void setLockListener(LockListener listener) { + this.lockListener = listener; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/internal/jobs/ObjectMap.d Tue Aug 12 02:34:21 2008 +0200 @@ -0,0 +1,354 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 IBM Corporation 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: + * IBM Corporation - initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.core.internal.jobs.ObjectMap; + +import dwt.dwthelper.utils; +import dwtx.dwtxhelper.Collection; +import dwtx.core.internal.jobs.StringPool; + +/** + * A specialized map implementation that is optimized for a small set of object + * keys. + * + * Implemented as a single array that alternates keys and values. + * + * Note: This class is copied from dwtx.core.resources + */ +public class ObjectMap : Map { + // 8 attribute keys, 8 attribute values + protected static const int DEFAULT_SIZE = 16; + protected static const int GROW_SIZE = 10; + protected int count = 0; + protected Object[] elements = null; + + /** + * Creates a new object map. + * + * @param initialCapacity + * The initial number of elements that will fit in the map. + */ + public this(int initialCapacity) { + if (initialCapacity > 0) + elements = new Object[Math.max(initialCapacity * 2, 0)]; + } + + /** + * Creates a new object map of the same size as the given map and populate + * it with the key/attribute pairs found in the map. + * + * @param map + * The entries in the given map will be added to the new map. + */ + public this(Map map) { + this(map.size()); + putAll(map); + } + + /** + * @see Map#clear() + */ + public void clear() { + elements = null; + count = 0; + } + + /** + * @see java.lang.Object#clone() + */ + public Object clone() { + return new ObjectMap(this); + } + + /** + * @see Map#containsKey(java.lang.Object) + */ + public bool containsKey(Object key) { + if (elements is null || count is 0) + return false; + for (int i = 0; i < elements.length; i = i + 2) + if (elements[i] !is null && elements[i].opEquals(key)) + return true; + return false; + } + public bool containsKey(String key) { + return containsKey(stringcast(key)); + } + /** + * @see Map#containsValue(java.lang.Object) + */ + public bool containsValue(Object value) { + if (elements is null || count is 0) + return false; + for (int i = 1; i < elements.length; i = i + 2) + if (elements[i] !is null && elements[i].opEquals(value)) + return true; + return false; + } + + /** + * @see Map#entrySet() + * + * Note: This implementation does not conform properly to the + * specification in the Map interface. The returned collection will not + * be bound to this map and will not remain in sync with this map. + */ + public Set entrySet() { + return count is 0 ? Collections.EMPTY_SET : toHashMap().entrySet(); + } + + /** + * @see Object#equals(java.lang.Object) + */ + public override int opEquals(Object o) { + if (!(cast(Map)o )) + return false; + Map other = cast(Map) o; + //must be same size + if (count !is other.size()) + return false; + //keysets must be equal + if (!(cast(Object)keySet()).opEquals(cast(Object)other.keySet())) + return false; + //values for each key must be equal + for (int i = 0; i < elements.length; i = i + 2) { + if (elements[i] !is null && (!elements[i + 1].opEquals(other.get(elements[i])))) + return false; + } + return true; + } + + /** + * @see Map#get(java.lang.Object) + */ + public Object get(Object key) { + if (elements is null || count is 0) + return null; + for (int i = 0; i < elements.length; i = i + 2) + if (elements[i] !is null && elements[i].opEquals(key)) + return elements[i + 1]; + return null; + } + public Object get(String key) { + return get(stringcast(key)); + } + + /** + * The capacity of the map has been exceeded, grow the array by GROW_SIZE to + * accomodate more entries. + */ + protected void grow() { + Object[] expanded = new Object[elements.length + GROW_SIZE]; + System.arraycopy(elements, 0, expanded, 0, elements.length); + elements = expanded; + } + + /** + * @see Object#hashCode() + */ + public override hash_t toHash() { + int hash = 0; + for (int i = 0; i < elements.length; i = i + 2) { + if (elements[i] !is null) { + hash += elements[i].toHash(); + } + } + return hash; + } + + /** + * @see Map#isEmpty() + */ + public bool isEmpty() { + return count is 0; + } + + /** + * Returns all keys in this table as an array. + */ + public String[] keys() { + String[] result = new String[count]; + int next = 0; + for (int i = 0; i < elements.length; i = i + 2) + if (elements[i] !is null) + result[next++] = stringcast( elements[i] ); + return result; + } + + /** + * @see Map#keySet() + * + * Note: This implementation does not conform properly to the + * specification in the Map interface. The returned collection will not + * be bound to this map and will not remain in sync with this map. + */ + public Set keySet() { + Set result = new HashSet(size()); + for (int i = 0; i < elements.length; i = i + 2) { + if (elements[i] !is null) { + result.add(elements[i]); + } + } + return result; + } + + /** + * @see Map#put(java.lang.Object, java.lang.Object) + */ + public Object put(Object key, Object value) { + if (key is null) + throw new NullPointerException(); + if (value is null) + return remove(key); + + // handle the case where we don't have any attributes yet + if (elements is null) + elements = new Object[DEFAULT_SIZE]; + if (count is 0) { + elements[0] = key; + elements[1] = value; + count++; + return null; + } + + int emptyIndex = -1; + // replace existing value if it exists + for (int i = 0; i < elements.length; i += 2) { + if (elements[i] !is null) { + if (elements[i].opEquals(key)) { + Object oldValue = elements[i + 1]; + elements[i + 1] = value; + return oldValue; + } + } else if (emptyIndex is -1) { + // keep track of the first empty index + emptyIndex = i; + } + } + // this will put the emptyIndex greater than the size but + // that's ok because we will grow first. + if (emptyIndex is -1) + emptyIndex = count * 2; + + // otherwise add it to the list of elements. + // grow if necessary + if (elements.length <= (count * 2)) + grow(); + elements[emptyIndex] = key; + elements[emptyIndex + 1] = value; + count++; + return null; + } + public Object put(String key, Object value) { + return put( stringcast(key), value ); + } + public Object put(Object key, String value) { + return put( key, stringcast(value) ); + } + public Object put(String key, String value) { + return put( stringcast(key), stringcast(value) ); + } + + /** + * @see Map#putAll(java.util.Map) + */ + public void putAll(Map map) { + for (Iterator i = map.keySet().iterator(); i.hasNext();) { + Object key = i.next(); + Object value = map.get(key); + put(key, value); + } + } + + /** + * @see Map#remove(java.lang.Object) + */ + public Object remove(Object key) { + if (elements is null || count is 0) + return null; + for (int i = 0; i < elements.length; i = i + 2) { + if (elements[i] !is null && elements[i].opEquals(key)) { + elements[i] = null; + Object result = elements[i + 1]; + elements[i + 1] = null; + count--; + return result; + } + } + return null; + } + public Object remove(String key) { + return remove( stringcast(key)); + } + + /* (non-Javadoc + * Method declared on IStringPoolParticipant + */ + public void shareStrings(StringPool set) { + //copy elements for thread safety + Object[] array = elements; + if (array is null) + return; + for (int i = 0; i < array.length; i++) { + Object o = array[i]; + if (cast(ArrayWrapperString)o ) + array[i] = stringcast(set.add(stringcast( o))); + } + } + + /** + * @see Map#size() + */ + public int size() { + return count; + } + + /** + * Creates a new hash map with the same contents as this map. + */ + private HashMap toHashMap() { + HashMap result = new HashMap(size()); + for (int i = 0; i < elements.length; i = i + 2) { + if (elements[i] !is null) { + result.put(elements[i], elements[i + 1]); + } + } + return result; + } + + /** + * @see Map#values() + * + * Note: This implementation does not conform properly to the + * specification in the Map interface. The returned collection will not + * be bound to this map and will not remain in sync with this map. + */ + public Collection values() { + Set result = new HashSet(size()); + for (int i = 1; i < elements.length; i = i + 2) { + if (elements[i] !is null) { + result.add(elements[i]); + } + } + return result; + } + + public int opApply (int delegate(ref Object value) dg){ + implMissing(__FILE__, __LINE__ ); + return 0; + } + public int opApply (int delegate(ref Object key, ref Object value) dg){ + implMissing(__FILE__, __LINE__ ); + return 0; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/internal/jobs/OrderedLock.d Tue Aug 12 02:34:21 2008 +0200 @@ -0,0 +1,318 @@ +/******************************************************************************* + * Copyright (c) 2003, 2006 IBM Corporation 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: + * IBM - Initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.core.internal.jobs.OrderedLock; + +import tango.text.convert.Format; +import tango.core.Thread; +import tango.io.Stdout; +import dwt.dwthelper.utils; + +import dwtx.core.runtime.Assert; +import dwtx.core.runtime.jobs.ILock; +import dwtx.core.runtime.jobs.ISchedulingRule; + +import dwtx.core.internal.jobs.LockManager; +import dwtx.core.internal.jobs.Queue; +import dwtx.core.internal.jobs.Semaphore; + +/** + * A lock used to control write access to an exclusive resource. + * + * The lock avoids circular waiting deadlocks by detecting the deadlocks + * and resolving them through the suspension of all locks owned by one + * of the threads involved in the deadlock. This makes it impossible for n such + * locks to deadlock while waiting for each other. The downside is that this means + * that during an interval when a process owns a lock, it can be forced + * to give the lock up and wait until all locks it requires become + * available. This removes the feature of exclusive access to the + * resource in contention for the duration between acquire() and + * release() calls. + * + * The lock implementation prevents starvation by granting the + * lock in the same order in which acquire() requests arrive. In + * this scheme, starvation is only possible if a thread retains + * a lock indefinitely. + */ +public class OrderedLock : ILock, ISchedulingRule { + + private static const bool DEBUG = false; + /** + * Locks are sequentially ordered for debugging purposes. + */ + private static int nextLockNumber = 0; + /** + * The thread of the operation that currently owns the lock. + */ + private /+volatile+/ Thread currentOperationThread; + /** + * Records the number of successive acquires in the same + * thread. The lock is released only when the depth + * reaches zero. + */ + private int depth; + /** + * The manager that implements the deadlock detection and resolution protocol. + */ + private const LockManager manager; + private const int number; + + /** + * Queue of semaphores for threads currently waiting + * on the lock. This queue is not thread-safe, so access + * to this queue must be synchronized on the lock instance. + */ + private const Queue operations; + + /** + * Creates a new workspace lock. + */ + this(LockManager manager) { + + operations = new Queue(); + + this.manager = manager; + this.number = nextLockNumber++; + } + + /* (non-Javadoc) + * @see Locks.ILock#acquire() + */ + public void acquire() { + //spin until the lock is successfully acquired + //NOTE: spinning here allows the UI thread to service pending syncExecs + //if the UI thread is waiting to acquire a lock. + while (true) { + try { + if (acquire(Long.MAX_VALUE)) + return; + } catch (InterruptedException e) { + //ignore and loop + } + } + } + + /* (non-Javadoc) + * @see Locks.ILock#acquire(long) + */ + public bool acquire(long delay) { + implMissing(__FILE__, __LINE__ ); +// if (Thread.interrupted()) +// throw new InterruptedException(); + + bool success = false; + if (delay <= 0) + return attempt(); + Semaphore semaphore = createSemaphore(); + if (semaphore is null) + return true; + if (DEBUG) + Stdout.formatln("[{}] Operation waiting to be executed... ", Thread.getThis(), this); //$NON-NLS-1$ //$NON-NLS-2$ + success = doAcquire(semaphore, delay); + manager.resumeSuspendedLocks(Thread.getThis()); + if (DEBUG && success) + Stdout.formatln("[{}] Operation started... ", Thread.getThis(), this); //$NON-NLS-1$ //$NON-NLS-2$ + else if (DEBUG) + Stdout.formatln("[{}] Operation timed out... ", Thread.getThis(), this); //$NON-NLS-1$ //$NON-NLS-2$ + return success; + } + + /** + * Attempts to acquire the lock. Returns false if the lock is not available and + * true if the lock has been successfully acquired. + */ + private synchronized bool attempt() { + //return true if we already own the lock + //also, if nobody is waiting, grant the lock immediately + if ((currentOperationThread is Thread.getThis()) || (currentOperationThread is null && operations.isEmpty())) { + depth++; + setCurrentOperationThread(Thread.getThis()); + return true; + } + return false; + } + + /* (non-Javadoc) + * @see dwtx.core.runtime.jobs.ISchedulingRule#contains(dwtx.core.runtime.jobs.ISchedulingRule) + */ + public bool contains(ISchedulingRule rule) { + return false; + } + + /** + * Returns null if acquired and a Semaphore object otherwise. If a + * waiting semaphore already exists for this thread, it will be returned, + * otherwise a new semaphore will be created, enqueued, and returned. + */ + private synchronized Semaphore createSemaphore() { + return attempt() ? null : enqueue(new Semaphore(Thread.getThis())); + } + + /** + * Attempts to acquire this lock. Callers will block until this lock comes available to + * them, or until the specified delay has elapsed. + */ + private bool doAcquire(Semaphore semaphore, long delay) { + bool success = false; + //notify hook to service pending syncExecs before falling asleep + if (manager.aboutToWait(this.currentOperationThread)) { + //hook granted immediate access + //remove semaphore for the lock request from the queue + //do not log in graph because this thread did not really get the lock + removeFromQueue(semaphore); + depth++; + manager.addLockThread(currentOperationThread, this); + return true; + } + //Make sure the semaphore is in the queue before we start waiting + //It might have been removed from the queue while servicing syncExecs + //This is will return our existing semaphore if it is still in the queue + semaphore = createSemaphore(); + if (semaphore is null) + return true; + manager.addLockWaitThread(Thread.getThis(), this); + try { + success = semaphore.acquire(delay); + } catch (InterruptedException e) { + if (DEBUG) + Stdout.formatln(Format("[{}] Operation interrupted while waiting... :-|", Thread.getThis())); //$NON-NLS-1$ //$NON-NLS-2$ + throw e; + } + if (success) { + depth++; + updateCurrentOperation(); + } else { + removeFromQueue(semaphore); + manager.removeLockWaitThread(Thread.getThis(), this); + } + return success; + } + + /** + * Releases this lock from the thread that used to own it. + * Grants this lock to the next thread in the queue. + */ + private synchronized void doRelease() { + //notify hook + manager.aboutToRelease(); + depth = 0; + Semaphore next = cast(Semaphore) operations.peek(); + setCurrentOperationThread(null); + if (next !is null) + next.release(); + } + + /** + * If there is another semaphore with the same runnable in the + * queue, the other is returned and the new one is not added. + */ + private synchronized Semaphore enqueue(Semaphore newSemaphore) { + Semaphore semaphore = cast(Semaphore) operations.get(newSemaphore); + if (semaphore is null) { + operations.enqueue(newSemaphore); + return newSemaphore; + } + return semaphore; + } + + /** + * Suspend this lock by granting the lock to the next lock in the queue. + * Return the depth of the suspended lock. + */ + protected int forceRelease() { + int oldDepth = depth; + doRelease(); + return oldDepth; + } + package int forceRelease_package() { + return forceRelease(); + } + + /* (non-Javadoc) + * @see Locks.ILock#getDepth() + */ + public int getDepth() { + return depth; + } + + /* (non-Javadoc) + * @see dwtx.core.runtime.jobs.ISchedulingRule#isConflicting(dwtx.core.runtime.jobs.ISchedulingRule) + */ + public bool isConflicting(ISchedulingRule rule) { + return rule is this; + } + + /* (non-Javadoc) + * @see Locks.ILock#release() + */ + public void release() { + if (depth is 0) + return; + //only release the lock when the depth reaches zero + Assert.isTrue(depth >= 0, "Lock released too many times"); //$NON-NLS-1$ + if (--depth is 0) + doRelease(); + else + manager.removeLockThread(currentOperationThread, this); + } + + /** + * Removes a semaphore from the queue of waiting operations. + * + * @param semaphore The semaphore to remove + */ + private synchronized void removeFromQueue(Semaphore semaphore) { + operations.remove(semaphore); + } + + /** + * If newThread is null, release this lock from its previous owner. + * If newThread is not null, grant this lock to newThread. + */ + private void setCurrentOperationThread(Thread newThread) { + if ((currentOperationThread !is null) && (newThread is null)) + manager.removeLockThread(currentOperationThread, this); + this.currentOperationThread = newThread; + if (currentOperationThread !is null) + manager.addLockThread(currentOperationThread, this); + } + + /** + * Forces the lock to be at the given depth. + * Used when re-acquiring a suspended lock. + */ + protected void setDepth(int newDepth) { + for (int i = depth; i < newDepth; i++) { + manager.addLockThread(currentOperationThread, this); + } + this.depth = newDepth; + } + package void setDepth_package(int newDepth) { + return setDepth(newDepth); + } + + /** + * For debugging purposes only. + */ + public String toString() { + return Format("OrderedLock ({})", number ); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * This lock has just been granted to a new thread (the thread waited for it). + * Remove the request from the queue and update both the graph and the lock. + */ + private synchronized void updateCurrentOperation() { + operations.dequeue(); + setCurrentOperationThread(Thread.getThis()); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/internal/jobs/Queue.d Tue Aug 12 02:34:21 2008 +0200 @@ -0,0 +1,190 @@ +/******************************************************************************* + * Copyright (c) 2003, 2006 IBM Corporation 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: + * IBM - Initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.core.internal.jobs.Queue; + +import tango.text.convert.Format; +import dwt.dwthelper.utils; +import dwtx.dwtxhelper.Collection; + +/** + * A Queue of objects. + */ +public class Queue { + protected Object[] elements_; + protected int head; + protected bool reuse; + protected int tail; + + public this() { + this(20, false); + } + + /** + * The parameter reuse indicates what do you want to happen with + * the object reference when you remove it from the queue. If + * reuse is false the queue no longer holds a reference to the + * object when it is removed. If reuse is true you can use the + * method getNextAvailableObject to get an used object, set its + * new values and add it again to the queue. + */ + public this(int size, bool reuse) { + elements_ = new Object[size]; + head = tail = 0; + this.reuse = reuse; + } + + /** + * Adds an object to the tail of the queue. + */ + public void enqueue(Object element) { + int newTail = increment(tail); + if (newTail is head) { + grow(); + newTail = tail + 1; + } + elements_[tail] = element; + tail = newTail; + } + + /** + * This method does not affect the queue itself. It is only a + * helper to decrement an index in the queue. + */ + public int decrement(int index) { + return (index is 0) ? (elements_.length - 1) : index - 1; + } + + public Iterator elements() { + /**/ + if (isEmpty()) + return (new ArrayList(0)).iterator(); + + /* if head < tail we can use the same array */ + if (head <= tail) + return Arrays.asList(elements_).iterator(); + + /* otherwise we need to create a new array */ + Object[] newElements = new Object[size()]; + int end = (elements_.length - head); + System.arraycopy(elements_, head, newElements, 0, end); + System.arraycopy(elements_, 0, newElements, end, tail); + return Arrays.asList(newElements).iterator(); + } + + public Object get(Object o) { + int index = head; + while (index !is tail) { + if (elements_[index].opEquals(o)) + return elements_[index]; + index = increment(index); + } + return null; + } + + /** + * Removes the given object from the queue. Shifts the underlying array. + */ + public bool remove(Object o) { + int index = head; + //find the object to remove + while (index !is tail) { + if (elements_[index].opEquals(o)) + break; + index = increment(index); + } + //if element wasn't found, return + if (index is tail) + return false; + //store a reference to it (needed for reuse of objects) + Object toRemove = elements_[index]; + int nextIndex = -1; + while (index !is tail) { + nextIndex = increment(index); + if (nextIndex !is tail) + elements_[index] = elements_[nextIndex]; + + index = nextIndex; + } + //decrement tail + tail = decrement(tail); + + //if objects are reused, transfer the reference that is removed to the end of the queue + //otherwise set the element after the last one to null (to avoid duplicate references) + elements_[tail] = reuse ? toRemove : null; + return true; + } + + protected void grow() { + int newSize = cast(int) (elements_.length * 1.5); + Object[] newElements = new Object[newSize]; + if (tail >= head) + System.arraycopy(elements_, head, newElements, head, size()); + else { + int newHead = newSize - (elements_.length - head); + System.arraycopy(elements_, 0, newElements, 0, tail + 1); + System.arraycopy(elements_, head, newElements, newHead, (newSize - newHead)); + head = newHead; + } + elements_ = newElements; + } + + /** + * This method does not affect the queue itself. It is only a + * helper to increment an index in the queue. + */ + public int increment(int index) { + return (index is (elements_.length - 1)) ? 0 : index + 1; + } + + public bool isEmpty() { + return tail is head; + } + + public Object peek() { + return elements_[head]; + } + + /** + * Removes an returns the item at the head of the queue. + */ + public Object dequeue() { + if (isEmpty()) + return null; + Object result = peek(); + if (!reuse) + elements_[head] = null; + head = increment(head); + return result; + } + + public int size() { + return tail > head ? (tail - head) : ((elements_.length - head) + tail); + } + + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("["); //$NON-NLS-1$ + if (!isEmpty()) { + Iterator it = elements(); + while (true) { + sb.append(Format("{}",it.next())); + if (it.hasNext()) + sb.append(", "); //$NON-NLS-1$ + else + break; + } + } + sb.append("]"); //$NON-NLS-1$ + return sb.toString(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/internal/jobs/Semaphore.d Tue Aug 12 02:34:21 2008 +0200 @@ -0,0 +1,80 @@ +/******************************************************************************* + * Copyright (c) 2003, 2006 IBM Corporation 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: + * IBM Corporation - initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.core.internal.jobs.Semaphore; + +import tango.core.Thread; +import tango.core.sync.Mutex; +import tango.core.sync.Condition; +import dwt.dwthelper.utils; +import dwt.dwthelper.Runnable; +import tango.text.convert.Format; + +public class Semaphore { + protected long notifications; + protected Thread runnable; + + private Mutex mutex; + private Condition condition; + + public this(Thread runnable) { + mutex = new Mutex; + condition = new Condition(mutex); + this.runnable = runnable; + notifications = 0; + } + + /** + * Attempts to acquire this semaphore. Returns true if it was successfully acquired, + * and false otherwise. + */ + public bool acquire(long delay) { + synchronized(mutex){ + implMissing( __FILE__, __LINE__ ); +// DWT +// if (Thread.interrupted()) +// throw new InterruptedException(); + long start = System.currentTimeMillis(); + long timeLeft = delay; + while (true) { + if (notifications > 0) { + notifications--; + return true; + } + if (timeLeft <= 0) + return false; + condition.wait(timeLeft/1000.0f); + timeLeft = start + delay - System.currentTimeMillis(); + } + } + } + + public override int opEquals(Object obj) { + return (runnable is (cast(Semaphore) obj).runnable); + } + + public override hash_t toHash() { + return runnable is null ? 0 : (cast(Object)runnable).toHash(); + } + + public void release() { + synchronized( mutex ){ + notifications++; + condition.notifyAll(); + } + } + + // for debug only + public String toString() { + return Format("Semaphore({})", cast(Object) runnable ); //$NON-NLS-1$ //$NON-NLS-2$ + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/internal/jobs/StringPool.d Tue Aug 12 02:34:21 2008 +0200 @@ -0,0 +1,75 @@ +/******************************************************************************* + * Copyright (c) 2005 IBM Corporation 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: + * IBM - Initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.core.internal.jobs.StringPool; + +import dwt.dwthelper.utils; +import dwtx.dwtxhelper.Collection; + +/** + * A string pool is used for sharing strings in a way that eliminates duplicate + * equal strings. A string pool instance can be maintained over a long period + * of time, or used as a temporary structure during a string sharing pass over + * a data structure. + * <p> + * This class is not intended to be subclassed by clients. + * </p> + * + * Note: This class is copied from dwtx.core.resources + * + * @since 3.1 + */ +public final class StringPool { + private int savings; + private const HashMap map; + + public this(){ + map = new HashMap(); + } + + /** + * Adds a <code>String</code> to the pool. Returns a <code>String</code> + * that is equal to the argument but that is unique within this pool. + * @param string The string to add to the pool + * @return A string that is equal to the argument. + */ + public String add(String string) { + if (string is null) + return string; + Object result = map.get(string); + if (result !is null) { + if (stringcast(result) !is string) + savings += 44 + 2 * string.length; + return stringcast( result ); + } + map.put(string, string); + return string; + } + + /** + * Returns an estimate of the size in bytes that was saved by sharing strings in + * the pool. In particular, this returns the size of all strings that were added to the + * pool after an equal string had already been added. This value can be used + * to estimate the effectiveness of a string sharing operation, in order to + * determine if or when it should be performed again. + * + * In some cases this does not precisely represent the number of bytes that + * were saved. For example, say the pool already contains string S1. Now + * string S2, which is equal to S1 but not identical, is added to the pool five + * times. This method will return the size of string S2 multiplied by the + * number of times it was added, even though the actual savings in this case + * is only the size of a single copy of S2. + */ + public int getSavedStringCount() { + return savings; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/internal/jobs/ThreadJob.d Tue Aug 12 02:34:21 2008 +0200 @@ -0,0 +1,347 @@ +/******************************************************************************* + * Copyright (c) 2004, 2007 IBM Corporation 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: + * IBM - Initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.core.internal.jobs.ThreadJob; + +import dwt.dwthelper.utils; +import tango.text.convert.Format; +import tango.io.Stdout; +import tango.core.Thread; +import tango.core.sync.Mutex; +import tango.core.sync.Condition; + +import dwtx.core.internal.runtime.RuntimeLog; +import dwtx.core.runtime.Assert; +import dwtx.core.runtime.IProgressMonitor; +import dwtx.core.runtime.IStatus; +import dwtx.core.runtime.OperationCanceledException; +import dwtx.core.runtime.Status; +import dwtx.core.runtime.jobs.ISchedulingRule; +import dwtx.core.runtime.jobs.Job; +import dwtx.core.internal.jobs.JobManager; +import dwtx.core.internal.jobs.InternalJob; +import dwtx.core.internal.jobs.LockManager; + +/** + * Captures the implicit job state for a given thread. + */ +class ThreadJob : Job { + /** + * The notifier is a shared object used to wake up waiting thread jobs + * when another job completes that is releasing a scheduling rule. + */ + + static const Mutex mutex; + static const Condition condition; + static this(){ + mutex = new Mutex(); + condition = new Condition(mutex); + } + + private const JobManager manager; + /** + * Set to true if this thread job is running in a thread that did + * not own a rule already. This means it needs to acquire the + * rule during beginRule, and must release the rule during endRule. + */ + /+protected+/ bool acquireRule = false; + + /** + * Indicates that this thread job did report to the progress manager + * that it will be blocked, and therefore when it begins it must + * be reported to the job manager when it is no longer blocked. + */ + bool isBlocked = false; + + /** + * True if this ThreadJob has begun execution + */ + /+protected+/ bool isRunning_ = false; + + /** + * Used for diagnosing mismatched begin/end pairs. This field + * is only used when in debug mode, to capture the stack trace + * of the last call to beginRule. + */ + private RuntimeException lastPush = null; + /** + * The actual job that is running in the thread that this + * ThreadJob represents. This will be null if this thread + * job is capturing a rule acquired outside of a job. + */ + protected package Job realJob; + /** + * The stack of rules that have been begun in this thread, but not yet ended. + */ + private ISchedulingRule[] ruleStack; + /** + * Rule stack pointer. + */ + private int top; + + this(JobManager manager, ISchedulingRule rule) { + super("Implicit Job"); //$NON-NLS-1$ + this.manager = manager; + setSystem(true); + setPriority(Job.INTERACTIVE); + ruleStack = new ISchedulingRule[2]; + top = -1; + setRule(rule); + } + + /** + * An endRule was called that did not match the last beginRule in + * the stack. Report and log a detailed informational message. + * @param rule The rule that was popped + */ + private void illegalPop(ISchedulingRule rule) { + StringBuffer buf = new StringBuffer("Attempted to endRule: "); //$NON-NLS-1$ + buf.append(Format("{}",rule)); + if (top >= 0 && top < ruleStack.length) { + buf.append(", does not match most recent begin: "); //$NON-NLS-1$ + buf.append(Format("{}",ruleStack[top])); + } else { + if (top < 0) + buf.append(", but there was no matching beginRule"); //$NON-NLS-1$ + else + buf.append( Format(", but the rule stack was out of bounds: {}", top)); //$NON-NLS-1$ + } + buf.append(". See log for trace information if rule tracing is enabled."); //$NON-NLS-1$ + String msg = buf.toString(); + if (JobManager.DEBUG || JobManager.DEBUG_BEGIN_END) { + Stdout.formatln("{}",msg); + Exception t = lastPush is null ? new IllegalArgumentException("") : lastPush; + IStatus error = new Status(IStatus.ERROR, JobManager.PI_JOBS, 1, msg, t); + RuntimeLog.log(error); + } + Assert.isLegal(false, msg); + } + + /** + * Client has attempted to begin a rule that is not contained within + * the outer rule. + */ + private void illegalPush(ISchedulingRule pushRule, ISchedulingRule baseRule) { + StringBuffer buf = new StringBuffer("Attempted to beginRule: "); //$NON-NLS-1$ + buf.append(Format("{}",pushRule)); + buf.append(", does not match outer scope rule: "); //$NON-NLS-1$ + buf.append(Format("{}",baseRule)); + String msg = buf.toString(); + if (JobManager.DEBUG) { + Stdout.formatln("{}",msg); + IStatus error = new Status(IStatus.ERROR, JobManager.PI_JOBS, 1, msg, new IllegalArgumentException("")); + RuntimeLog.log(error); + } + Assert.isLegal(false, msg); + + } + + /** + * Returns true if the monitor is canceled, and false otherwise. + * Protects the caller from exception in the monitor implementation. + */ + private bool isCanceled(IProgressMonitor monitor) { + try { + return monitor.isCanceled(); + } catch (RuntimeException e) { + //logged message should not be translated + IStatus status = new Status(IStatus.ERROR, JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, "ThreadJob.isCanceled", e); //$NON-NLS-1$ + RuntimeLog.log(status); + } + return false; + } + + /** + * Returns true if this thread job was scheduled and actually started running. + */ + bool isRunning() { + synchronized(mutex){ + return isRunning_; + } + } + + /** + * Schedule the job and block the calling thread until the job starts running. + * Returns the ThreadJob instance that was started. + */ + ThreadJob joinRun(IProgressMonitor monitor) { + if (isCanceled(monitor)) + throw new OperationCanceledException(); + //check if there is a blocking thread before waiting + InternalJob blockingJob = manager.findBlockingJob_package(this); + Thread blocker = blockingJob is null ? null : blockingJob.getThread_package(); + ThreadJob result = this; + try { + //just return if lock listener decided to grant immediate access + if (manager.getLockManager().aboutToWait(blocker)) + return this; + try { + waitStart(monitor, blockingJob); + final Thread getThis = Thread.getThis(); + while (true) { + if (isCanceled(monitor)) + throw new OperationCanceledException(); + //try to run the job + if (manager.runNow_package(this)) + return this; + //update blocking job + blockingJob = manager.findBlockingJob_package(this); + //the rule could have been transferred to this thread while we were waiting + blocker = blockingJob is null ? null : blockingJob.getThread_package(); + if (blocker is getThis && cast(ThreadJob)blockingJob ) { + //now we are just the nested acquire case + result = cast(ThreadJob) blockingJob; + result.push(getRule()); + result.isBlocked = this.isBlocked; + return result; + } + //just return if lock listener decided to grant immediate access + if (manager.getLockManager().aboutToWait(blocker)) + return this; + //must lock instance before calling wait + synchronized (mutex) { + try { + condition.wait(0.250); + } catch (InterruptedException e) { + //ignore + } + } + } + } finally { + if (this is result) + waitEnd(monitor); + } + } finally { + manager.getLockManager().aboutToRelease(); + } + } + + /** + * Pops a rule. Returns true if it was the last rule for this thread + * job, and false otherwise. + */ + bool pop(ISchedulingRule rule) { + if (top < 0 || ruleStack[top] !is rule) + illegalPop(rule); + ruleStack[top--] = null; + return top < 0; + } + + /** + * Adds a new scheduling rule to the stack of rules for this thread. Throws + * a runtime exception if the new rule is not compatible with the base + * scheduling rule for this thread. + */ + void push(ISchedulingRule rule) { + ISchedulingRule baseRule = getRule(); + if (++top >= ruleStack.length) { + ISchedulingRule[] newStack = new ISchedulingRule[ruleStack.length * 2]; + SimpleType!(ISchedulingRule).arraycopy(ruleStack, 0, newStack, 0, ruleStack.length); + ruleStack = newStack; + } + ruleStack[top] = rule; + if (JobManager.DEBUG_BEGIN_END) + lastPush = new RuntimeException()/+).fillInStackTrace()+/; + //check for containment last because we don't want to fail again on endRule + if (baseRule !is null && rule !is null && !baseRule.contains(rule)) + illegalPush(rule, baseRule); + } + + /** + * Reset all of this job's fields so it can be reused. Returns false if + * reuse is not possible + */ + bool recycle() { + //don't recycle if still running for any reason + if (getState() !is Job.NONE) + return false; + //clear and reset all fields + acquireRule = isRunning_ = isBlocked = false; + realJob = null; + setRule(null); + setThread(null); + if (ruleStack.length !is 2) + ruleStack = new ISchedulingRule[2]; + else + ruleStack[0] = ruleStack[1] = null; + top = -1; + return true; + } + + /** (non-Javadoc) + * @see dwtx.core.runtime.jobs.Job#run(dwtx.core.runtime.IProgressMonitor) + */ + public IStatus run(IProgressMonitor monitor) { + synchronized (this) { + isRunning_ = true; + } + return ASYNC_FINISH; + } + + /** + * Records the job that is actually running in this thread, if any + * @param realJob The running job + */ + void setRealJob(Job realJob) { + this.realJob = realJob; + } + + /** + * Returns true if this job should cause a self-canceling job + * to cancel itself, and false otherwise. + */ + bool shouldInterrupt() { + return realJob is null ? true : !realJob.isSystem(); + } + + /* (non-javadoc) + * For debugging purposes only + */ + public String toString() { + StringBuffer buf = new StringBuffer("ThreadJob"); //$NON-NLS-1$ + buf.append('(').append(Format("{}",realJob)).append(',').append('['); + for (int i = 0; i <= top && i < ruleStack.length; i++) + buf.append(Format("{}",ruleStack[i])).append(','); + buf.append(']').append(')'); + return buf.toString(); + } + + /** + * Reports that this thread was blocked, but is no longer blocked and is able + * to proceed. + * @param monitor The monitor to report unblocking to. + */ + private void waitEnd(IProgressMonitor monitor) { + final LockManager lockManager = manager.getLockManager(); + final Thread getThis = Thread.getThis(); + if (isRunning()) { + lockManager.addLockThread(getThis, getRule()); + //need to re-acquire any locks that were suspended while this thread was blocked on the rule + lockManager.resumeSuspendedLocks(getThis); + } else { + //tell lock manager that this thread gave up waiting + lockManager.removeLockWaitThread(getThis, getRule()); + } + } + + /** + * Indicates the start of a wait on a scheduling rule. Report the + * blockage to the progress manager and update the lock manager. + * @param monitor The monitor to report blocking to + * @param blockingJob The job that is blocking this thread, or <code>null</code> + */ + private void waitStart(IProgressMonitor monitor, InternalJob blockingJob) { + manager.getLockManager().addLockWaitThread(Thread.getThis(), getRule()); + isBlocked = true; + manager.reportBlocked(monitor, blockingJob); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/internal/jobs/Worker.d Tue Aug 12 02:34:21 2008 +0200 @@ -0,0 +1,104 @@ +/******************************************************************************* + * Copyright (c) 2003, 2006 IBM Corporation 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: + * IBM Corporation - initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.core.internal.jobs.Worker; + +import tango.core.Thread; +import tango.text.convert.Format; +import dwt.dwthelper.utils; + +import dwtx.core.internal.runtime.RuntimeLog; +import dwtx.core.runtime.IStatus; +import dwtx.core.runtime.OperationCanceledException; +import dwtx.core.runtime.Status; +import dwtx.core.runtime.jobs.Job; +import dwtx.osgi.util.NLS; + +import dwtx.core.internal.jobs.InternalJob; +import dwtx.core.internal.jobs.WorkerPool; +import dwtx.core.internal.jobs.JobMessages; +import dwtx.core.internal.jobs.JobManager; + +/** + * A worker thread processes jobs supplied to it by the worker pool. When + * the worker pool gives it a null job, the worker dies. + */ +public class Worker : Thread { + //worker number used for debugging purposes only + private static int nextWorkerNumber = 0; + private /+volatile+/ InternalJob currentJob_; + private final WorkerPool pool; + + public this(WorkerPool pool) { + super(&run); + this.name = Format("Worker-{}", nextWorkerNumber++); //$NON-NLS-1$ + this.pool = pool; + //set the context loader to avoid leaking the current context loader + //for the thread that spawns this worker (bug 98376) +// DWT +// setContextClassLoader(pool.defaultContextLoader); + } + + /** + * Returns the currently running job, or null if none. + */ + public Job currentJob() { + return cast(Job) currentJob_; + } + + private IStatus handleException(InternalJob job, Exception t) { + String message = NLS.bind(JobMessages.jobs_internalError, job.getName_package()); + return new Status(IStatus.ERROR, JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, message, t); + } + + public void run() { + this.priority((PRIORITY_MAX-PRIORITY_MIN)/2); // DWT normal priority + try { + while ((currentJob_ = pool.startJob_package(this)) !is null) { + currentJob_.setThread_package(this); + IStatus result = Status.OK_STATUS; + try { + result = currentJob_.run_package(currentJob_.getProgressMonitor()); + } catch (OperationCanceledException e) { + result = Status.CANCEL_STATUS; + } catch (Exception e) { + result = handleException(currentJob_, e); +// } catch (ThreadDeath e) { +// //must not consume thread death +// result = handleException(currentJob_, e); +// throw e; +// } catch (Error e) { +// result = handleException(currentJob_, e); + } finally { + //clear interrupted state for this thread + implMissing( __FILE__, __LINE__ ); +// DWT +// Thread.interrupted(); + + + //result must not be null + if (result is null) + result = handleException(currentJob_, new NullPointerException()); + pool.endJob_package(currentJob_, result); + if ((result.getSeverity() & (IStatus.ERROR | IStatus.WARNING)) !is 0) + RuntimeLog.log(result); + currentJob_ = null; + } + } + } catch (Exception t) { + ExceptionPrintStackTrace(t); + } finally { + currentJob_ = null; + pool.endWorker_package(this); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/internal/jobs/WorkerPool.d Tue Aug 12 02:34:21 2008 +0200 @@ -0,0 +1,296 @@ +/******************************************************************************* + * Copyright (c) 2003, 2007 IBM Corporation 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: + * IBM - Initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.core.internal.jobs.WorkerPool; + +import tango.core.Thread; +import tango.core.sync.Mutex; +import tango.core.sync.Condition; +import tango.text.convert.Format; +import dwt.dwthelper.utils; + +import dwtx.core.runtime.Assert; +import dwtx.core.runtime.IStatus; +import dwtx.core.runtime.jobs.Job; +import dwtx.core.internal.jobs.JobManager; +import dwtx.core.internal.jobs.Worker; + +import dwtx.core.internal.jobs.InternalJob; +import dwtx.core.internal.jobs.ThreadJob; + +/** + * Maintains a pool of worker threads. Threads are constructed lazily as + * required, and are eventually discarded if not in use for awhile. This class + * maintains the thread creation/destruction policies for the job manager. + * + * Implementation note: all the data structures of this class are protected + * by the instance's object monitor. To avoid deadlock with third party code, + * this lock is never held when calling methods outside this class that may in + * turn use locks. + */ +class WorkerPool { + + protected Mutex mutex; + protected Condition condition; + + + /** + * Threads not used by their best before timestamp are destroyed. + */ + private static const int BEST_BEFORE = 60000; + /** + * There will always be at least MIN_THREADS workers in the pool. + */ + private static const int MIN_THREADS = 1; + /** + * Use the busy thread count to avoid starting new threads when a living + * thread is just doing house cleaning (notifying listeners, etc). + */ + private int busyThreads = 0; + + /** + * The default context class loader to use when creating worker threads. + */ +// protected const ClassLoader defaultContextLoader; + + /** + * Records whether new worker threads should be daemon threads. + */ + private bool isDaemon = false; + + private JobManager manager; + /** + * The number of workers in the threads array + */ + private int numThreads = 0; + /** + * The number of threads that are currently sleeping + */ + private int sleepingThreads = 0; + /** + * The living set of workers in this pool. + */ + private Worker[] threads; + + protected package this(JobManager manager) { + threads = new Worker[10]; + this.manager = manager; + mutex = new Mutex; + condition = new Condition(mutex); +// this.defaultContextLoader = Thread.currentThread().getContextClassLoader(); + } + + /** + * Adds a worker to the list of workers. + */ + private void add(Worker worker) { + synchronized(mutex){ + int size = threads.length; + if (numThreads + 1 > size) { + Worker[] newThreads = new Worker[2 * size]; + System.arraycopy(threads, 0, newThreads, 0, size); + threads = newThreads; + } + threads[numThreads++] = worker; + } + } + + private void decrementBusyThreads() { + synchronized(mutex){ + //impossible to have less than zero busy threads + if (--busyThreads < 0) { + if (JobManager.DEBUG) + Assert.isTrue(false, Integer.toString(busyThreads)); + busyThreads = 0; + } + } + } + + /** + * Signals the end of a job. Note that this method can be called under + * OutOfMemoryError conditions and thus must be paranoid about allocating objects. + */ + protected void endJob(InternalJob job, IStatus result) { + decrementBusyThreads(); + //need to end rule in graph before ending job so that 2 threads + //do not become the owners of the same rule in the graph + if ((job.getRule_package() !is null) && !(cast(ThreadJob)job )) { + //remove any locks this thread may be owning on that rule + manager.getLockManager().removeLockCompletely(Thread.getThis(), job.getRule_package()); + } + manager.endJob_package(job, result, true); + //ensure this thread no longer owns any scheduling rules + manager.implicitJobs.endJob(job); + } + package void endJob_package(InternalJob job, IStatus result) { + endJob(job, result); + } + + /** + * Signals the death of a worker thread. Note that this method can be called under + * OutOfMemoryError conditions and thus must be paranoid about allocating objects. + */ + protected void endWorker(Worker worker) { + synchronized(mutex){ + if (remove(worker) && JobManager.DEBUG) + JobManager.debug_(Format("worker removed from pool: {}", worker)); //$NON-NLS-1$ + } + } + package void endWorker_package(Worker worker) { + endWorker(worker); + } + + private void incrementBusyThreads() { + synchronized(mutex){ + //impossible to have more busy threads than there are threads + if (++busyThreads > numThreads) { + if (JobManager.DEBUG) + Assert.isTrue(false, Format( "{},{}", busyThreads, numThreads)); + busyThreads = numThreads; + } + } + } + + /** + * Notification that a job has been added to the queue. Wake a worker, + * creating a new worker if necessary. The provided job may be null. + */ + protected package void jobQueued() { + synchronized(mutex){ + //if there is a sleeping thread, wake it up + if (sleepingThreads > 0) { + condition.notify(); + return; + } + //create a thread if all threads are busy + if (busyThreads >= numThreads) { + Worker worker = new Worker(this); + worker.isDaemon(isDaemon); + add(worker); + if (JobManager.DEBUG) + JobManager.debug_(Format("worker added to pool: {}", worker)); //$NON-NLS-1$ + worker.start(); + return; + } + } + } + + /** + * Remove a worker thread from our list. + * @return true if a worker was removed, and false otherwise. + */ + private bool remove(Worker worker) { + synchronized(mutex){ + for (int i = 0; i < threads.length; i++) { + if (threads[i] is worker) { + System.arraycopy(threads, i + 1, threads, i, numThreads - i - 1); + threads[--numThreads] = null; + return true; + } + } + return false; + } + } + + /** + * Sets whether threads created in the worker pool should be daemon threads. + */ + void setDaemon(bool value) { + this.isDaemon = value; + } + + protected void shutdown() { + synchronized(mutex){ + condition.notifyAll(); + } + } + package void shutdown_package() { + shutdown(); + } + + /** + * Sleep for the given duration or until woken. + */ + private void sleep(long duration) { + synchronized(mutex){ + sleepingThreads++; + busyThreads--; + if (JobManager.DEBUG) + JobManager.debug_(Format("worker sleeping for: {}ms", duration)); //$NON-NLS-1$ //$NON-NLS-2$ + try { + condition.wait(duration/1000.0f); + } catch (InterruptedException e) { + if (JobManager.DEBUG) + JobManager.debug_("worker interrupted while waiting... :-|"); //$NON-NLS-1$ + } finally { + sleepingThreads--; + busyThreads++; + } + } + } + + /** + * Returns a new job to run. Returns null if the thread should die. + */ + protected InternalJob startJob(Worker worker) { + //if we're above capacity, kill the thread + synchronized (mutex) { + if (!manager.isActive_package()) { + //must remove the worker immediately to prevent all threads from expiring + endWorker(worker); + return null; + } + //set the thread to be busy now in case of reentrant scheduling + incrementBusyThreads(); + } + Job job = null; + try { + job = manager.startJob_package(); + //spin until a job is found or until we have been idle for too long + long idleStart = System.currentTimeMillis(); + while (manager.isActive_package() && job is null) { + long hint = manager.sleepHint_package(); + if (hint > 0) + sleep(Math.min(hint, BEST_BEFORE)); + job = manager.startJob_package(); + //if we were already idle, and there are still no new jobs, then + // the thread can expire + synchronized (mutex) { + if (job is null && (System.currentTimeMillis() - idleStart > BEST_BEFORE) && (numThreads - busyThreads) > MIN_THREADS) { + //must remove the worker immediately to prevent all threads from expiring + endWorker(worker); + return null; + } + } + } + if (job !is null) { + //if this job has a rule, then we are essentially acquiring a lock + if ((job.getRule() !is null) && !(cast(ThreadJob)job )) { + //don't need to re-aquire locks because it was not recorded in the graph + //that this thread waited to get this rule + manager.getLockManager().addLockThread(Thread.getThis(), job.getRule()); + } + //see if we need to wake another worker + if (manager.sleepHint_package() <= 0) + jobQueued(); + } + } finally { + //decrement busy thread count if we're not running a job + if (job is null) + decrementBusyThreads(); + } + return job; + } + package InternalJob startJob_package(Worker worker) { + return startJob(worker); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/internal/runtime/RuntimeLog.d Tue Aug 12 02:34:21 2008 +0200 @@ -0,0 +1,126 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 IBM Corporation 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: + * IBM Corporation - initial API and implementation + * Julian Chen - fix for bug #92572, jclRM + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.core.internal.runtime.RuntimeLog; + +import dwt.dwthelper.utils; +import dwtx.dwtxhelper.Collection; + +import dwtx.core.runtime.ILogListener; +import dwtx.core.runtime.IStatus; +import dwtx.core.runtime.OperationCanceledException; + +/** + * NOT API!!! This log infrastructure was split from the InternalPlatform. + * + * @since dwtx.equinox.common 3.2 + */ +// XXX this must be removed and replaced with something more reasonable +public final class RuntimeLog { + + private static ArrayList logListeners = new ArrayList(5); + + /** + * Keep the messages until the first log listener is registered. + * Once first log listeners is registred, it is going to receive + * all status messages accumulated during the period when no log + * listener was available. + */ + private static ArrayList queuedMessages = new ArrayList(5); + + /** + * See dwtx.core.runtime.Platform#addLogListener(ILogListener) + */ + public static void addLogListener(ILogListener listener) { + synchronized (logListeners) { + bool firstListener = (logListeners.size() is 0); + // replace if already exists (Set behaviour but we use an array + // since we want to retain order) + logListeners.remove(listener); + logListeners.add(listener); + if (firstListener) { + for (Iterator i = queuedMessages.iterator(); i.hasNext();) { + try { + IStatus recordedMessage = cast(IStatus) i.next(); + listener.logging(recordedMessage, IRuntimeConstants.PI_RUNTIME); + } catch (Exception e) { + handleException(e); + } catch (LinkageError e) { + handleException(e); + } + } + queuedMessages.clear(); + } + } + } + + /** + * See dwtx.core.runtime.Platform#removeLogListener(ILogListener) + */ + public static void removeLogListener(ILogListener listener) { + synchronized (logListeners) { + logListeners.remove(listener); + } + } + + /** + * Checks if the given listener is present + */ + public static bool contains(ILogListener listener) { + synchronized (logListeners) { + return logListeners.contains(listener); + } + } + + /** + * Notifies all listeners of the platform log. + */ + public static void log(IStatus status) { + // create array to avoid concurrent access + ILogListener[] listeners; + synchronized (logListeners) { + listeners = arraycast!(ILogListener)( logListeners.toArray()); + if (listeners.length is 0) { + queuedMessages.add(status); + return; + } + } + for (int i = 0; i < listeners.length; i++) { + try { + listeners[i].logging(status, IRuntimeConstants.PI_RUNTIME); + } catch (Exception e) { + handleException(e); + } catch (LinkageError e) { + handleException(e); + } + } + } + + private static void handleException(Exception e) { + if (!(cast(OperationCanceledException)e )) { + // Got a error while logging. Don't try to log again, just put it into stderr + ExceptionPrintStackTrace(e); + } + } + + /** + * Helps determine if any listeners are registered with the logging mechanism. + * @return true if no listeners are registered + */ + public static bool isEmpty() { + synchronized (logListeners) { + return (logListeners.size() is 0); + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/runtime/PlatformObject.d Tue Aug 12 02:34:21 2008 +0200 @@ -0,0 +1,75 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 IBM Corporation 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: + * IBM Corporation - initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.core.runtime.PlatformObject; + +import dwt.dwthelper.utils; + +import dwtx.core.runtime.IAdaptable; +// import dwtx.core.internal.runtime.AdapterManager; + +/** + * An abstract superclass implementing the <code>IAdaptable</code> + * interface. <code>getAdapter</code> invocations are directed + * to the platform's adapter manager. + * <p> + * Note: In situations where it would be awkward to subclass this + * class, the same affect can be achieved simply by implementing + * the {@link IAdaptable} interface and explicitly forwarding + * the <code>getAdapter</code> request to an implementation + * of the {@link IAdapterManager} service. The method would look like: + * <pre> + * public Object getAdapter(Class adapter) { + * IAdapterManager manager = ...;//lookup the IAdapterManager service + * return manager.getAdapter(this, adapter); + * } + * </pre> + * </p><p> + * This class can be used without OSGi running. + * </p><p> + * Clients may subclass. + * </p> + * + * @see IAdapterManager + * @see IAdaptable + */ +public abstract class PlatformObject : IAdaptable { + /** + * Constructs a new platform object. + */ + public this() { + super(); + } + + /** + * Returns an object which is an instance of the given class + * associated with this object. Returns <code>null</code> if + * no such object can be found. + * <p> + * This implementation of the method declared by <code>IAdaptable</code> + * passes the request along to the platform's adapter manager; roughly + * <code>Platform.getAdapterManager().getAdapter(this, adapter)</code>. + * Subclasses may override this method (however, if they do so, they + * should invoke the method on their superclass to ensure that the + * Platform's adapter manager is consulted). + * </p> + * + * @param adapter the class to adapt to + * @return the adapted object or <code>null</code> + * @see IAdaptable#getAdapter(Class) + */ + public Object getAdapter(ClassInfo adapter) { + implMissing( __FILE__, __LINE__ ); + return null; +// return AdapterManager.getDefault().getAdapter(this, adapter); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/runtime/jobs/IJobChangeEvent.d Tue Aug 12 02:34:21 2008 +0200 @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2003, 2008 IBM Corporation 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: + * IBM - Initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.core.runtime.jobs.IJobChangeEvent; + +import dwt.dwthelper.utils; + +import dwtx.core.runtime.IStatus; +import dwtx.core.runtime.jobs.Job; + +/** + * An event describing a change to the state of a job. + * + * @see IJobChangeListener + * @since 3.0 + * @noimplement This interface is not intended to be implemented by clients. + */ +public interface IJobChangeEvent { + /** + * The amount of time in milliseconds to wait after scheduling the job before it + * should be run, or <code>-1</code> if not applicable for this type of event. + * This value is only applicable for the <code>scheduled</code> event. + * + * @return the delay time for this event + */ + public long getDelay(); + + /** + * The job on which this event occurred. + * + * @return the job for this event + */ + public Job getJob(); + + /** + * The result returned by the job's run method, or <code>null</code> if + * not applicable. This value is only applicable for the <code>done</code> event. + * + * @return the status for this event + */ + public IStatus getResult(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/runtime/jobs/IJobChangeListener.d Tue Aug 12 02:34:21 2008 +0200 @@ -0,0 +1,92 @@ +/******************************************************************************* + * Copyright (c) 2003, 2006 IBM Corporation 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: + * IBM Corporation - initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.core.runtime.jobs.IJobChangeListener; + +import dwt.dwthelper.utils; +import dwtx.core.runtime.jobs.IJobChangeEvent; + +/** + * Callback interface for clients interested in being notified when jobs change state. + * <p> + * A single job listener instance can be added either to the job manager, for notification + * of all scheduled jobs, or to any set of individual jobs. A single listener instance should + * not be added to both the job manager, and to individual jobs (such a listener may + * receive duplicate notifications). + * </p><p> + * Clients should not rely on the result of the <code>Job#getState()</code> + * method on jobs for which notification is occurring. Listeners are notified of + * all job state changes, but whether the state change occurs before, during, or + * after listeners are notified is unspecified. + * </p><p> + * Clients may implement this interface. + * </p> + * @see JobChangeAdapter + * @see IJobManager#addJobChangeListener(IJobChangeListener) + * @see IJobManager#removeJobChangeListener(IJobChangeListener) + * @see Job#addJobChangeListener(IJobChangeListener) + * @see Job#getState() + * @see Job#removeJobChangeListener(IJobChangeListener) + * @since 3.0 + */ +public interface IJobChangeListener { + /** + * Notification that a job is about to be run. Listeners are allowed to sleep, cancel, + * or change the priority of the job before it is started (and as a result may prevent + * the run from actually occurring). + * + * @param event the event details + */ + public void aboutToRun(IJobChangeEvent event); + + /** + * Notification that a job was previously sleeping and has now been rescheduled + * to run. + * + * @param event the event details + */ + public void awake(IJobChangeEvent event); + + /** + * Notification that a job has completed execution, either due to cancelation, successful + * completion, or failure. The event status object indicates how the job finished, + * and the reason for failure, if applicable. + * + * @param event the event details + */ + public void done(IJobChangeEvent event); + + /** + * Notification that a job has started running. + * + * @param event the event details + */ + public void running(IJobChangeEvent event); + + /** + * Notification that a job is being added to the queue of scheduled jobs. + * The event details includes the scheduling delay before the job should start + * running. + * + * @param event the event details, including the job instance and the scheduling + * delay + */ + public void scheduled(IJobChangeEvent event); + + /** + * Notification that a job was waiting to run and has now been put in the + * sleeping state. + * + * @param event the event details + */ + public void sleeping(IJobChangeEvent event); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/runtime/jobs/IJobManager.d Tue Aug 12 02:34:21 2008 +0200 @@ -0,0 +1,420 @@ +/******************************************************************************* + * Copyright (c) 2003, 2008 IBM Corporation 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: + * IBM Corporation - initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.core.runtime.jobs.IJobManager; + +import dwt.dwthelper.utils; + +import dwtx.core.runtime.IProgressMonitor; +import dwtx.core.runtime.OperationCanceledException; +import dwtx.core.runtime.jobs.IJobChangeListener; +import dwtx.core.runtime.jobs.ISchedulingRule; +import dwtx.core.runtime.jobs.Job; +import dwtx.core.runtime.jobs.ILock; +import dwtx.core.runtime.jobs.LockListener; + +import dwtx.core.runtime.jobs.ProgressProvider; +import tango.core.Thread; + +/** + * The job manager provides facilities for scheduling, querying, and maintaining jobs + * and locks. In particular, the job manager provides the following services: + * <ul> + * <li>Maintains a queue of jobs that are waiting to be run. Items can be added to + * the queue using the <code>schedule</code> method.</li> + * <li>Allows manipulation of groups of jobs called job families. Job families can + * be canceled, put to sleep, or woken up atomically. There is also a mechanism + * for querying the set of known jobs in a given family.</li> + * <li>Allows listeners to find out about progress on running jobs, and to find out + * when jobs have changed states.</li> + * <li>Provides a factory for creating lock objects. Lock objects are smart monitors + * that have strategies for avoiding deadlock.</li> + * <li>Provide feedback to a client that is waiting for a given job or family of jobs + * to complete.</li> + * </ul> + * + * @see Job + * @see ILock + * @since 3.0 + * @noimplement This interface is not intended to be implemented by clients. + */ +public interface IJobManager { + /** + * A system property key indicating whether the job manager should create + * job threads as daemon threads. Set to <code>true</code> to force all worker + * threads to be created as daemon threads. Set to <code>false</code> to force + * all worker threads to be created as non-daemon threads. + * @since 3.3 + */ + public static final String PROP_USE_DAEMON_THREADS = "eclipse.jobs.daemon"; //$NON-NLS-1$ + + /** + * Registers a job listener with the job manager. + * Has no effect if an identical listener is already registered. + * + * @param listener the listener to be added + * @see #removeJobChangeListener(IJobChangeListener) + * @see IJobChangeListener + */ + public void addJobChangeListener(IJobChangeListener listener); + + /** + * Begins applying this rule in the calling thread. If the rule conflicts with another + * rule currently running in another thread, this method blocks until there are + * no conflicting rules. Calls to <tt>beginRule</tt> must eventually be followed + * by a matching call to <tt>endRule</tt> in the same thread and with the identical + * rule instance. + * <p> + * Rules can be nested only if the rule for the inner <tt>beginRule</tt> + * is contained within the rule for the outer <tt>beginRule</tt>. Rule containment + * is tested with the API method <tt>ISchedulingRule.contains</tt>. Also, begin/end + * pairs must be strictly nested. Only the rule that has most recently begun + * can be ended at any given time. + * <p> + * A rule of <code>null</code> can be used, but will be ignored for scheduling + * purposes. The outermost non-null rule in the thread will be used for scheduling. A + * <code>null</code> rule that is begun must still be ended. + * <p> + * If this method is called from within a job that has a scheduling rule, the + * given rule must also be contained within the rule for the running job. + * <p> + * Note that <tt>endRule</tt> must be called even if <tt>beginRule</tt> fails. + * The recommended usage is: + * <pre> + * final ISchedulingRule rule = ...; + * try { + * manager.beginRule(rule, monitor); + * } finally { + * manager.endRule(rule); + * } + * </pre> + * + * @param rule the rule to begin applying in this thread, or <code>null</code> + * @param monitor a progress monitor, or <code>null</code> if progress + * reporting and cancellation are not desired + * @throws IllegalArgumentException if the rule is not strictly nested within + * all other rules currently active for this thread + * @throws OperationCanceledException if the supplied monitor reports cancelation + * before the rule becomes available + * @see ISchedulingRule#contains(ISchedulingRule) + */ + public void beginRule(ISchedulingRule rule, IProgressMonitor monitor); + + /** + * Cancels all jobs in the given job family. Jobs in the family that are currently waiting + * will be removed from the queue. Sleeping jobs will be discarded without having + * a chance to wake up. Currently executing jobs will be asked to cancel but there + * is no guarantee that they will do so. + * + * @param family the job family to cancel, or <code>null</code> to cancel all jobs + * @see Job#belongsTo(Object) + */ + public void cancel(Object family); + + /** + * Returns a progress monitor that can be used to provide + * aggregated progress feedback on a set of running jobs. A user + * interface will typically group all jobs in a progress group together, + * providing progress feedback for individual jobs as well as aggregated + * progress for the entire group. Jobs in the group may be run sequentially, + * in parallel, or some combination of the two. + * <p> + * Recommended usage (this snippet runs two jobs in sequence in a + * single progress group): + * <pre> + * Job parseJob, compileJob; + * IProgressMonitor pm = Platform.getJobManager().createProgressGroup(); + * try { + * pm.beginTask("Building", 10); + * parseJob.setProgressGroup(pm, 5); + * parseJob.schedule(); + * compileJob.setProgressGroup(pm, 5); + * compileJob.schedule(); + * parseJob.join(); + * compileJob.join(); + * } finally { + * pm.done(); + * } + * </pre> + * + * @see Job#setProgressGroup(IProgressMonitor, int) + * @see IProgressMonitor + * @return a progress monitor + */ + public IProgressMonitor createProgressGroup(); + + /** + * Returns the job that is currently running in this thread, or <code>null</code> if there + * is no currently running job. + * + * @return the job or <code>null</code> + */ + public Job currentJob(); + + /** + * Ends the application of a rule to the calling thread. Calls to <tt>endRule</tt> + * must be preceded by a matching call to <tt>beginRule</tt> in the same thread + * with an identical rule instance. + * <p> + * Rules can be nested only if the rule for the inner <tt>beginRule</tt> + * is contained within the rule for the outer <tt>beginRule</tt>. Also, begin/end + * pairs must be strictly nested. Only the rule that has most recently begun + * can be ended at any given time. + * + * @param rule the rule to end applying in this thread + * @throws IllegalArgumentException if this method is called on a rule for which + * there is no matching begin, or that does not match the most recent begin. + * @see ISchedulingRule#contains(ISchedulingRule) + */ + public void endRule(ISchedulingRule rule); + + /** + * Returns all waiting, executing and sleeping jobs belonging + * to the given family. If no jobs are found, an empty array is returned. + * + * @param family the job family to find, or <code>null</code> to find all jobs + * @return the job array + * @see Job#belongsTo(Object) + */ + public Job[] find(Object family); + + /** + * Returns whether the job manager is currently idle. The job manager is + * idle if no jobs are currently running or waiting to run. + * + * @return <code>true</code> if the job manager is idle, and + * <code>false</code> otherwise + * @since 3.1 + */ + public bool isIdle(); + + /** + * Returns whether the job manager is currently suspended. + * + * @return <code>true</code> if the job manager is suspended, and + * <code>false</code> otherwise + * @since 3.4 + * @see #suspend() + * @see #resume() + */ + public bool isSuspended(); + + /** + * Waits until all jobs of the given family are finished. This method will block the + * calling thread until all such jobs have finished executing, or until this thread is + * interrupted. If there are no jobs in + * the family that are currently waiting, running, or sleeping, this method returns + * immediately. Feedback on how the join is progressing is provided to a progress + * monitor. + * <p> + * If this method is called while the job manager is suspended, only jobs + * that are currently running will be joined; Once there are no jobs + * in the family in the {@link Job#RUNNING} state, this method returns. + * </p> + * <p> + * Note that there is a deadlock risk when using join. If the calling thread owns + * a lock or object monitor that the joined thread is waiting for, deadlock + * will occur. This method can also result in starvation of the current thread if + * another thread continues to add jobs of the given family, or if a + * job in the given family reschedules itself in an infinite loop. + * </p> + * + * @param family the job family to join, or <code>null</code> to join all jobs. + * @param monitor Progress monitor for reporting progress on how the + * wait is progressing, or <code>null</code> if no progress monitoring is required. + * @exception InterruptedException if this thread is interrupted while waiting + * @exception OperationCanceledException if the progress monitor is canceled while waiting + * @see Job#belongsTo(Object) + * @see #suspend() + */ + public void join(Object family, IProgressMonitor monitor); + + /** + * Creates a new lock object. All lock objects supplied by the job manager + * know about each other and will always avoid circular deadlock amongst + * themselves. + * + * @return the new lock object + */ + public ILock newLock(); + + /** + * Removes a job listener from the job manager. + * Has no effect if an identical listener is not already registered. + * + * @param listener the listener to be removed + * @see #addJobChangeListener(IJobChangeListener) + * @see IJobChangeListener + */ + public void removeJobChangeListener(IJobChangeListener listener); + + /** + * Resumes execution of jobs after a previous <code>suspend</code>. All + * jobs that were sleeping or waiting prior to the suspension, or that were + * scheduled while the job manager was suspended, will now be eligible + * for execution. + * <p> + * Calling this method on a rule that is not suspended has no effect. If another + * thread also owns the rule at the time this method is called, then the rule will + * not be resumed until all threads have released the rule. + * + * @deprecated This method is not safe and should not be used. + * Suspending a scheduling rule violates the thread safety + * of clients that use scheduling rules as a mutual exclusion mechanism, + * and can result in concurrency problems in all clients that use the suspended rule. + * @see #suspend(ISchedulingRule, IProgressMonitor) + */ + public void resume(ISchedulingRule rule); + + /** + * Resumes execution of jobs after a previous <code>suspend</code>. All + * jobs that were sleeping or waiting prior to the suspension, or that were + * scheduled while the job manager was suspended, will now be eligible + * for execution. + * <p> + * Calling <code>resume</code> when the job manager is not suspended + * has no effect. + * + * @see #suspend() + * @see #isSuspended() + */ + public void resume(); + + /** + * Provides a hook that is notified whenever a thread is about to wait on a lock, + * or when a thread is about to release a lock. This hook must only be set once. + * <p> + * This method is for internal use by the platform-related plug-ins. + * Clients should not call this method. + * </p> + * @see LockListener + */ + public void setLockListener(LockListener listener); + + /** + * Registers a progress provider with the job manager. If there was a + * provider already registered, it is replaced. + * <p> + * This method is intended for use by the currently executing Eclipse application. + * Plug-ins outside the currently running application should not call this method. + * </p> + * + * @param provider the new provider, or <code>null</code> if no progress + * is needed + */ + public void setProgressProvider(ProgressProvider provider); + + /** + * Suspends execution of all jobs. Jobs that are already running + * when this method is invoked will complete as usual, but all sleeping and + * waiting jobs will not be executed until the job manager is resumed. + * <p> + * The job manager will remain suspended until a subsequent call to + * <code>resume</code>. Further calls to <code>suspend</code> + * when the job manager is already suspended are ignored. + * <p> + * All attempts to join sleeping and waiting jobs while the job manager is + * suspended will return immediately. + * <p> + * Note that this very powerful function should be used with extreme caution. + * Suspending the job manager will prevent all jobs in the system from executing, + * which may have adverse affects on components that are relying on + * execution of jobs. The job manager should never be suspended without intent + * to resume execution soon afterwards. + * + * @see #resume() + * @see #join(Object, IProgressMonitor) + * @see #isSuspended() + */ + public void suspend(); + + /** + * Defers execution of all jobs with scheduling rules that conflict with the + * given rule. The caller will be blocked until all currently executing jobs with + * conflicting rules are completed. Conflicting jobs that are sleeping or waiting at + * the time this method is called will not be executed until the rule is resumed. + * <p> + * While a rule is suspended, all calls to <code>beginRule</code> and + * <code>endRule</code> on a suspended rule will not block the caller. + * The rule remains suspended until a subsequent call to + * <code>resume(ISchedulingRule)</code> with the identical rule instance. + * Further calls to <code>suspend</code> with an identical rule prior to calling + * <code>resume</code> are ignored. + * </p> + * <p> + * This method is long-running; progress and cancelation are provided by + * the given progress monitor. In the case of cancelation, the rule will + * not be suspended. + * </p> + * Note: this very powerful function should be used with extreme caution. + * Suspending rules will prevent jobs in the system from executing, which may + * have adverse effects on components that are relying on execution of jobs. + * The job manager should never be suspended without intent to resume + * execution soon afterwards. Deadlock will result if the thread responsible + * for resuming the rule attempts to join a suspended job. + * + * @deprecated This method is not safe and should not be used. + * Suspending a scheduling rule violates the thread safety + * of clients that use scheduling rules as a mutual exclusion mechanism, + * and can result in concurrency problems in all clients that use the suspended rule. + * @param rule The scheduling rule to suspend. Must not be <code>null</code>. + * @param monitor a progress monitor, or <code>null</code> if progress + * reporting is not desired + * @exception OperationCanceledException if the operation is canceled. + * Cancelation can occur even if no progress monitor is provided. + * @see #resume(ISchedulingRule) + */ + public void suspend(ISchedulingRule rule, IProgressMonitor monitor); + + /** + * Requests that all jobs in the given job family be suspended. Jobs currently + * waiting to be run will be removed from the queue and moved into the + * <code>SLEEPING</code> state. Jobs that have been put to sleep + * will remain in that state until either resumed or canceled. This method has + * no effect on jobs that are not currently waiting to be run. + * <p> + * Sleeping jobs can be resumed using <code>wakeUp</code>. + * + * @param family the job family to sleep, or <code>null</code> to sleep all jobs. + * @see Job#belongsTo(Object) + */ + public void sleep(Object family); + + /** + * Transfers ownership of a scheduling rule to another thread. The identical + * scheduling rule must currently be owned by the calling thread as a result of + * a previous call to <code>beginRule</code>. The destination thread must + * not already own a scheduling rule. + * <p> + * Calling this method is equivalent to atomically calling <code>endRule</code> + * in the calling thread followed by an immediate <code>beginRule</code> in + * the destination thread. The destination thread is responsible for subsequently + * calling <code>endRule</code> when it is finished using the rule. + * <p> + * This method has no effect when the destination thread is the same as the + * calling thread. + * + * @param rule The scheduling rule to transfer + * @param destinationThread The new owner for the transferred rule. + * @since 3.1 + */ + public void transferRule(ISchedulingRule rule, Thread destinationThread); + + /** + * Resumes scheduling of all sleeping jobs in the given family. This method + * has no effect on jobs in the family that are not currently sleeping. + * + * @param family the job family to wake up, or <code>null</code> to wake up all jobs + * @see Job#belongsTo(Object) + */ + public void wakeUp(Object family); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/runtime/jobs/IJobStatus.d Tue Aug 12 02:34:21 2008 +0200 @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 IBM Corporation 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: + * IBM - Initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.core.runtime.jobs.IJobStatus; + +import dwt.dwthelper.utils; + +import dwtx.core.runtime.IStatus; +import dwtx.core.runtime.jobs.Job; + +/** + * Represents status relating to the execution of jobs. + * + * @see dwtx.core.runtime.IStatus + * @noimplement This interface is not intended to be implemented by clients. + */ +public interface IJobStatus : IStatus { + /** + * Returns the job associated with this status. + * + * @return the job associated with this status + */ + public Job getJob(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/runtime/jobs/ILock.d Tue Aug 12 02:34:21 2008 +0200 @@ -0,0 +1,107 @@ +/******************************************************************************* + * Copyright (c) 2003, 2008 IBM Corporation 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: + * IBM - Initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.core.runtime.jobs.ILock; + +import dwt.dwthelper.utils; + +/** + * A lock is used to control access to an exclusive resource. + * <p> + * Locks are reentrant. That is, they can be acquired multiple times by the same thread + * without releasing. Locks are only released when the number of successful acquires + * equals the number of successful releases. + * </p><p> + * Locks are capable of detecting and recovering from programming errors that cause + * circular waiting deadlocks. When a deadlock between two or more <tt>ILock</tt> + * instances is detected, detailed debugging information is printed to the log file. The + * locks will then automatically recover from the deadlock by employing a release + * and wait strategy. One thread will lose control of the locks it owns, thus breaking + * the deadlock and allowing other threads to proceed. Once that thread's locks are + * all available, it will be given exclusive access to all its locks and allowed to proceed. + * A thread can only lose locks while it is waiting on an <tt>acquire()</tt> call. + * + * </p><p> + * Successive acquire attempts by different threads are queued and serviced on + * a first come, first served basis. + * </p><p> + * It is very important that acquired locks eventually get released. Calls to release + * should be done in a finally block to ensure they execute. For example: + * <pre> + * try { + * lock.acquire(); + * // ... do work here ... + * } finally { + * lock.release(); + * } + * </pre> + * Note: although <tt>lock.acquire</tt> should never fail, it is good practice to place + * it inside the try block anyway. Releasing without acquiring is far less catastrophic + * than acquiring without releasing. + * </p> + * + * @see IJobManager#newLock() + * @since 3.0 + * @noimplement This interface is not intended to be implemented by clients. + */ +public interface ILock { + /** + * Attempts to acquire this lock. If the lock is in use and the specified delay is + * greater than zero, the calling thread will block until one of the following happens: + * <ul> + * <li>This lock is available</li> + * <li>The thread is interrupted</li> + * <li>The specified delay has elapsed</li> + * </ul> + * <p> + * While a thread is waiting, locks it already owns may be granted to other threads + * if necessary to break a deadlock. In this situation, the calling thread may be blocked + * for longer than the specified delay. On returning from this call, the calling thread + * will once again have exclusive access to any other locks it owned upon entering + * the acquire method. + * + * @param delay the number of milliseconds to delay + * @return <code>true</code> if the lock was successfully acquired, and + * <code>false</code> otherwise. + * @exception InterruptedException if the thread was interrupted + */ + public bool acquire(long delay); + + /** + * Acquires this lock. If the lock is in use, the calling thread will block until the lock + * becomes available. If the calling thread owns several locks, it will be blocked + * until all threads it requires become available, or until the thread is interrupted. + * While a thread is waiting, its locks may be granted to other threads if necessary + * to break a deadlock. On returning from this call, the calling thread will + * have exclusive access to this lock, and any other locks it owned upon + * entering the acquire method. + * <p> + * This implementation ignores attempts to interrupt the thread. If response to + * interruption is needed, use the method <code>acquire(long)</code> + */ + public void acquire(); + + /** + * Returns the number of nested acquires on this lock that have not been released. + * This is the number of times that release() must be called before the lock is + * freed. + * + * @return the number of nested acquires that have not been released + */ + public int getDepth(); + + /** + * Releases this lock. Locks must only be released by the thread that currently + * owns the lock. + */ + public void release(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/runtime/jobs/ISchedulingRule.d Tue Aug 12 02:34:21 2008 +0200 @@ -0,0 +1,69 @@ +/******************************************************************************* + * Copyright (c) 2003, 2006 IBM Corporation 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: + * IBM - Initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.core.runtime.jobs.ISchedulingRule; + +import dwt.dwthelper.utils; + +/** + * Scheduling rules are used by jobs to indicate when they need exclusive access + * to a resource. Scheduling rules can also be applied synchronously to a thread + * using <tt>IJobManager.beginRule(ISchedulingRule)</tt> and + * <tt>IJobManager.endRule(ISchedulingRule)</tt>. The job manager guarantees that + * no two jobs with conflicting scheduling rules will run concurrently. + * Multiple rules can be applied to a given thread only if the outer rule explicitly + * allows the nesting as specified by the <code>contains</code> method. + * <p> + * Clients may implement this interface. + * + * @see Job#getRule() + * @see Job#setRule(ISchedulingRule) + * @see Job#schedule(long) + * @see IJobManager#beginRule(ISchedulingRule, dwtx.core.runtime.IProgressMonitor) + * @see IJobManager#endRule(ISchedulingRule) + * @since 3.0 + */ +public interface ISchedulingRule { + /** + * Returns whether this scheduling rule completely contains another scheduling + * rule. Rules can only be nested within a thread if the inner rule is completely + * contained within the outer rule. + * <p> + * Implementations of this method must obey the rules of a partial order relation + * on the set of all scheduling rules. In particular, implementations must be reflexive + * (a.contains(a) is always true), antisymmetric (a.contains(b) and b.contains(a) iff a.equals(b), + * and transitive (if a.contains(b) and b.contains(c), then a.contains(c)). Implementations + * of this method must return <code>false</code> when compared to a rule they + * know nothing about. + * + * @param rule the rule to check for containment + * @return <code>true</code> if this rule contains the given rule, and + * <code>false</code> otherwise. + */ + public bool contains(ISchedulingRule rule); + + /** + * Returns whether this scheduling rule is compatible with another scheduling rule. + * If <code>true</code> is returned, then no job with this rule will be run at the + * same time as a job with the conflicting rule. If <code>false</code> is returned, + * then the job manager is free to run jobs with these rules at the same time. + * <p> + * Implementations of this method must be reflexive, symmetric, and consistent, + * and must return <code>false</code> when compared to a rule they know + * nothing about. + * + * @param rule the rule to check for conflicts + * @return <code>true</code> if the rule is conflicting, and <code>false</code> + * otherwise. + */ + public bool isConflicting(ISchedulingRule rule); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/runtime/jobs/Job.d Tue Aug 12 02:34:21 2008 +0200 @@ -0,0 +1,699 @@ +/******************************************************************************* + * Copyright (c) 2003, 2007 IBM Corporation 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: + * IBM Corporation - initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.core.runtime.jobs.Job; + +import tango.core.Thread; +import dwt.dwthelper.utils; + +import dwtx.core.internal.jobs.InternalJob; +import dwtx.core.internal.jobs.JobManager; +import dwtx.core.runtime.IAdaptable; +import dwtx.core.runtime.IProgressMonitor; +import dwtx.core.runtime.IStatus; +import dwtx.core.runtime.QualifiedName; +import dwtx.core.runtime.Status; +import dwtx.core.runtime.jobs.IJobManager; +import dwtx.core.runtime.jobs.IJobChangeListener; +import dwtx.core.runtime.jobs.ISchedulingRule; + +/** + * Jobs are units of runnable work that can be scheduled to be run with the job + * manager. Once a job has completed, it can be scheduled to run again (jobs are + * reusable). + * <p> + * Jobs have a state that indicates what they are currently doing. When constructed, + * jobs start with a state value of <code>NONE</code>. When a job is scheduled + * to be run, it moves into the <code>WAITING</code> state. When a job starts + * running, it moves into the <code>RUNNING</code> state. When execution finishes + * (either normally or through cancelation), the state changes back to + * <code>NONE</code>. + * </p><p> + * A job can also be in the <code>SLEEPING</code> state. This happens if a user + * calls Job.sleep() on a waiting job, or if a job is scheduled to run after a specified + * delay. Only jobs in the <code>WAITING</code> state can be put to sleep. + * Sleeping jobs can be woken at any time using Job.wakeUp(), which will put the + * job back into the <code>WAITING</code> state. + * </p><p> + * Jobs can be assigned a priority that is used as a hint about how the job should + * be scheduled. There is no guarantee that jobs of one priority will be run before + * all jobs of lower priority. The javadoc for the various priority constants provide + * more detail about what each priority means. By default, jobs start in the + * <code>LONG</code> priority class. + * + * @see IJobManager + * @since 3.0 + */ +public abstract class Job : InternalJob, IAdaptable { + // DWT from IAdaptable + public Object getAdapter(ClassInfo adapter){ + return super.getAdapter(adapter); + } + + /** + * Job status return value that is used to indicate asynchronous job completion. + * @see Job#run(IProgressMonitor) + * @see Job#done(IStatus) + */ + private static IStatus ASYNC_FINISH_; + public static IStatus ASYNC_FINISH(){ + if( ASYNC_FINISH_ is null ){ + synchronized( Job.classinfo ) { + if( ASYNC_FINISH_ is null ){ + ASYNC_FINISH_ = new Status(IStatus.OK, JobManager.PI_JOBS, 1, "", null);//$NON-NLS-1$ + } + } + } + return ASYNC_FINISH_; + } + + /* Job priorities */ + /** + * Job priority constant (value 10) for interactive jobs. + * Interactive jobs generally have priority over all other jobs. + * Interactive jobs should be either fast running or very low on CPU + * usage to avoid blocking other interactive jobs from running. + * + * @see #getPriority() + * @see #setPriority(int) + * @see #run(IProgressMonitor) + */ + public static const int INTERACTIVE = 10; + /** + * Job priority constant (value 20) for short background jobs. + * Short background jobs are jobs that typically complete within a second, + * but may take longer in some cases. Short jobs are given priority + * over all other jobs except interactive jobs. + * + * @see #getPriority() + * @see #setPriority(int) + * @see #run(IProgressMonitor) + */ + public static const int SHORT = 20; + /** + * Job priority constant (value 30) for long-running background jobs. + * + * @see #getPriority() + * @see #setPriority(int) + * @see #run(IProgressMonitor) + */ + public static const int LONG = 30; + /** + * Job priority constant (value 40) for build jobs. Build jobs are + * generally run after all other background jobs complete. + * + * @see #getPriority() + * @see #setPriority(int) + * @see #run(IProgressMonitor) + */ + public static const int BUILD = 40; + + /** + * Job priority constant (value 50) for decoration jobs. + * Decoration jobs have lowest priority. Decoration jobs generally + * compute extra information that the user may be interested in seeing + * but is generally not waiting for. + * + * @see #getPriority() + * @see #setPriority(int) + * @see #run(IProgressMonitor) + */ + public static const int DECORATE = 50; + /** + * Job state code (value 0) indicating that a job is not + * currently sleeping, waiting, or running (i.e., the job manager doesn't know + * anything about the job). + * + * @see #getState() + */ + public static const int NONE = 0; + /** + * Job state code (value 1) indicating that a job is sleeping. + * + * @see #run(IProgressMonitor) + * @see #getState() + */ + public static const int SLEEPING = 0x01; + /** + * Job state code (value 2) indicating that a job is waiting to be run. + * + * @see #getState() + */ + public static const int WAITING = 0x02; + /** + * Job state code (value 4) indicating that a job is currently running + * + * @see #getState() + */ + public static const int RUNNING = 0x04; + + /** + * Returns the job manager. + * + * @return the job manager + * @since dwtx.core.jobs 3.2 + */ + public static final IJobManager getJobManager() { + return manager; + } + + /** + * Creates a new job with the specified name. The job name is a human-readable + * value that is displayed to users. The name does not need to be unique, but it + * must not be <code>null</code>. + * + * @param name the name of the job. + */ + public this(String name) { + super(name); + } + + /** + * Registers a job listener with this job + * Has no effect if an identical listener is already registered. + * + * @param listener the listener to be added. + */ + public final void addJobChangeListener(IJobChangeListener listener) { + super.addJobChangeListener(listener); + } + + /** + * Returns whether this job belongs to the given family. Job families are + * represented as objects that are not interpreted or specified in any way + * by the job manager. Thus, a job can choose to belong to any number of + * families. + * <p> + * Clients may override this method. This default implementation always returns + * <code>false</code>. Overriding implementations must return <code>false</code> + * for families they do not recognize. + * </p> + * + * @param family the job family identifier + * @return <code>true</code> if this job belongs to the given family, and + * <code>false</code> otherwise. + */ + public bool belongsTo(Object family) { + return false; + } + + /** + * Stops the job. If the job is currently waiting, + * it will be removed from the queue. If the job is sleeping, + * it will be discarded without having a chance to resume and its sleeping state + * will be cleared. If the job is currently executing, it will be asked to + * stop but there is no guarantee that it will do so. + * + * @return <code>false</code> if the job is currently running (and thus may not + * respond to cancelation), and <code>true</code> in all other cases. + */ + public final bool cancel() { + return super.cancel(); + } + + /** + * A hook method indicating that this job is running and {@link #cancel()} + * is being called for the first time. + * <p> + * Subclasses may override this method to perform additional work when + * a cancelation request is made. This default implementation does nothing. + * @since 3.3 + */ + protected void canceling() { + //default implementation does nothing + } + + /** + * Jobs that complete their execution asynchronously must indicate when they + * are finished by calling this method. This method must not be called by + * a job that has not indicated that it is executing asynchronously. + * <p> + * This method must not be called from within the scope of a job's <code>run</code> + * method. Jobs should normally indicate completion by returning an appropriate + * status from the <code>run</code> method. Jobs that return a status of + * <code>ASYNC_FINISH</code> from their run method must later call + * <code>done</code> to indicate completion. + * + * @param result a status object indicating the result of the job's execution. + * @see #ASYNC_FINISH + * @see #run(IProgressMonitor) + */ + public final void done(IStatus result) { + super.done(result); + } + + /** + * Returns the human readable name of this job. The name is never + * <code>null</code>. + * + * @return the name of this job + */ + public final String getName() { + return super.getName(); + } + + /** + * Returns the priority of this job. The priority is used as a hint when the job + * is scheduled to be run. + * + * @return the priority of the job. One of INTERACTIVE, SHORT, LONG, BUILD, + * or DECORATE. + */ + public final int getPriority() { + return super.getPriority(); + } + + /** + * Returns the value of the property of this job identified by the given key, + * or <code>null</code> if this job has no such property. + * + * @param key the name of the property + * @return the value of the property, + * or <code>null</code> if this job has no such property + * @see #setProperty(QualifiedName, Object) + */ + public final Object getProperty(QualifiedName key) { + return super.getProperty(key); + } + + /** + * Returns the result of this job's last run. + * + * @return the result of this job's last run, or <code>null</code> if this + * job has never finished running. + */ + public final IStatus getResult() { + return super.getResult(); + } + + /** + * Returns the scheduling rule for this job. Returns <code>null</code> if this job has no + * scheduling rule. + * + * @return the scheduling rule for this job, or <code>null</code>. + * @see ISchedulingRule + * @see #setRule(ISchedulingRule) + */ + public final ISchedulingRule getRule() { + return super.getRule(); + } + + /** + * Returns the state of the job. Result will be one of: + * <ul> + * <li><code>Job.RUNNING</code> - if the job is currently running.</li> + * <li><code>Job.WAITING</code> - if the job is waiting to be run.</li> + * <li><code>Job.SLEEPING</code> - if the job is sleeping.</li> + * <li><code>Job.NONE</code> - in all other cases.</li> + * </ul> + * <p> + * Note that job state is inherently volatile, and in most cases clients + * cannot rely on the result of this method being valid by the time the + * result is obtained. For example, if <tt>getState</tt> returns + * <tt>RUNNING</tt>, the job may have actually completed by the + * time the <tt>getState</tt> method returns. All clients can infer from + * invoking this method is that the job was recently in the returned state. + * + * @return the job state + */ + public final int getState() { + return super.getState(); + } + + /** + * Returns the thread that this job is currently running in. + * + * @return the thread this job is running in, or <code>null</code> + * if this job is not running or the thread is unknown. + */ + public final Thread getThread() { + return super.getThread(); + } + + /** + * Returns whether this job is blocking a higher priority non-system job from + * starting due to a conflicting scheduling rule. Returns <code>false</code> + * if this job is not running, or is not blocking a higher priority non-system job. + * + * @return <code>true</code> if this job is blocking a higher priority non-system + * job, and <code>false</code> otherwise. + * @see #getRule() + * @see #isSystem() + */ + public final bool isBlocking() { + return super.isBlocking(); + } + + /** + * Returns whether this job is a system job. System jobs are typically not + * revealed to users in any UI presentation of jobs. Other than their UI presentation, + * system jobs act exactly like other jobs. If this value is not explicitly set, jobs + * are treated as non-system jobs. The default value is <code>false</code>. + * + * @return <code>true</code> if this job is a system job, and + * <code>false</code> otherwise. + * @see #setSystem(bool) + */ + public final bool isSystem() { + return super.isSystem(); + } + + /** + * Returns whether this job has been directly initiated by a UI end user. + * These jobs may be presented differently in the UI. The default value + * is <code>false</code>. + * + * @return <code>true</code> if this job is a user-initiated job, and + * <code>false</code> otherwise. + * @see #setUser(bool) + */ + public final bool isUser() { + return super.isUser(); + } + + /** + * Waits until this job is finished. This method will block the calling thread until the + * job has finished executing, or until this thread has been interrupted. If the job + * has not been scheduled, this method returns immediately. A job must not + * be joined from within the scope of its run method. + * <p> + * If this method is called on a job that reschedules itself from within the + * <tt>run</tt> method, the join will return at the end of the first execution. + * In other words, join will return the first time this job exits the + * {@link #RUNNING} state, or as soon as this job enters the {@link #NONE} state. + * </p> + * <p> + * If this method is called while the job manager is suspended, this job + * will only be joined if it is already running; if this job is waiting or sleeping, + * this method returns immediately. + * </p> + * <p> + * Note that there is a deadlock risk when using join. If the calling thread owns + * a lock or object monitor that the joined thread is waiting for, deadlock + * will occur. + * </p> + * + * @exception InterruptedException if this thread is interrupted while waiting + * @see ILock + * @see IJobManager#suspend() + */ + public final void join() { + super.join(); + } + + /** + * Removes a job listener from this job. + * Has no effect if an identical listener is not already registered. + * + * @param listener the listener to be removed + */ + public final void removeJobChangeListener(IJobChangeListener listener) { + super.removeJobChangeListener(listener); + } + + /** + * Executes this job. Returns the result of the execution. + * <p> + * The provided monitor can be used to report progress and respond to + * cancellation. If the progress monitor has been canceled, the job + * should finish its execution at the earliest convenience and return a result + * status of severity {@link IStatus#CANCEL}. The singleton + * cancel status {@link Status#CANCEL_STATUS} can be used for + * this purpose. The monitor is only valid for the duration of the invocation + * of this method. + * <p> + * This method must not be called directly by clients. Clients should call + * <code>schedule</code>, which will in turn cause this method to be called. + * <p> + * Jobs can optionally finish their execution asynchronously (in another thread) by + * returning a result status of {@link #ASYNC_FINISH}. Jobs that finish + * asynchronously <b>must</b> specify the execution thread by calling + * <code>setThread</code>, and must indicate when they are finished by calling + * the method <code>done</code>. + * + * @param monitor the monitor to be used for reporting progress and + * responding to cancelation. The monitor is never <code>null</code> + * @return resulting status of the run. The result must not be <code>null</code> + * @see #ASYNC_FINISH + * @see #done(IStatus) + */ + protected abstract IStatus run(IProgressMonitor monitor); + + /** + * Schedules this job to be run. The job is added to a queue of waiting + * jobs, and will be run when it arrives at the beginning of the queue. + * <p> + * This is a convenience method, fully equivalent to + * <code>schedule(0L)</code>. + * </p> + * @see #schedule(long) + */ + public final void schedule() { + super.schedule(0L); + } + + /** + * Schedules this job to be run after a specified delay. The job is put in the + * {@link #SLEEPING} state until the specified delay has elapsed, after which + * the job is added to a queue of {@link #WAITING} jobs. Once the job arrives + * at the beginning of the queue, it will be run at the first available opportunity. + * </p><p> + * Jobs of equal priority and <code>delay</code> with conflicting scheduling + * rules are guaranteed to run in the order they are scheduled. No guarantees + * are made about the relative execution order of jobs with unrelated or + * <code>null</code> scheduling rules, or different priorities. + * <p> + * If this job is currently running, it will be rescheduled with the specified + * delay as soon as it finishes. If this method is called multiple times + * while the job is running, the job will still only be rescheduled once, + * with the most recent delay value that was provided. + * </p><p> + * Scheduling a job that is waiting or sleeping has no effect. + * </p> + * + * @param delay a time delay in milliseconds before the job should run + * @see ISchedulingRule + */ + public final void schedule(long delay) { + super.schedule(delay); + } + + /** + * Changes the name of this job. If the job is currently running, waiting, + * or sleeping, the new job name may not take effect until the next time the + * job is scheduled. + * <p> + * The job name is a human-readable value that is displayed to users. The name + * does not need to be unique, but it must not be <code>null</code>. + * + * @param name the name of the job. + */ + public final void setName(String name) { + super.setName(name); + } + + /** + * Sets the priority of the job. This will not affect the execution of + * a running job, but it will affect how the job is scheduled while + * it is waiting to be run. + * + * @param priority the new job priority. One of + * INTERACTIVE, SHORT, LONG, BUILD, or DECORATE. + */ + public final void setPriority(int priority) { + super.setPriority(priority); + } + + /** + * Associates this job with a progress group. Progress feedback + * on this job's next execution will be displayed together with other + * jobs in that group. The provided monitor must be a monitor + * created by the method <tt>IJobManager.createProgressGroup</tt> + * and must have at least <code>ticks</code> units of available work. + * <p> + * The progress group must be set before the job is scheduled. + * The group will be used only for a single invocation of the job's + * <tt>run</tt> method, after which any association of this job to the + * group will be lost. + * + * @see IJobManager#createProgressGroup() + * @param group The progress group to use for this job + * @param ticks the number of work ticks allocated from the + * parent monitor, or {@link IProgressMonitor#UNKNOWN} + */ + public final void setProgressGroup(IProgressMonitor group, int ticks) { + super.setProgressGroup(group, ticks); + } + + /** + * Sets the value of the property of this job identified + * by the given key. If the supplied value is <code>null</code>, + * the property is removed from this resource. + * <p> + * Properties are intended to be used as a caching mechanism + * by ISV plug-ins. They allow key-object associations to be stored with + * a job instance. These key-value associations are maintained in + * memory (at all times), and the information is never discarded automatically. + * </p><p> + * The qualifier part of the property name must be the unique identifier + * of the declaring plug-in (e.g. <code>"com.example.plugin"</code>). + * </p> + * + * @param key the qualified name of the property + * @param value the value of the property, + * or <code>null</code> if the property is to be removed + * @see #getProperty(QualifiedName) + */ + public void setProperty(QualifiedName key, Object value) { + super.setProperty(key, value); + } + + /** + * Sets the scheduling rule to be used when scheduling this job. This method + * must be called before the job is scheduled. + * + * @param rule the new scheduling rule, or <code>null</code> if the job + * should have no scheduling rule + * @see #getRule() + */ + public final void setRule(ISchedulingRule rule) { + super.setRule(rule); + } + + /** + * Sets whether or not this job is a system job. System jobs are typically not + * revealed to users in any UI presentation of jobs. Other than their UI presentation, + * system jobs act exactly like other jobs. If this value is not explicitly set, jobs + * are treated as non-system jobs. This method must be called before the job + * is scheduled. + * + * @param value <code>true</code> if this job should be a system job, and + * <code>false</code> otherwise. + * @see #isSystem() + */ + public final void setSystem(bool value) { + super.setSystem(value); + } + + /** + * Sets whether or not this job has been directly initiated by a UI end user. + * These jobs may be presented differently in the UI. This method must be + * called before the job is scheduled. + * + * @param value <code>true</code> if this job is a user-initiated job, and + * <code>false</code> otherwise. + * @see #isUser() + */ + public final void setUser(bool value) { + super.setUser(value); + } + + /** + * Sets the thread that this job is currently running in, or <code>null</code> + * if this job is not running or the thread is unknown. + * <p> + * Jobs that use the {@link #ASYNC_FINISH} return code should tell + * the job what thread it is running in. This is used to prevent deadlocks. + * + * @param thread the thread that this job is running in. + * + * @see #ASYNC_FINISH + * @see #run(IProgressMonitor) + */ + public final void setThread(Thread thread) { + super.setThread(thread); + } + + /** + * Returns whether this job should be run. + * If <code>false</code> is returned, this job will be discarded by the job manager + * without running. + * <p> + * This method is called immediately prior to calling the job's + * run method, so it can be used for last minute pre-condition checking before + * a job is run. This method must not attempt to schedule or change the + * state of any other job. + * </p><p> + * Clients may override this method. This default implementation always returns + * <code>true</code>. + * </p> + * + * @return <code>true</code> if this job should be run + * and <code>false</code> otherwise + */ + public bool shouldRun() { + return true; + } + + /** + * Returns whether this job should be scheduled. + * If <code>false</code> is returned, this job will be discarded by the job manager + * without being added to the queue. + * <p> + * This method is called immediately prior to adding the job to the waiting job + * queue.,so it can be used for last minute pre-condition checking before + * a job is scheduled. + * </p><p> + * Clients may override this method. This default implementation always returns + * <code>true</code>. + * </p> + * + * @return <code>true</code> if the job manager should schedule this job + * and <code>false</code> otherwise + */ + public bool shouldSchedule() { + return true; + } + + /** + * Requests that this job be suspended. If the job is currently waiting to be run, it + * will be removed from the queue move into the {@link #SLEEPING} state. + * The job will remain asleep until either resumed or canceled. If this job is not + * currently waiting to be run, this method has no effect. + * <p> + * Sleeping jobs can be resumed using <code>wakeUp</code>. + * + * @return <code>false</code> if the job is currently running (and thus cannot + * be put to sleep), and <code>true</code> in all other cases + * @see #wakeUp() + */ + public final bool sleep() { + return super.sleep(); + } + + /** + * Puts this job immediately into the {@link #WAITING} state so that it is + * eligible for immediate execution. If this job is not currently sleeping, + * the request is ignored. + * <p> + * This is a convenience method, fully equivalent to + * <code>wakeUp(0L)</code>. + * </p> + * @see #sleep() + */ + public final void wakeUp() { + super.wakeUp(0L); + } + + /** + * Puts this job back into the {@link #WAITING} state after + * the specified delay. This is equivalent to canceling the sleeping job and + * rescheduling with the given delay. If this job is not currently sleeping, + * the request is ignored. + * + * @param delay the number of milliseconds to delay + * @see #sleep() + */ + public final void wakeUp(long delay) { + super.wakeUp(delay); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/runtime/jobs/JobChangeAdapter.d Tue Aug 12 02:34:21 2008 +0200 @@ -0,0 +1,79 @@ +/******************************************************************************* + * Copyright (c) 2003, 2006 IBM Corporation 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: + * IBM - Initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.core.runtime.jobs.JobChangeAdapter; + +import dwt.dwthelper.utils; +import dwtx.core.runtime.jobs.IJobChangeListener; +import dwtx.core.runtime.jobs.IJobChangeEvent; + +/** + * This adapter class provides default implementations for the + * methods described by the <code>IJobChangeListener</code> interface. + * <p> + * Classes that wish to listen to the progress of scheduled jobs can + * extend this class and override only the methods which they are + * interested in. + * </p> + * + * @see IJobChangeListener + * @since 3.0 + */ +public class JobChangeAdapter : IJobChangeListener { + /* (non-Javadoc) + * @see IJobChangeListener#aboutToRun(IJobChangeEvent) + * This default implementation does nothing + */ + public void aboutToRun(IJobChangeEvent event) { + // do nothing + } + + /* (non-Javadoc) + * @see IJobChangeListener#awake(IJobChangeEvent) + * This default implementation does nothing + */ + public void awake(IJobChangeEvent event) { + // do nothing + } + + /* (non-Javadoc) + * @see IJobChangeListener#done(IJobChangeEvent) + * This default implementation does nothing + */ + public void done(IJobChangeEvent event) { + // do nothing + } + + /* (non-Javadoc) + * @see IJobChangeListener#running(IJobChangeEvent) + * This default implementation does nothing + */ + public void running(IJobChangeEvent event) { + // do nothing + } + + /* (non-Javadoc) + * @see IJobChangeListener#scheduled(IJobChangeEvent) + * This default implementation does nothing + */ + public void scheduled(IJobChangeEvent event) { + // do nothing + } + + /* (non-Javadoc) + * @see IJobChangeListener#sleeping(IJobChangeEvent) + * This default implementation does nothing + */ + public void sleeping(IJobChangeEvent event) { + // do nothing + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/runtime/jobs/LockListener.d Tue Aug 12 02:34:21 2008 +0200 @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright (c) 2004, 2006 IBM Corporation 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: + * IBM - Initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.core.runtime.jobs.LockListener; + +import tango.core.Thread; +import dwt.dwthelper.utils; + +import dwtx.core.internal.jobs.JobManager; +import dwtx.core.internal.jobs.LockManager; +import dwtx.core.runtime.jobs.Job; + +/** + * A lock listener is notified whenever a thread is about to wait + * on a lock, and when a thread is about to release a lock. + * <p> + * This class is for internal use by the platform-related plug-ins. + * Clients outside of the base platform should not reference or subclass this class. + * </p> + * + * @see IJobManager#setLockListener(LockListener) + * @since 3.0 + */ +public class LockListener { + private const LockManager manager; + + public this(){ + manager = (cast(JobManager)Job.getJobManager()).getLockManager(); + } + + /** + * Notification that a thread is about to block on an attempt to acquire a lock. + * Returns whether the thread should be granted immediate access to the lock. + * <p> + * This default implementation always returns <code>false</code>. + * Subclasses may override. + * + * @param lockOwner the thread that currently owns the lock this thread is + * waiting for, or <code>null</code> if unknown. + * @return <code>true</code> if the thread should be granted immediate access, + * and <code>false</code> if it should wait for the lock to be available + */ + public bool aboutToWait(Thread lockOwner) { + return false; + } + + /** + * Notification that a thread is about to release a lock. + * <p> + * This default implementation does nothing. Subclasses may override. + */ + public void aboutToRelease() { + //do nothing + } + + /** + * Returns whether this thread currently owns any locks + * @return <code>true</code> if this thread owns any locks, and + * <code>false</code> otherwise. + */ + protected final bool isLockOwnerThread() { + return manager.isLockOwner(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/runtime/jobs/MultiRule.d Tue Aug 12 02:34:21 2008 +0200 @@ -0,0 +1,192 @@ +/******************************************************************************* + * Copyright (c) 2003, 2008 IBM Corporation 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: + * IBM - Initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.core.runtime.jobs.MultiRule; + +import dwt.dwthelper.utils; +import dwtx.dwtxhelper.Collection; + +import dwtx.core.runtime.jobs.ISchedulingRule; + +/** + * A MultiRule is a compound scheduling rule that represents a fixed group of child + * scheduling rules. A MultiRule conflicts with another rule if any of its children conflict + * with that rule. More formally, a compound rule represents a logical intersection + * of its child rules with respect to the <code>isConflicting</code> equivalence + * relation. + * <p> + * A MultiRule will never contain other MultiRules as children. If a MultiRule is provided + * as a child, its children will be added instead. + * </p> + * + * @since 3.0 + * @noextend This class is not intended to be subclassed by clients. + */ +public class MultiRule : ISchedulingRule { + private ISchedulingRule[] rules; + + /** + * Returns a scheduling rule that encompasses all provided rules. The resulting + * rule may or may not be an instance of <code>MultiRule</code>. If all + * provided rules are <code>null</code> then the result will be + * <code>null</code>. + * + * @param ruleArray An array of scheduling rules, some of which may be <code>null</code> + * @return a combined scheduling rule, or <code>null</code> + * @since 3.1 + */ + public static ISchedulingRule combine(ISchedulingRule[] ruleArray) { + ISchedulingRule result = null; + for (int i = 0; i < ruleArray.length; i++) { + if (ruleArray[i] is null) + continue; + if (result is null) { + result = ruleArray[i]; + continue; + } + result = combine(result, ruleArray[i]); + } + return result; + } + + /** + * Returns a scheduling rule that encompasses both provided rules. The resulting + * rule may or may not be an instance of <code>MultiRule</code>. If both + * provided rules are <code>null</code> then the result will be + * <code>null</code>. + * + * @param rule1 a scheduling rule, or <code>null</code> + * @param rule2 another scheduling rule, or <code>null</code> + * @return a combined scheduling rule, or <code>null</code> + */ + public static ISchedulingRule combine(ISchedulingRule rule1, ISchedulingRule rule2) { + if (rule1 is rule2) + return rule1; + if (rule1 is null) + return rule2; + if (rule2 is null) + return rule1; + if (rule1.contains(rule2)) + return rule1; + if (rule2.contains(rule1)) + return rule2; + MultiRule result = new MultiRule(); + result.rules = [rule1, rule2]; + //make sure we don't end up with nested multi-rules + if (cast(MultiRule)rule1 || cast(MultiRule)rule2 ) + result.rules = flatten(result.rules); + return result; + } + + /* + * Collapses an array of rules that may contain MultiRules into an + * array in which no rules are MultiRules. + */ + private static ISchedulingRule[] flatten(ISchedulingRule[] nestedRules) { + ArrayList myRules = new ArrayList(nestedRules.length); + for (int i = 0; i < nestedRules.length; i++) { + if (cast(MultiRule)nestedRules[i] ) { + ISchedulingRule[] children = (cast(MultiRule) nestedRules[i]).getChildren(); + for (int j = 0; j < children.length; j++) + myRules.add(cast(Object)children[j]); + } else { + myRules.add(cast(Object)nestedRules[i]); + } + } + return arraycast!(ISchedulingRule)( myRules.toArray() ); + } + + /** + * Creates a new scheduling rule that composes a set of nested rules. + * + * @param nestedRules the nested rules for this compound rule. + */ + public this(ISchedulingRule[] nestedRules) { + this.rules = flatten(nestedRules); + } + + /** + * Creates a new scheduling rule with no nested rules. For + * internal use only. + */ + private this() { + //to be invoked only by factory methods + } + + /** + * Returns the child rules within this rule. + * @return the child rules + */ + public ISchedulingRule[] getChildren() { + return rules.dup; + } + + /* (non-Javadoc) + * @see dwtx.core.runtime.jobs.ISchedulingRule#contains(dwtx.core.runtime.jobs.ISchedulingRule) + */ + public bool contains(ISchedulingRule rule) { + if (this is rule) + return true; + if (cast(MultiRule)rule ) { + ISchedulingRule[] otherRules = (cast(MultiRule) rule).getChildren(); + //for each child of the target, there must be some child in this rule that contains it. + for (int other = 0; other < otherRules.length; other++) { + bool found = false; + for (int mine = 0; !found && mine < rules.length; mine++) + found = rules[mine].contains(otherRules[other]); + if (!found) + return false; + } + return true; + } + for (int i = 0; i < rules.length; i++) + if (rules[i].contains(rule)) + return true; + return false; + } + + /* (non-Javadoc) + * @see dwtx.core.runtime.jobs.ISchedulingRule#isConflicting(dwtx.core.runtime.jobs.ISchedulingRule) + */ + public bool isConflicting(ISchedulingRule rule) { + if (this is rule) + return true; + if (cast(MultiRule)rule ) { + ISchedulingRule[] otherRules = (cast(MultiRule) rule).getChildren(); + for (int j = 0; j < otherRules.length; j++) + for (int i = 0; i < rules.length; i++) + if (rules[i].isConflicting(otherRules[j])) + return true; + } else { + for (int i = 0; i < rules.length; i++) + if (rules[i].isConflicting(rule)) + return true; + } + return false; + } + + /* + * For debugging purposes only. + */ + public String toString() { + StringBuffer buffer = new StringBuffer(); + buffer.append("MultiRule["); //$NON-NLS-1$ + int last = rules.length - 1; + for (int i = 0; i < rules.length; i++) { + buffer.append(rules[i] ? (cast(Object)rules[i]).toString() : "null" ); + if (i !is last) + buffer.append(','); + } + buffer.append(']'); + return buffer.toString(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/runtime/jobs/ProgressProvider.d Tue Aug 12 02:34:21 2008 +0200 @@ -0,0 +1,106 @@ +/******************************************************************************* + * Copyright (c) 2003, 2006 IBM Corporation 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: + * IBM Corporation - initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.core.runtime.jobs.ProgressProvider; + +import dwt.dwthelper.utils; + +import dwtx.core.runtime.IProgressMonitor; +import dwtx.core.runtime.NullProgressMonitor; +import dwtx.core.runtime.SubProgressMonitor; + +import dwtx.core.runtime.jobs.Job; + +/** + * The progress provider supplies the job manager with progress monitors for + * running jobs. There can only be one progress provider at any given time. + * <p> + * This class is intended for use by the currently executing Eclipse application. + * Plug-ins outside the currently running application should not reference or + * subclass this class. + * </p> + * + * @see IJobManager#setProgressProvider(ProgressProvider) + * @since 3.0 + */ +public abstract class ProgressProvider { + /** + * Provides a new progress monitor instance to be used by the given job. + * This method is called prior to running any job that does not belong to a + * progress group. The returned monitor will be supplied to the job's + * <code>run</code> method. + * + * @see #createProgressGroup() + * @see Job#setProgressGroup(IProgressMonitor, int) + * @param job the job to create a progress monitor for + * @return a progress monitor, or <code>null</code> if no progress monitoring + * is needed. + */ + public abstract IProgressMonitor createMonitor(Job job); + + /** + * Returns a progress monitor that can be used to provide + * aggregated progress feedback on a set of running jobs. + * This method implements <code>IJobManager.createProgressGroup</code>, + * and must obey all rules specified in that contract. + * <p> + * This default implementation returns a new + * <code>NullProgressMonitor</code> Subclasses may override. + * + * @see IJobManager#createProgressGroup() + * @return a progress monitor + */ + public IProgressMonitor createProgressGroup() { + return new NullProgressMonitor(); + } + + /** + * Returns a progress monitor that can be used by a running job + * to report progress in the context of a progress group. This method + * implements <code>Job.setProgressGroup</code>. One of the + * two <code>createMonitor</code> methods will be invoked + * prior to each execution of a job, depending on whether a progress + * group was specified for the job. + * <p> + * The provided monitor must be a monitor returned by the method + * <code>createProgressGroup</code>. This method is responsible + * for asserting this and throwing an appropriate runtime exception + * if an invalid monitor is provided. + * <p> + * This default implementation returns a new + * <code>SubProgressMonitor</code>. Subclasses may override. + * + * @see IJobManager#createProgressGroup() + * @see Job#setProgressGroup(IProgressMonitor, int) + * @param job the job to create a progress monitor for + * @param group the progress monitor group that this job belongs to + * @param ticks the number of ticks of work for the progress monitor + * @return a progress monitor, or <code>null</code> if no progress monitoring + * is needed. + */ + public IProgressMonitor createMonitor(Job job, IProgressMonitor group, int ticks) { + return new SubProgressMonitor(group, ticks); + } + + /** + * Returns a progress monitor to use when none has been provided + * by the client running the job. + * <p> + * This default implementation returns a new + * <code>NullProgressMonitor</code> Subclasses may override. + * + * @return a progress monitor + */ + public IProgressMonitor getDefaultMonitor() { + return new NullProgressMonitor(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/core/runtime/jobs/package.html Tue Aug 12 02:34:21 2008 +0200 @@ -0,0 +1,22 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> + <title>Package-level Javadoc</title> +</head> +<body> +Provides core support for scheduling and interacting with background activity. +<h2> +Package Specification</h2> +<p> +This package specifies API for scheduling background tasks, or jobs. Jobs can be +scheduled for immediate execution, or for execution after a specified delay. Once +scheduled, jobs can be queried, canceled, or suspended. Rules can be attached to +jobs to indicate when they can run, and whether they can run simultaneously with other +jobs. This package also includes a generic locking facility that includes support for +detecting and responding to deadlock. +<p> +@since 3.0 +<p> +</body> +</html>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/osgi/util/NLS.d Tue Aug 12 02:34:21 2008 +0200 @@ -0,0 +1,446 @@ +/******************************************************************************* + * Copyright (c) 2005, 2008 IBM Corporation 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: + * IBM - Initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.osgi.util.NLS; + +import dwt.dwthelper.utils; + +// import java.io.IOException; +// import java.io.InputStream; +// import java.lang.reflect.Field; +// import java.lang.reflect.Modifier; +// import java.security.AccessController; +// import java.security.PrivilegedAction; +// import java.util.ArrayList; +// import java.util.HashMap; +// import java.util.Locale; +// import java.util.Map; +// import java.util.Properties; +// +// import dwtx.osgi.framework.debug.Debug; +// import dwtx.osgi.framework.log.FrameworkLog; +// import dwtx.osgi.framework.log.FrameworkLogEntry; + +/** + * Common superclass for all message bundle classes. Provides convenience + * methods for manipulating messages. + * <p> + * The <code>#bind</code> methods perform string substitution and should be considered a + * convenience and <em>not</em> a full substitute replacement for <code>MessageFormat#format</code> + * method calls. + * </p> + * <p> + * Text appearing within curly braces in the given message, will be interpreted + * as a numeric index to the corresponding substitution object in the given array. Calling + * the <code>#bind</code> methods with text that does not map to an integer will result in an + * {@link IllegalArgumentException}. + * </p> + * <p> + * Text appearing within single quotes is treated as a literal. A single quote is escaped by + * a preceeding single quote. + * </p> + * <p> + * Clients who wish to use the full substitution power of the <code>MessageFormat</code> class should + * call that class directly and not use these <code>#bind</code> methods. + * </p> + * <p> + * Clients may subclass this type. + * </p> + * + * @since 3.1 + */ +public abstract class NLS { + +// private static final Object[] EMPTY_ARGS = new Object[0]; +// private static final String EXTENSION = ".properties"; //$NON-NLS-1$ +// private static String[] nlSuffixes; +// /* +// * NOTE do not change the name of this field; it is set by the Framework using reflection +// */ +// private static FrameworkLog frameworkLog; +// +// static final int SEVERITY_ERROR = 0x04; +// static final int SEVERITY_WARNING = 0x02; +// /* +// * This object is assigned to the value of a field map to indicate +// * that a translated message has already been assigned to that field. +// */ +// static final Object ASSIGNED = new Object(); +// +// /** +// * Creates a new NLS instance. +// */ +// protected NLS() { +// super(); +// } + + /** + * Bind the given message's substitution locations with the given string value. + * + * @param message the message to be manipulated + * @param binding the object to be inserted into the message + * @return the manipulated String + * @throws IllegalArgumentException if the text appearing within curly braces in the given message does not map to an integer + */ + public static String bind(String message, Object binding) { + implMissing( __FILE__, __LINE__ ); + return null; +// return internalBind(message, null, String.valueOf(binding), null); + } + public static String bind(String message, String binding) { + implMissing( __FILE__, __LINE__ ); + return null; +// return internalBind(message, null, String.valueOf(binding), null); + } + + /** + * Bind the given message's substitution locations with the given string values. + * + * @param message the message to be manipulated + * @param binding1 An object to be inserted into the message + * @param binding2 A second object to be inserted into the message + * @return the manipulated String + * @throws IllegalArgumentException if the text appearing within curly braces in the given message does not map to an integer + */ + public static String bind(String message, Object binding1, Object binding2) { + implMissing( __FILE__, __LINE__ ); + return null; +// return internalBind(message, null, String.valueOf(binding1), String.valueOf(binding2)); + } + public static String bind(String message, String binding1, String binding2) { + implMissing( __FILE__, __LINE__ ); + return null; +// return internalBind(message, null, String.valueOf(binding1), String.valueOf(binding2)); + } + + /** + * Bind the given message's substitution locations with the given string values. + * + * @param message the message to be manipulated + * @param bindings An array of objects to be inserted into the message + * @return the manipulated String + * @throws IllegalArgumentException if the text appearing within curly braces in the given message does not map to an integer + */ + public static String bind(String message, Object[] bindings) { + implMissing( __FILE__, __LINE__ ); + return null; +// return internalBind(message, bindings, null, null); + } + public static String bind(String message, String[] bindings) { + implMissing( __FILE__, __LINE__ ); + return null; +// return internalBind(message, bindings, null, null); + } + +// /** +// * Initialize the given class with the values from the specified message bundle. +// * +// * @param bundleName fully qualified path of the class name +// * @param clazz the class where the constants will exist +// */ +// public static void initializeMessages(final String bundleName, final Class clazz) { +// if (System.getSecurityManager() is null) { +// load(bundleName, clazz); +// return; +// } +// AccessController.doPrivileged(new PrivilegedAction() { +// public Object run() { +// load(bundleName, clazz); +// return null; +// } +// }); +// } +// +// /* +// * Perform the string substitution on the given message with the specified args. +// * See the class comment for exact details. +// */ +// private static String internalBind(String message, Object[] args, String argZero, String argOne) { +// if (message is null) +// return "No message available."; //$NON-NLS-1$ +// if (args is null || args.length is 0) +// args = EMPTY_ARGS; +// +// int length = message.length(); +// //estimate correct size of string buffer to avoid growth +// int bufLen = length + (args.length * 5); +// if (argZero !is null) +// bufLen += argZero.length() - 3; +// if (argOne !is null) +// bufLen += argOne.length() - 3; +// StringBuffer buffer = new StringBuffer(bufLen < 0 ? 0 : bufLen); +// for (int i = 0; i < length; i++) { +// char c = message.charAt(i); +// switch (c) { +// case '{' : +// int index = message.indexOf('}', i); +// // if we don't have a matching closing brace then... +// if (index is -1) { +// buffer.append(c); +// break; +// } +// i++; +// if (i >= length) { +// buffer.append(c); +// break; +// } +// // look for a substitution +// int number = -1; +// try { +// number = Integer.parseInt(message.substring(i, index)); +// } catch (NumberFormatException e) { +// throw new IllegalArgumentException(); +// } +// if (number is 0 && argZero !is null) +// buffer.append(argZero); +// else if (number is 1 && argOne !is null) +// buffer.append(argOne); +// else { +// if (number >= args.length || number < 0) { +// buffer.append("<missing argument>"); //$NON-NLS-1$ +// i = index; +// break; +// } +// buffer.append(args[number]); +// } +// i = index; +// break; +// case '\'' : +// // if a single quote is the last char on the line then skip it +// int nextIndex = i + 1; +// if (nextIndex >= length) { +// buffer.append(c); +// break; +// } +// char next = message.charAt(nextIndex); +// // if the next char is another single quote then write out one +// if (next is '\'') { +// i++; +// buffer.append(c); +// break; +// } +// // otherwise we want to read until we get to the next single quote +// index = message.indexOf('\'', nextIndex); +// // if there are no more in the string, then skip it +// if (index is -1) { +// buffer.append(c); +// break; +// } +// // otherwise write out the chars inside the quotes +// buffer.append(message.substring(nextIndex, index)); +// i = index; +// break; +// default : +// buffer.append(c); +// } +// } +// return buffer.toString(); +// } +// +// /* +// * Build an array of property files to search. The returned array contains +// * the property fields in order from most specific to most generic. +// * So, in the FR_fr locale, it will return file_fr_FR.properties, then +// * file_fr.properties, and finally file.properties. +// */ +// private static String[] buildVariants(String root) { +// if (nlSuffixes is null) { +// //build list of suffixes for loading resource bundles +// String nl = Locale.getDefault().toString(); +// ArrayList result = new ArrayList(4); +// int lastSeparator; +// while (true) { +// result.add('_' + nl + EXTENSION); +// lastSeparator = nl.lastIndexOf('_'); +// if (lastSeparator is -1) +// break; +// nl = nl.substring(0, lastSeparator); +// } +// //add the empty suffix last (most general) +// result.add(EXTENSION); +// nlSuffixes = (String[]) result.toArray(new String[result.size()]); +// } +// root = root.replace('.', '/'); +// String[] variants = new String[nlSuffixes.length]; +// for (int i = 0; i < variants.length; i++) +// variants[i] = root + nlSuffixes[i]; +// return variants; +// } +// +// private static void computeMissingMessages(String bundleName, Class clazz, Map fieldMap, Field[] fieldArray, bool isAccessible) { +// // iterate over the fields in the class to make sure that there aren't any empty ones +// final int MOD_EXPECTED = Modifier.PUBLIC | Modifier.STATIC; +// final int MOD_MASK = MOD_EXPECTED | Modifier.FINAL; +// final int numFields = fieldArray.length; +// for (int i = 0; i < numFields; i++) { +// Field field = fieldArray[i]; +// if ((field.getModifiers() & MOD_MASK) !is MOD_EXPECTED) +// continue; +// //if the field has a a value assigned, there is nothing to do +// if (fieldMap.get(field.getName()) is ASSIGNED) +// continue; +// try { +// // Set a value for this empty field. We should never get an exception here because +// // we know we have a public static non-final field. If we do get an exception, silently +// // log it and continue. This means that the field will (most likely) be un-initialized and +// // will fail later in the code and if so then we will see both the NPE and this error. +// String value = "NLS missing message: " + field.getName() + " in: " + bundleName; //$NON-NLS-1$ //$NON-NLS-2$ +// if (Debug.DEBUG_MESSAGE_BUNDLES) +// System.out.println(value); +// log(SEVERITY_WARNING, value, null); +// if (!isAccessible) +// field.setAccessible(true); +// field.set(null, value); +// } catch (Exception e) { +// log(SEVERITY_ERROR, "Error setting the missing message value for: " + field.getName(), e); //$NON-NLS-1$ +// } +// } +// } +// +// /* +// * Load the given resource bundle using the specified class loader. +// */ +// static void load(final String bundleName, Class clazz) { +// long start = System.currentTimeMillis(); +// final Field[] fieldArray = clazz.getDeclaredFields(); +// ClassLoader loader = clazz.getClassLoader(); +// +// bool isAccessible = (clazz.getModifiers() & Modifier.PUBLIC) !is 0; +// +// //build a map of field names to Field objects +// final int len = fieldArray.length; +// Map fields = new HashMap(len * 2); +// for (int i = 0; i < len; i++) +// fields.put(fieldArray[i].getName(), fieldArray[i]); +// +// // search the variants from most specific to most general, since +// // the MessagesProperties.put method will mark assigned fields +// // to prevent them from being assigned twice +// final String[] variants = buildVariants(bundleName); +// for (int i = 0; i < variants.length; i++) { +// // loader is null if we're launched off the Java boot classpath +// final InputStream input = loader is null ? ClassLoader.getSystemResourceAsStream(variants[i]) : loader.getResourceAsStream(variants[i]); +// if (input is null) +// continue; +// try { +// final MessagesProperties properties = new MessagesProperties(fields, bundleName, isAccessible); +// properties.load(input); +// } catch (IOException e) { +// log(SEVERITY_ERROR, "Error loading " + variants[i], e); //$NON-NLS-1$ +// } finally { +// if (input !is null) +// try { +// input.close(); +// } catch (IOException e) { +// // ignore +// } +// } +// } +// computeMissingMessages(bundleName, clazz, fields, fieldArray, isAccessible); +// if (Debug.DEBUG_MESSAGE_BUNDLES) +// System.out.println("Time to load message bundle: " + bundleName + " was " + (System.currentTimeMillis() - start) + "ms."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ +// } +// +// /* +// * The method adds a log entry based on the error message and exception. +// * The output is written to the System.err. +// * +// * This method is only expected to be called if there is a problem in +// * the NLS mechanism. As a result, translation facility is not available +// * here and messages coming out of this log are generally not translated. +// * +// * @param severity - severity of the message (SEVERITY_ERROR or SEVERITY_WARNING) +// * @param message - message to log +// * @param e - exception to log +// */ +// static void log(int severity, String message, Exception e) { +// if (frameworkLog !is null) { +// frameworkLog.log(new FrameworkLogEntry("dwtx.osgi", severity, 1, message, 0, e, null)); //$NON-NLS-1$ +// return; +// } +// String statusMsg; +// switch (severity) { +// case SEVERITY_ERROR : +// statusMsg = "Error: "; //$NON-NLS-1$ +// break; +// case SEVERITY_WARNING : +// // intentionally fall through: +// default : +// statusMsg = "Warning: "; //$NON-NLS-1$ +// } +// if (message !is null) +// statusMsg += message; +// if (e !is null) +// statusMsg += ": " + e.getMessage(); //$NON-NLS-1$ +// System.err.println(statusMsg); +// if (e !is null) +// e.printStackTrace(); +// } +// +// /* +// * Class which sub-classes java.util.Properties and uses the #put method +// * to set field values rather than storing the values in the table. +// */ +// private static class MessagesProperties extends Properties { +// +// private static final int MOD_EXPECTED = Modifier.PUBLIC | Modifier.STATIC; +// private static final int MOD_MASK = MOD_EXPECTED | Modifier.FINAL; +// private static final long serialVersionUID = 1L; +// +// private final String bundleName; +// private final Map fields; +// private final bool isAccessible; +// +// public MessagesProperties(Map fieldMap, String bundleName, bool isAccessible) { +// super(); +// this.fields = fieldMap; +// this.bundleName = bundleName; +// this.isAccessible = isAccessible; +// } +// +// /* (non-Javadoc) +// * @see java.util.Hashtable#put(java.lang.Object, java.lang.Object) +// */ +// public synchronized Object put(Object key, Object value) { +// Object fieldObject = fields.put(key, ASSIGNED); +// // if already assigned, there is nothing to do +// if (fieldObject is ASSIGNED) +// return null; +// if (fieldObject is null) { +// final String msg = "NLS unused message: " + key + " in: " + bundleName;//$NON-NLS-1$ //$NON-NLS-2$ +// if (Debug.DEBUG_MESSAGE_BUNDLES) +// System.out.println(msg); +// log(SEVERITY_WARNING, msg, null); +// return null; +// } +// final Field field = (Field) fieldObject; +// //can only set value of public static non-final fields +// if ((field.getModifiers() & MOD_MASK) !is MOD_EXPECTED) +// return null; +// try { +// // Check to see if we are allowed to modify the field. If we aren't (for instance +// // if the class is not public) then change the accessible attribute of the field +// // before trying to set the value. +// if (!isAccessible) +// field.setAccessible(true); +// // Set the value into the field. We should never get an exception here because +// // we know we have a public static non-final field. If we do get an exception, silently +// // log it and continue. This means that the field will (most likely) be un-initialized and +// // will fail later in the code and if so then we will see both the NPE and this error. +// field.set(null, value); +// } catch (Exception e) { +// log(SEVERITY_ERROR, "Exception setting field value.", e); //$NON-NLS-1$ +// } +// return null; +// } +// } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/res/dwtx.core.internal.jobs.messages.properties Tue Aug 12 02:34:21 2008 +0200 @@ -0,0 +1,20 @@ +############################################################################### +# Copyright (c) 2000, 2005 IBM Corporation 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: +# IBM Corporation - initial API and implementation +############################################################################### +### Runtime jobs plugin messages + +### Job Manager and Locks +jobs_blocked0=The user operation is waiting for background work to complete. +jobs_blocked1=The user operation is waiting for \"{0}\" to complete. +jobs_internalError=An internal error occurred during: \"{0}\". +jobs_waitFamSub={0} work item(s) left. + +### metadata +meta_pluginProblems = Problems occurred when invoking code from plug-in: \"{0}\".