Bug 44423: workspace locked on OutOfMemoryError
diff --git a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/jobs/JobManager.java b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/jobs/JobManager.java
index f3dc511..90ba9b8 100644
--- a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/jobs/JobManager.java
+++ b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/jobs/JobManager.java
@@ -262,7 +262,9 @@
 		pool.shutdown();
 	}
 	/**
-	 * Indicates that a job was running, and has now finished.
+	 * 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, boolean notify) {
 		InternalJob blocked = null;
@@ -277,9 +279,9 @@
 			if (JobManager.DEBUG && notify)
 				JobManager.debug("Ending job: " + job); //$NON-NLS-1$
 			job.setResult(result);
-			changeState(job, Job.NONE);
 			job.setMonitor(null);
 			job.setThread(null);
+			changeState(job, Job.NONE);
 			blocked = job.previous();
 			job.setPrevious(null);
 
@@ -338,16 +340,21 @@
 	 * Returns the thread that owns the rule that is blocking this job from running, or 
 	 * null if there is none.
 	 */
-	public Thread getBlockingThread(InternalJob job) {
+	protected Thread getBlockingThread(InternalJob job) {
+		Thread result = null;
 		synchronized (lock) {
-			if (job.internalGetState() != InternalJob.BLOCKED)
-				return null;
-			//if this job is blocked, then the head of the queue is the job that is blocking it
-			InternalJob next = job.next();
-			while (next.next() != null)
-				next = next.next();
-			return next == null ? null : next.getThread();
+			if (job.internalGetState() == InternalJob.BLOCKED) {
+				//if this job is blocked, then the head of the queue is the job that is blocking it
+				InternalJob next = job.next();
+				while (next.next() != null)
+					next = next.next();
+				result = next == null ? null : next.getThread();
+			}
 		}
+		//if nobody is blocking this job, kick start the system by creating another worker
+		if (result == null)
+			pool.jobQueued(job);
+		return result;
 	}
 	public LockManager getLockManager() {
 		return lockManager;
diff --git a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/jobs/Worker.java b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/jobs/Worker.java
index a248c43..7ae4d25 100644
--- a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/jobs/Worker.java
+++ b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/jobs/Worker.java
@@ -69,15 +69,16 @@
 				} finally {
 					//clear interrupted state for this thread
 					Thread.interrupted();
+					pool.endJob(currentJob, result);
 					if ((result.getSeverity() & (IStatus.ERROR | IStatus.WARNING)) != 0)
 						log(result);
-					pool.endJob(currentJob, result);
 					currentJob = null;
 				}
 			}
 		} catch (Throwable t) {
 			t.printStackTrace();
 		} finally {
+			currentJob = null;
 			pool.endWorker(this);
 		}
 	}
diff --git a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/jobs/WorkerPool.java b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/jobs/WorkerPool.java
index 4cf7e8d..40a46c9 100644
--- a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/jobs/WorkerPool.java
+++ b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/jobs/WorkerPool.java
@@ -9,8 +9,6 @@
  **********************************************************************/
 package org.eclipse.core.internal.jobs;
 
-import java.util.ArrayList;
-
 import org.eclipse.core.internal.runtime.InternalPlatform;
 import org.eclipse.core.runtime.*;
 import org.eclipse.core.runtime.jobs.Job;
@@ -26,10 +24,25 @@
  * turn use locks.
  */
 class WorkerPool {
+	/**
+	 * There will always be at least MIN_THREADS workers in the pool.
+	 */
 	private static final int MIN_THREADS = 1;
 	private static final int MAX_THREADS = 25;
+	/**
+	 * Threads not used by their best before timestamp are destroyed. 
+	 */
+	private static final int BEST_BEFORE = 60000;
+	
 	private boolean running = false;
-	private ArrayList threads = new ArrayList();
+	/**
+	 * The living set of workers in this pool.
+	 */
+	private Worker[] threads = new Worker[10];
+	/**
+	 * The number of workers in the threads array
+	 */
+	private int numThreads = 0;
 	/**
 	 * The number of threads that are currently sleeping 
 	 */
@@ -39,10 +52,6 @@
 	 * thread is just doing house cleaning (notifying listeners, etc).
 	 */
 	private int busyThreads = 0;
-	/**
-	 * Threads not used by their best before timestamp are destroyed. 
-	 */
-	private static final int BEST_BEFORE = 60000;
 
 	private JobManager manager;
 
@@ -50,17 +59,41 @@
 		this.manager = manager;
 		running = true;
 	}
+	/**
+	 * Adds a worker to the list of workers.
+	 */
+	private synchronized void add(Worker worker) {
+		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 synchronized void decrementBusyThreads() {
 		busyThreads--;
 	}
+
+	/**
+	 * 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();
-		manager.endJob(job, result, true);
-		//remove any locks this thread may be owning
-		manager.getLockManager().removeAllLocks(Thread.currentThread());
+		try {
+			manager.endJob(job, result, true);
+		} finally {
+			//remove any locks this thread may be owning
+			manager.getLockManager().removeAllLocks(Thread.currentThread());
+		}
 	}
+	/**
+	 * 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 synchronized void endWorker(Worker worker) {
-		if (threads.remove(worker) && JobManager.DEBUG)
+		if (remove(worker) && JobManager.DEBUG)
 			JobManager.debug("worker removed from pool: " + worker); //$NON-NLS-1$
 	}
 	private synchronized void incrementBusyThreads() {
@@ -78,12 +111,12 @@
 			notify();
 			return;
 		}
-		int threadCount = threads.size();
+		int threadCount = numThreads;
 		//create a thread if all threads are busy and we're under the max size
 		//if the job is high priority, we start a thread no matter what
 		if (busyThreads >= threadCount && (threadCount < MAX_THREADS || (job != null && job.getPriority() == Job.INTERACTIVE))) {
 			Worker worker = new Worker(this);
-			threads.add(worker);
+			add(worker);
 			if (JobManager.DEBUG)
 				JobManager.debug("worker added to pool: " + worker); //$NON-NLS-1$
 			worker.start();
@@ -93,6 +126,20 @@
 			InternalPlatform.log(new Status(IStatus.ERROR, Platform.PI_RUNTIME, 1, msg, null));
 		}
 	}
+	/**
+	 * Remove a worker thread from our list.
+	 * @return true if a worker was removed, and false otherwise.
+	 */
+	private boolean remove(Worker worker) {
+		for (int i = 0; i < threads.length; i++) {
+			if (threads[i] == worker) {
+				System.arraycopy(threads, i+1, threads, i, numThreads - i - 1);
+				threads[--numThreads] = null;
+				return true;
+			}
+		}
+		return false;
+	}
 	protected synchronized void shutdown() {
 		running = false;
 		notifyAll();
@@ -119,7 +166,7 @@
 	protected InternalJob startJob(Worker worker) {
 		//if we're above capacity, kill the thread
 		synchronized (this) {
-			if (!running || threads.size() > MAX_THREADS) {
+			if (!running || numThreads > MAX_THREADS) {
 				//must remove the worker immediately to prevent all threads from expiring
 				endWorker(worker);
 				return null;
@@ -136,7 +183,7 @@
 			//if we were already idle, and there are still no new jobs, then
 			// the thread can expire
 			synchronized (this) {
-				if (job == null && (System.currentTimeMillis() - idleStart > BEST_BEFORE) && (threads.size() - busyThreads) > MIN_THREADS) {
+				if (job == 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;