KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > core > internal > jobs > JobManager


1 /*******************************************************************************
2  * Copyright (c) 2003, 2007 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  *******************************************************************************/

11 package org.eclipse.core.internal.jobs;
12
13 //don't use ICU because this is used for debugging only (see bug 135785)
14
import java.text.*;
15 import java.util.*;
16 import org.eclipse.core.internal.runtime.RuntimeLog;
17 import org.eclipse.core.runtime.*;
18 import org.eclipse.core.runtime.jobs.*;
19 import org.eclipse.osgi.util.NLS;
20
21 /**
22  * Implementation of API type IJobManager
23  *
24  * Implementation note: all the data structures of this class are protected
25  * by a single lock object held as a private field in this class. The JobManager
26  * instance itself is not used because this class is publicly reachable, and third
27  * party clients may try to synchronize on it.
28  *
29  * The WorkerPool class uses its own monitor for synchronizing its data
30  * structures. To avoid deadlock between the two classes, the JobManager
31  * must NEVER call the worker pool while its own monitor is held.
32  */

33 public class JobManager implements IJobManager {
34
35     /**
36      * The unique identifier constant of this plug-in.
37      */

38     public static final String JavaDoc PI_JOBS = "org.eclipse.core.jobs"; //$NON-NLS-1$
39

40     /**
41      * Status code constant indicating an error occurred while running a plug-in.
42      * For backward compatibility with Platform.PLUGIN_ERROR left at (value = 2).
43      */

44     public static final int PLUGIN_ERROR = 2;
45
46     private static final String JavaDoc OPTION_DEADLOCK_ERROR = PI_JOBS + "/jobs/errorondeadlock"; //$NON-NLS-1$
47
private static final String JavaDoc OPTION_DEBUG_BEGIN_END = PI_JOBS + "/jobs/beginend"; //$NON-NLS-1$
48
private static final String JavaDoc OPTION_DEBUG_JOBS = PI_JOBS + "/jobs"; //$NON-NLS-1$
49
private static final String JavaDoc OPTION_DEBUG_JOBS_TIMING = PI_JOBS + "/jobs/timing"; //$NON-NLS-1$
50
private static final String JavaDoc OPTION_LOCKS = PI_JOBS + "/jobs/locks"; //$NON-NLS-1$
51
private static final String JavaDoc OPTION_SHUTDOWN = PI_JOBS + "/jobs/shutdown"; //$NON-NLS-1$
52

53     static boolean DEBUG = false;
54     static boolean DEBUG_BEGIN_END = false;
55     static boolean DEBUG_DEADLOCK = false;
56     static boolean DEBUG_LOCKS = false;
57     static boolean DEBUG_TIMING = false;
58     static boolean DEBUG_SHUTDOWN = false;
59     private static DateFormat DEBUG_FORMAT;
60
61     /**
62      * The singleton job manager instance. It must be a singleton because
63      * all job instances maintain a reference (as an optimization) and have no way
64      * of updating it.
65      */

66     private static JobManager instance;
67     /**
68      * Scheduling rule used for validation of client-defined rules.
69      */

70     private static final ISchedulingRule nullRule = new ISchedulingRule() {
71         public boolean contains(ISchedulingRule rule) {
72             return rule == this;
73         }
74
75         public boolean isConflicting(ISchedulingRule rule) {
76             return rule == this;
77         }
78     };
79
80     /**
81      * True if this manager is active, and false otherwise. A job manager
82      * starts out active, and becomes inactive if it has been shutdown
83      * and not restarted.
84      */

85     private volatile boolean active = true;
86
87     final ImplicitJobs implicitJobs = new ImplicitJobs(this);
88
89     private final JobListeners jobListeners = new JobListeners();
90
91     /**
92      * The lock for synchronizing all activity in the job manager. To avoid deadlock,
93      * this lock must never be held for extended periods, and must never be
94      * held while third party code is being called.
95      */

96     private final Object JavaDoc lock = new Object JavaDoc();
97
98     private final LockManager lockManager = new LockManager();
99
100     /**
101      * The pool of worker threads.
102      */

103     private WorkerPool pool;
104
105     private ProgressProvider progressProvider = null;
106     /**
107      * Jobs that are currently running. Should only be modified from changeState
108      */

109     private final HashSet running;
110
111     /**
112      * Jobs that are sleeping. Some sleeping jobs are scheduled to wake
113      * up at a given start time, while others will sleep indefinitely until woken.
114      * Should only be modified from changeState
115      */

116     private final JobQueue sleeping;
117     /**
118      * True if this manager has been suspended, and false otherwise. A job manager
119      * starts out not suspended, and becomes suspended when <code>suspend</code>
120      * is invoked. Once suspended, no jobs will start running until <code>resume</code>
121      * is called.
122      */

123     private boolean suspended = false;
124
125     /**
126      * jobs that are waiting to be run. Should only be modified from changeState
127      */

128     private final JobQueue waiting;
129
130     public static void debug(String JavaDoc msg) {
131         StringBuffer JavaDoc msgBuf = new StringBuffer JavaDoc(msg.length() + 40);
132         if (DEBUG_TIMING) {
133             //lazy initialize to avoid overhead when not debugging
134
if (DEBUG_FORMAT == null)
135                 DEBUG_FORMAT = new SimpleDateFormat("HH:mm:ss.SSS"); //$NON-NLS-1$
136
DEBUG_FORMAT.format(new Date(), msgBuf, new FieldPosition(0));
137             msgBuf.append('-');
138         }
139         msgBuf.append('[').append(Thread.currentThread()).append(']').append(msg);
140         System.out.println(msgBuf.toString());
141     }
142
143     /**
144      * Returns the job manager singleton. For internal use only.
145      */

146     static synchronized JobManager getInstance() {
147         if (instance == null)
148             new JobManager();
149         return instance;
150     }
151
152     /**
153      * For debugging purposes only
154      */

155     private static String JavaDoc printJobName(Job job) {
156         if (job instanceof ThreadJob) {
157             Job realJob = ((ThreadJob) job).realJob;
158             if (realJob != null)
159                 return realJob.getClass().getName();
160             return "ThreadJob on rule: " + job.getRule(); //$NON-NLS-1$
161
}
162         return job.getClass().getName();
163     }
164
165     /**
166      * For debugging purposes only
167      */

168     public static String JavaDoc printState(int state) {
169         switch (state) {
170             case Job.NONE :
171                 return "NONE"; //$NON-NLS-1$
172
case Job.WAITING :
173                 return "WAITING"; //$NON-NLS-1$
174
case Job.SLEEPING :
175                 return "SLEEPING"; //$NON-NLS-1$
176
case Job.RUNNING :
177                 return "RUNNING"; //$NON-NLS-1$
178
case InternalJob.BLOCKED :
179                 return "BLOCKED"; //$NON-NLS-1$
180
case InternalJob.ABOUT_TO_RUN :
181                 return "ABOUT_TO_RUN"; //$NON-NLS-1$
182
case InternalJob.ABOUT_TO_SCHEDULE :
183                 return "ABOUT_TO_SCHEDULE";//$NON-NLS-1$
184
}
185         return "UNKNOWN"; //$NON-NLS-1$
186
}
187
188     /**
189      * Note that although this method is not API, clients have historically used
190      * it to force jobs shutdown in cases where OSGi shutdown does not occur.
191      * For this reason, this method should be considered near-API and should not
192      * be changed if at all possible.
193      */

194     public static void shutdown() {
195         if (instance != null) {
196             instance.doShutdown();
197             instance = null;
198         }
199     }
200
201     private JobManager() {
202         instance = this;
203         initDebugOptions();
204         synchronized (lock) {
205             waiting = new JobQueue(false);
206             sleeping = new JobQueue(true);
207             running = new HashSet(10);
208             pool = new WorkerPool(this);
209         }
210         pool.setDaemon(JobOSGiUtils.getDefault().useDaemonThreads());
211     }
212
213     /* (non-Javadoc)
214      * @see org.eclipse.core.runtime.jobs.IJobManager#addJobListener(org.eclipse.core.runtime.jobs.IJobChangeListener)
215      */

216     public void addJobChangeListener(IJobChangeListener listener) {
217         jobListeners.add(listener);
218     }
219
220     /* (non-Javadoc)
221      * @see org.eclipse.core.runtime.jobs.IJobManager#beginRule(org.eclipse.core.runtime.jobs.ISchedulingRule, org.eclipse.core.runtime.IProgressMonitor)
222      */

223     public void beginRule(ISchedulingRule rule, IProgressMonitor monitor) {
224         validateRule(rule);
225         implicitJobs.begin(rule, monitorFor(monitor), false);
226     }
227
228     /**
229      * Cancels a job
230      */

231     protected boolean cancel(InternalJob job) {
232         IProgressMonitor monitor = null;
233         synchronized (lock) {
234             switch (job.getState()) {
235                 case Job.NONE :
236                     return true;
237                 case Job.RUNNING :
238                     //cannot cancel a job that has already started (as opposed to ABOUT_TO_RUN)
239
if (job.internalGetState() == Job.RUNNING) {
240                         monitor = job.getProgressMonitor();
241                         break;
242                     }
243                     //signal that the job should be canceled before it gets a chance to run
244
job.setAboutToRunCanceled(true);
245                     return true;
246                 default :
247                     changeState(job, Job.NONE);
248             }
249         }
250         //call monitor outside sync block
251
if (monitor != null) {
252             if (!monitor.isCanceled()) {
253                 monitor.setCanceled(true);
254                 job.canceling();
255             }
256             return false;
257         }
258         //only notify listeners if the job was waiting or sleeping
259
jobListeners.done((Job) job, Status.CANCEL_STATUS, false);
260         return true;
261     }
262
263     /* (non-Javadoc)
264      * @see org.eclipse.core.runtime.jobs.IJobManager#cancel(java.lang.String)
265      */

266     public void cancel(Object JavaDoc family) {
267         //don't synchronize because cancel calls listeners
268
for (Iterator it = select(family).iterator(); it.hasNext();)
269             cancel((Job) it.next());
270     }
271
272     /**
273      * Atomically updates the state of a job, adding or removing from the
274      * necessary queues or sets.
275      */

276     private void changeState(InternalJob job, int newState) {
277         boolean blockedJobs = false;
278         synchronized (lock) {
279             int oldState = job.internalGetState();
280             switch (oldState) {
281                 case Job.NONE :
282                 case InternalJob.ABOUT_TO_SCHEDULE :
283                     break;
284                 case InternalJob.BLOCKED :
285                     //remove this job from the linked list of blocked jobs
286
job.remove();
287                     break;
288                 case Job.WAITING :
289                     try {
290                         waiting.remove(job);
291                     } catch (RuntimeException JavaDoc e) {
292                         Assert.isLegal(false, "Tried to remove a job that wasn't in the queue"); //$NON-NLS-1$
293
}
294                     break;
295                 case Job.SLEEPING :
296                     try {
297                         sleeping.remove(job);
298                     } catch (RuntimeException JavaDoc e) {
299                         Assert.isLegal(false, "Tried to remove a job that wasn't in the queue"); //$NON-NLS-1$
300
}
301                     break;
302                 case Job.RUNNING :
303                 case InternalJob.ABOUT_TO_RUN :
304                     running.remove(job);
305                     //add any blocked jobs back to the wait queue
306
InternalJob blocked = job.previous();
307                     job.remove();
308                     blockedJobs = blocked != null;
309                     while (blocked != null) {
310                         InternalJob previous = blocked.previous();
311                         changeState(blocked, Job.WAITING);
312                         blocked = previous;
313                     }
314                     break;
315                 default :
316                     Assert.isLegal(false, "Invalid job state: " + job + ", state: " + oldState); //$NON-NLS-1$ //$NON-NLS-2$
317
}
318             job.internalSetState(newState);
319             switch (newState) {
320                 case Job.NONE :
321                     job.setStartTime(InternalJob.T_NONE);
322                 case InternalJob.BLOCKED :
323                     break;
324                 case Job.WAITING :
325                     waiting.enqueue(job);
326                     break;
327                 case Job.SLEEPING :
328                     try {
329                         sleeping.enqueue(job);
330                     } catch (RuntimeException JavaDoc e) {
331                         throw new RuntimeException JavaDoc("Error changing from state: " + oldState); //$NON-NLS-1$
332
}
333                     break;
334                 case Job.RUNNING :
335                 case InternalJob.ABOUT_TO_RUN :
336                     job.setStartTime(InternalJob.T_NONE);
337                     running.add(job);
338                     break;
339                 case InternalJob.ABOUT_TO_SCHEDULE :
340                     break;
341                 default :
342                     Assert.isLegal(false, "Invalid job state: " + job + ", state: " + newState); //$NON-NLS-1$ //$NON-NLS-2$
343
}
344         }
345         //notify queue outside sync block
346
if (blockedJobs)
347             pool.jobQueued();
348     }
349
350     /**
351      * Returns a new progress monitor for this job, belonging to the given
352      * progress group. Returns null if it is not a valid time to set the job's group.
353      */

354     protected IProgressMonitor createMonitor(InternalJob job, IProgressMonitor group, int ticks) {
355         synchronized (lock) {
356             //group must be set before the job is scheduled
357
//this includes the ABOUT_TO_SCHEDULE state, during which it is still
358
//valid to set the progress monitor
359
if (job.getState() != Job.NONE)
360                 return null;
361             IProgressMonitor monitor = null;
362             if (progressProvider != null)
363                 monitor = progressProvider.createMonitor((Job) job, group, ticks);
364             if (monitor == null)
365                 monitor = new NullProgressMonitor();
366             return monitor;
367         }
368     }
369
370     /**
371      * Returns a new progress monitor for this job. Never returns null.
372      */

373     private IProgressMonitor createMonitor(Job job) {
374         IProgressMonitor monitor = null;
375         if (progressProvider != null)
376             monitor = progressProvider.createMonitor(job);
377         if (monitor == null)
378             monitor = new NullProgressMonitor();
379         return monitor;
380     }
381
382     /* (non-Javadoc)
383      * @see org.eclipse.core.runtime.jobs.IJobManager#createProgressGroup()
384      */

385     public IProgressMonitor createProgressGroup() {
386         if (progressProvider != null)
387             return progressProvider.createProgressGroup();
388         return new NullProgressMonitor();
389     }
390
391     /* (non-Javadoc)
392      * @see org.eclipse.core.runtime.jobs.IJobManager#currentJob()
393      */

394     public Job currentJob() {
395         Thread JavaDoc current = Thread.currentThread();
396         if (current instanceof Worker)
397             return ((Worker) current).currentJob();
398         synchronized (lock) {
399             for (Iterator it = running.iterator(); it.hasNext();) {
400                 Job job = (Job) it.next();
401                 if (job.getThread() == current)
402                     return job;
403             }
404         }
405         return null;
406     }
407
408     /**
409      * Returns the delay in milliseconds that a job with a given priority can
410      * tolerate waiting.
411      */

412     private long delayFor(int priority) {
413         //these values may need to be tweaked based on machine speed
414
switch (priority) {
415             case Job.INTERACTIVE :
416                 return 0L;
417             case Job.SHORT :
418                 return 50L;
419             case Job.LONG :
420                 return 100L;
421             case Job.BUILD :
422                 return 500L;
423             case Job.DECORATE :
424                 return 1000L;
425             default :
426                 Assert.isTrue(false, "Job has invalid priority: " + priority); //$NON-NLS-1$
427
return 0;
428         }
429     }
430
431     /**
432      * Performs the scheduling of a job. Does not perform any notifications.
433      */

434     private void doSchedule(InternalJob job, long delay) {
435         synchronized (lock) {
436             //if it's a decoration job, don't run it right now if the system is busy
437
if (job.getPriority() == Job.DECORATE) {
438                 long minDelay = running.size() * 100;
439                 delay = Math.max(delay, minDelay);
440             }
441             if (delay > 0) {
442                 job.setStartTime(System.currentTimeMillis() + delay);
443                 changeState(job, Job.SLEEPING);
444             } else {
445                 job.setStartTime(System.currentTimeMillis() + delayFor(job.getPriority()));
446                 changeState(job, Job.WAITING);
447             }
448         }
449     }
450
451     /**
452      * Shuts down the job manager. Currently running jobs will be told
453      * to stop, but worker threads may still continue processing.
454      * (note: This implemented IJobManager.shutdown which was removed
455      * due to problems caused by premature shutdown)
456      */

457     private void doShutdown() {
458         Job[] toCancel = null;
459         synchronized (lock) {
460             if (active) {
461                 active = false;
462                 //cancel all running jobs
463
toCancel = (Job[]) running.toArray(new Job[running.size()]);
464                 //clean up
465
sleeping.clear();
466                 waiting.clear();
467                 running.clear();
468             }
469         }
470
471         // Give running jobs a chance to finish. Wait 0.1 seconds for up to 3 times.
472
if (toCancel != null && toCancel.length > 0) {
473             for (int i = 0; i < toCancel.length; i++) {
474                 cancel(toCancel[i]); // cancel jobs outside sync block to avoid deadlock
475
}
476
477             for (int waitAttempts = 0; waitAttempts < 3; waitAttempts++) {
478                 Thread.yield();
479                 synchronized (lock) {
480                     if (running.isEmpty())
481                         break;
482                 }
483                 if (DEBUG_SHUTDOWN) {
484                     JobManager.debug("Shutdown - job wait cycle #" + (waitAttempts + 1)); //$NON-NLS-1$
485
Job[] stillRunning = null;
486                     synchronized (lock) {
487                         stillRunning = (Job[]) running.toArray(new Job[running.size()]);
488                     }
489                     if (stillRunning != null) {
490                         for (int j = 0; j < stillRunning.length; j++) {
491                             JobManager.debug("\tJob: " + printJobName(stillRunning[j])); //$NON-NLS-1$
492
}
493                     }
494                 }
495                 try {
496                     Thread.sleep(100);
497                 } catch (InterruptedException JavaDoc e) {
498                     //ignore
499
}
500                 Thread.yield();
501             }
502
503             synchronized (lock) { // retrieve list of the jobs that are still running
504
toCancel = (Job[]) running.toArray(new Job[running.size()]);
505             }
506         }
507
508         if (toCancel != null) {
509             for (int i = 0; i < toCancel.length; i++) {
510                 String JavaDoc jobName = printJobName(toCancel[i]);
511                 //this doesn't need to be translated because it's just being logged
512
String JavaDoc msg = "Job found still running after platform shutdown. Jobs should be canceled by the plugin that scheduled them during shutdown: " + jobName; //$NON-NLS-1$
513
RuntimeLog.log(new Status(IStatus.WARNING, JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, msg, null));
514
515                 // TODO the RuntimeLog.log in its current implementation won't produce a log
516
// during this stage of shutdown. For now add a standard error output.
517
// One the logging story is improved, the System.err output below can be removed:
518
System.err.println(msg);
519             }
520         }
521
522         pool.shutdown();
523     }
524
525     /**
526      * Indicates that a job was running, and has now finished. Note that this method
527      * can be called under OutOfMemoryError conditions and thus must be paranoid
528      * about allocating objects.
529      */

530     protected void endJob(InternalJob job, IStatus result, boolean notify) {
531         long rescheduleDelay = InternalJob.T_NONE;
532         synchronized (lock) {
533             //if the job is finishing asynchronously, there is nothing more to do for now
534
if (result == Job.ASYNC_FINISH)
535                 return;
536             //if job is not known then it cannot be done
537
if (job.getState() == Job.NONE)
538                 return;
539             if (JobManager.DEBUG && notify)
540                 JobManager.debug("Ending job: " + job); //$NON-NLS-1$
541
job.setResult(result);
542             job.setProgressMonitor(null);
543             job.setThread(null);
544             rescheduleDelay = job.getStartTime();
545             changeState(job, Job.NONE);
546         }
547         //notify listeners outside sync block
548
final boolean reschedule = active && rescheduleDelay > InternalJob.T_NONE && job.shouldSchedule();
549         if (notify)
550             jobListeners.done((Job) job, result, reschedule);
551         //reschedule the job if requested and we are still active
552
if (reschedule)
553             schedule(job, rescheduleDelay, reschedule);
554     }
555
556     /* (non-Javadoc)
557      * @see org.eclipse.core.runtime.jobs.IJobManager#endRule(org.eclipse.core.runtime.jobs.ISchedulingRule)
558      */

559     public void endRule(ISchedulingRule rule) {
560         implicitJobs.end(rule, false);
561     }
562
563     /* (non-Javadoc)
564      * @see org.eclipse.core.runtime.jobs.IJobManager#find(java.lang.String)
565      */

566     public Job[] find(Object JavaDoc family) {
567         List members = select(family);
568         return (Job[]) members.toArray(new Job[members.size()]);
569     }
570
571     /**
572      * Returns a running or blocked job whose scheduling rule conflicts with the
573      * scheduling rule of the given waiting job. Returns null if there are no
574      * conflicting jobs. A job can only run if there are no running jobs and no blocked
575      * jobs whose scheduling rule conflicts with its rule.
576      */

577     protected InternalJob findBlockingJob(InternalJob waitingJob) {
578         if (waitingJob.getRule() == null)
579             return null;
580         synchronized (lock) {
581             if (running.isEmpty())
582                 return null;
583             //check the running jobs
584
boolean hasBlockedJobs = false;
585             for (Iterator it = running.iterator(); it.hasNext();) {
586                 InternalJob job = (InternalJob) it.next();
587                 if (waitingJob.isConflicting(job))
588                     return job;
589                 if (!hasBlockedJobs)
590                     hasBlockedJobs = job.previous() != null;
591             }
592             //there are no blocked jobs, so we are done
593
if (!hasBlockedJobs)
594                 return null;
595             //check all jobs blocked by running jobs
596
for (Iterator it = running.iterator(); it.hasNext();) {
597                 InternalJob job = (InternalJob) it.next();
598                 while (true) {
599                     job = job.previous();
600                     if (job == null)
601                         break;
602                     if (waitingJob.isConflicting(job))
603                         return job;
604                 }
605             }
606         }
607         return null;
608     }
609
610     public LockManager getLockManager() {
611         return lockManager;
612     }
613
614     private void initDebugOptions() {
615         DEBUG = JobOSGiUtils.getDefault().getBooleanDebugOption(OPTION_DEBUG_JOBS, false);
616         DEBUG_BEGIN_END = JobOSGiUtils.getDefault().getBooleanDebugOption(OPTION_DEBUG_BEGIN_END, false);
617         DEBUG_DEADLOCK = JobOSGiUtils.getDefault().getBooleanDebugOption(OPTION_DEADLOCK_ERROR, false);
618         DEBUG_LOCKS = JobOSGiUtils.getDefault().getBooleanDebugOption(OPTION_LOCKS, false);
619         DEBUG_TIMING = JobOSGiUtils.getDefault().getBooleanDebugOption(OPTION_DEBUG_JOBS_TIMING, false);
620         DEBUG_SHUTDOWN = JobOSGiUtils.getDefault().getBooleanDebugOption(OPTION_SHUTDOWN, false);
621     }
622
623     /**
624      * Returns whether the job manager is active (has not been shutdown).
625      */

626     protected boolean isActive() {
627         return active;
628     }
629
630     /**
631      * Returns true if the given job is blocking the execution of a non-system
632      * job.
633      */

634     protected boolean isBlocking(InternalJob runningJob) {
635         synchronized (lock) {
636             // if this job isn't running, it can't be blocking anyone
637
if (runningJob.getState() != Job.RUNNING)
638                 return false;
639             // if any job is queued behind this one, it is blocked by it
640
InternalJob previous = runningJob.previous();
641             while (previous != null) {
642                 // ignore jobs of lower priority (higher priority value means lower priority)
643
if (previous.getPriority() < runningJob.getPriority()) {
644                     if (!previous.isSystem())
645                         return true;
646                     // implicit jobs should interrupt unless they act on behalf of system jobs
647
if (previous instanceof ThreadJob && ((ThreadJob) previous).shouldInterrupt())
648                         return true;
649                 }
650                 previous = previous.previous();
651             }
652             // none found
653
return false;
654         }
655     }
656
657     /* (non-Javadoc)
658      * @see org.eclipse.core.runtime.jobs.IJobManager#isIdle()
659      */

660     public boolean isIdle() {
661         synchronized (lock) {
662             return running.isEmpty() && waiting.isEmpty();
663         }
664     }
665
666     /* (non-Javadoc)
667      * @see org.eclipse.core.runtime.jobs.Job#job(org.eclipse.core.runtime.jobs.Job)
668      */

669     protected void join(InternalJob job) {
670         final IJobChangeListener listener;
671         final Semaphore barrier;
672         synchronized (lock) {
673             int state = job.getState();
674             if (state == Job.NONE)
675                 return;
676             //don't join a waiting or sleeping job when suspended (deadlock risk)
677
if (suspended && state != Job.RUNNING)
678                 return;
679             //it's an error for a job to join itself
680
if (state == Job.RUNNING && job.getThread() == Thread.currentThread())
681                 throw new IllegalStateException JavaDoc("Job attempted to join itself"); //$NON-NLS-1$
682
//the semaphore will be released when the job is done
683
barrier = new Semaphore(null);
684             listener = new JobChangeAdapter() {
685                 public void done(IJobChangeEvent event) {
686                     barrier.release();
687                 }
688             };
689             job.addJobChangeListener(listener);
690             //compute set of all jobs that must run before this one
691
//add a listener that removes jobs from the blocking set when they finish
692
}
693         //wait until listener notifies this thread.
694
try {
695             while (true) {
696                 //notify hook to service pending syncExecs before falling asleep
697
lockManager.aboutToWait(job.getThread());
698                 try {
699                     if (barrier.acquire(Long.MAX_VALUE))
700                         break;
701                 } catch (InterruptedException JavaDoc e) {
702                     //loop and keep trying
703
}
704             }
705         } finally {
706             lockManager.aboutToRelease();
707             job.removeJobChangeListener(listener);
708         }
709     }
710
711     /* (non-Javadoc)
712      * @see IJobManager#join(String, IProgressMonitor)
713      */

714     public void join(final Object JavaDoc family, IProgressMonitor monitor) throws InterruptedException JavaDoc, OperationCanceledException {
715         monitor = monitorFor(monitor);
716         IJobChangeListener listener = null;
717         final Set jobs;
718         int jobCount;
719         Job blocking = null;
720         synchronized (lock) {
721             //don't join a waiting or sleeping job when suspended (deadlock risk)
722
int states = suspended ? Job.RUNNING : Job.RUNNING | Job.WAITING | Job.SLEEPING;
723             jobs = Collections.synchronizedSet(new HashSet(select(family, states)));
724             jobCount = jobs.size();
725             if (jobCount > 0) {
726                 //if there is only one blocking job, use it in the blockage callback below
727
if (jobCount == 1)
728                     blocking = (Job) jobs.iterator().next();
729                 listener = new JobChangeAdapter() {
730                     public void done(IJobChangeEvent event) {
731                         //don't remove from list if job is being rescheduled
732
if (!((JobChangeEvent) event).reschedule)
733                             jobs.remove(event.getJob());
734                     }
735
736                     //update the list of jobs if new ones are added during the join
737
public void scheduled(IJobChangeEvent event) {
738                         //don't add to list if job is being rescheduled
739
if (((JobChangeEvent) event).reschedule)
740                             return;
741                         Job job = event.getJob();
742                         if (job.belongsTo(family))
743                             jobs.add(job);
744                     }
745                 };
746                 addJobChangeListener(listener);
747             }
748         }
749         if (jobCount == 0) {
750             //use up the monitor outside synchronized block because monitors call untrusted code
751
monitor.beginTask(JobMessages.jobs_blocked0, 1);
752             monitor.done();
753             return;
754         }
755         //spin until all jobs are completed
756
try {
757             monitor.beginTask(JobMessages.jobs_blocked0, jobCount);
758             monitor.subTask(NLS.bind(JobMessages.jobs_waitFamSub, Integer.toString(jobCount)));
759             reportBlocked(monitor, blocking);
760             int jobsLeft;
761             int reportedWorkDone = 0;
762             while ((jobsLeft = jobs.size()) > 0) {
763                 //don't let there be negative work done if new jobs have
764
//been added since the join began
765
int actualWorkDone = Math.max(0, jobCount - jobsLeft);
766                 if (reportedWorkDone < actualWorkDone) {
767                     monitor.worked(actualWorkDone - reportedWorkDone);
768                     reportedWorkDone = actualWorkDone;
769                     monitor.subTask(NLS.bind(JobMessages.jobs_waitFamSub, Integer.toString(jobsLeft)));
770                 }
771                 if (Thread.interrupted())
772                     throw new InterruptedException JavaDoc();
773                 if (monitor.isCanceled())
774                     throw new OperationCanceledException();
775                 //notify hook to service pending syncExecs before falling asleep
776
lockManager.aboutToWait(null);
777                 Thread.sleep(100);
778             }
779         } finally {
780             lockManager.aboutToRelease();
781             removeJobChangeListener(listener);
782             reportUnblocked(monitor);
783             monitor.done();
784         }
785     }
786
787     /**
788      * Returns a non-null progress monitor instance. If the monitor is null,
789      * returns the default monitor supplied by the progress provider, or a
790      * NullProgressMonitor if no default monitor is available.
791      */

792     private IProgressMonitor monitorFor(IProgressMonitor monitor) {
793         if (monitor == null || (monitor instanceof NullProgressMonitor)) {
794             if (progressProvider != null) {
795                 try {
796                     monitor = progressProvider.getDefaultMonitor();
797                 } catch (Exception JavaDoc e) {
798                     String JavaDoc msg = NLS.bind(JobMessages.meta_pluginProblems, JobManager.PI_JOBS);
799                     RuntimeLog.log(new Status(IStatus.ERROR, JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, msg, e));
800                 }
801             }
802         }
803
804         if (monitor == null)
805             return new NullProgressMonitor();
806         return monitor;
807     }
808
809     /* (non-Javadoc)
810      * @see IJobManager#newLock(java.lang.String)
811      */

812     public ILock newLock() {
813         return lockManager.newLock();
814     }
815
816     /**
817      * Removes and returns the first waiting job in the queue. Returns null if there
818      * are no items waiting in the queue. If an item is removed from the queue,
819      * it is moved to the running jobs list.
820      */

821     private Job nextJob() {
822         synchronized (lock) {
823             //do nothing if the job manager is suspended
824
if (suspended)
825                 return null;
826             //tickle the sleep queue to see if anyone wakes up
827
long now = System.currentTimeMillis();
828             InternalJob job = sleeping.peek();
829             while (job != null && job.getStartTime() < now) {
830                 job.setStartTime(now + delayFor(job.getPriority()));
831                 changeState(job, Job.WAITING);
832                 job = sleeping.peek();
833             }
834             //process the wait queue until we find a job whose rules are satisfied.
835
while ((job = waiting.peek()) != null) {
836                 InternalJob blocker = findBlockingJob(job);
837                 if (blocker == null)
838                     break;
839                 //queue this job after the job that's blocking it
840
changeState(job, InternalJob.BLOCKED);
841                 //assert job does not already belong to some other data structure
842
Assert.isTrue(job.next() == null);
843                 Assert.isTrue(job.previous() == null);
844                 blocker.addLast(job);
845             }
846             //the job to run must be in the running list before we exit
847
//the sync block, otherwise two jobs with conflicting rules could start at once
848
if (job != null) {
849                 changeState(job, InternalJob.ABOUT_TO_RUN);
850                 if (JobManager.DEBUG)
851                     JobManager.debug("Starting job: " + job); //$NON-NLS-1$
852
}
853             return (Job) job;
854         }
855     }
856
857     /* (non-Javadoc)
858      * @see org.eclipse.core.runtime.jobs.IJobManager#removeJobListener(org.eclipse.core.runtime.jobs.IJobChangeListener)
859      */

860     public void removeJobChangeListener(IJobChangeListener listener) {
861         jobListeners.remove(listener);
862     }
863
864     /**
865      * Report to the progress monitor that this thread is blocked, supplying
866      * an information message, and if possible the job that is causing the blockage.
867      * Important: An invocation of this method MUST be followed eventually be
868      * an invocation of reportUnblocked.
869      * @param monitor The monitor to report blocking to
870      * @param blockingJob The job that is blocking this thread, or <code>null</code>
871      * @see #reportUnblocked
872      */

873     final void reportBlocked(IProgressMonitor monitor, InternalJob blockingJob) {
874         if (!(monitor instanceof IProgressMonitorWithBlocking))
875             return;
876         IStatus reason;
877         if (blockingJob == null || blockingJob instanceof ThreadJob || blockingJob.isSystem()) {
878             reason = new Status(IStatus.INFO, JobManager.PI_JOBS, 1, JobMessages.jobs_blocked0, null);
879         } else {
880             String JavaDoc msg = NLS.bind(JobMessages.jobs_blocked1, blockingJob.getName());
881             reason = new JobStatus(IStatus.INFO, (Job) blockingJob, msg);
882         }
883         ((IProgressMonitorWithBlocking) monitor).setBlocked(reason);
884     }
885
886     /**
887      * Reports that this thread was blocked, but is no longer blocked and is able
888      * to proceed.
889      * @param monitor The monitor to report unblocking to.
890      * @see #reportBlocked
891      */

892     final void reportUnblocked(IProgressMonitor monitor) {
893         if (monitor instanceof IProgressMonitorWithBlocking)
894             ((IProgressMonitorWithBlocking) monitor).clearBlocked();
895     }
896
897     /*(non-Javadoc)
898      * @see org.eclipse.core.runtime.jobs.IJobManager#resume()
899      */

900     public final void resume() {
901         synchronized (lock) {
902             suspended = false;
903             //poke the job pool
904
pool.jobQueued();
905         }
906     }
907
908     /** (non-Javadoc)
909      * @deprecated this method should not be used
910      * @see org.eclipse.core.runtime.jobs.IJobManager#resume(org.eclipse.core.runtime.jobs.ISchedulingRule)
911      */

912     public final void resume(ISchedulingRule rule) {
913         implicitJobs.resume(rule);
914     }
915
916     /**
917      * Attempts to immediately start a given job. Returns true if the job was
918      * successfully started, and false if it could not be started immediately
919      * due to a currently running job with a conflicting rule. Listeners will never
920      * be notified of jobs that are run in this way.
921      */

922     protected boolean runNow(InternalJob job) {
923         synchronized (lock) {
924             //cannot start if there is a conflicting job
925
if (findBlockingJob(job) != null)
926                 return false;
927             changeState(job, Job.RUNNING);
928             job.setProgressMonitor(new NullProgressMonitor());
929             job.run(null);
930         }
931         return true;
932     }
933
934     /* (non-Javadoc)
935      * @see org.eclipse.core.runtime.jobs.Job#schedule(long)
936      */

937     protected void schedule(InternalJob job, long delay, boolean reschedule) {
938         if (!active)
939             throw new IllegalStateException JavaDoc("Job manager has been shut down."); //$NON-NLS-1$
940
Assert.isNotNull(job, "Job is null"); //$NON-NLS-1$
941
Assert.isLegal(delay >= 0, "Scheduling delay is negative"); //$NON-NLS-1$
942
synchronized (lock) {
943             //if the job is already running, set it to be rescheduled when done
944
if (job.getState() == Job.RUNNING) {
945                 job.setStartTime(delay);
946                 return;
947             }
948             //can't schedule a job that is waiting or sleeping
949
if (job.internalGetState() != Job.NONE)
950                 return;
951             if (JobManager.DEBUG)
952                 JobManager.debug("Scheduling job: " + job); //$NON-NLS-1$
953
//remember that we are about to schedule the job
954
//to prevent multiple schedule attempts from succeeding (bug 68452)
955
changeState(job, InternalJob.ABOUT_TO_SCHEDULE);
956         }
957         //notify listeners outside sync block
958
jobListeners.scheduled((Job) job, delay, reschedule);
959         //schedule the job
960
doSchedule(job, delay);
961         //call the pool outside sync block to avoid deadlock
962
pool.jobQueued();
963     }
964
965     /**
966      * Adds all family members in the list of jobs to the collection
967      */

968     private void select(List members, Object JavaDoc family, InternalJob firstJob, int stateMask) {
969         if (firstJob == null)
970             return;
971         InternalJob job = firstJob;
972         do {
973             //note that job state cannot be NONE at this point
974
if ((family == null || job.belongsTo(family)) && ((job.getState() & stateMask) != 0))
975                 members.add(job);
976             job = job.previous();
977         } while (job != null && job != firstJob);
978     }
979
980     /**
981      * Returns a list of all jobs known to the job manager that belong to the given family.
982      */

983     private List select(Object JavaDoc family) {
984         return select(family, Job.WAITING | Job.SLEEPING | Job.RUNNING);
985     }
986
987     /**
988      * Returns a list of all jobs known to the job manager that belong to the given
989      * family and are in one of the provided states.
990      */

991     private List select(Object JavaDoc family, int stateMask) {
992         List members = new ArrayList();
993         synchronized (lock) {
994             if ((stateMask & Job.RUNNING) != 0) {
995                 for (Iterator it = running.iterator(); it.hasNext();) {
996                     select(members, family, (InternalJob) it.next(), stateMask);
997                 }
998             }
999             if ((stateMask & Job.WAITING) != 0)
1000                select(members, family, waiting.peek(), stateMask);
1001            if ((stateMask & Job.SLEEPING) != 0)
1002                select(members, family, sleeping.peek(), stateMask);
1003        }
1004        return members;
1005    }
1006
1007    /* (non-Javadoc)
1008     * @see IJobManager#setLockListener(LockListener)
1009     */

1010    public void setLockListener(LockListener listener) {
1011        lockManager.setLockListener(listener);
1012    }
1013
1014    /**
1015     * Changes a job priority.
1016     */

1017    protected void setPriority(InternalJob job, int newPriority) {
1018        synchronized (lock) {
1019            int oldPriority = job.getPriority();
1020            if (oldPriority == newPriority)
1021                return;
1022            job.internalSetPriority(newPriority);
1023            //if the job is waiting to run, re-shuffle the queue
1024
if (job.getState() == Job.WAITING) {
1025                long oldStart = job.getStartTime();
1026                job.setStartTime(oldStart + (delayFor(newPriority) - delayFor(oldPriority)));
1027                waiting.resort(job);
1028            }
1029        }
1030    }
1031
1032    /* (non-Javadoc)
1033     * @see IJobManager#setProgressProvider(IProgressProvider)
1034     */

1035    public void setProgressProvider(ProgressProvider provider) {
1036        progressProvider = provider;
1037    }
1038
1039    /* (non-Javadoc)
1040     * @see Job#setRule
1041     */

1042    public void setRule(InternalJob job, ISchedulingRule rule) {
1043        synchronized (lock) {
1044            //cannot change the rule of a job that is already running
1045
Assert.isLegal(job.getState() == Job.NONE);
1046            validateRule(rule);
1047            job.internalSetRule(rule);
1048        }
1049    }
1050
1051    /**
1052     * Puts a job to sleep. Returns true if the job was successfully put to sleep.
1053     */

1054    protected boolean sleep(InternalJob job) {
1055        synchronized (lock) {
1056            switch (job.getState()) {
1057                case Job.RUNNING :
1058                    //cannot be paused if it is already running (as opposed to ABOUT_TO_RUN)
1059
if (job.internalGetState() == Job.RUNNING)
1060                        return false;
1061                    //job hasn't started running yet (aboutToRun listener)
1062
break;
1063                case Job.SLEEPING :
1064                    //update the job wake time
1065
job.setStartTime(InternalJob.T_INFINITE);
1066                    //change state again to re-shuffle the sleep queue
1067
changeState(job, Job.SLEEPING);
1068                    return true;
1069                case Job.NONE :
1070                    return true;
1071                case Job.WAITING :
1072                    //put the job to sleep
1073
break;
1074            }
1075            job.setStartTime(InternalJob.T_INFINITE);
1076            changeState(job, Job.SLEEPING);
1077        }
1078        jobListeners.sleeping((Job) job);
1079        return true;
1080    }
1081
1082    /* (non-Javadoc)
1083     * @see IJobManager#sleep(String)
1084     */

1085    public void sleep(Object JavaDoc family) {
1086        //don't synchronize because sleep calls listeners
1087
for (Iterator it = select(family).iterator(); it.hasNext();) {
1088            sleep((InternalJob) it.next());
1089        }
1090    }
1091
1092    /**
1093     * Returns the estimated time in milliseconds before the next job is scheduled
1094     * to wake up. The result may be negative. Returns InternalJob.T_INFINITE if
1095     * there are no sleeping or waiting jobs.
1096     */

1097    protected long sleepHint() {
1098        synchronized (lock) {
1099            //wait forever if job manager is suspended
1100
if (suspended)
1101                return InternalJob.T_INFINITE;
1102            if (!waiting.isEmpty())
1103                return 0L;
1104            //return the anticipated time that the next sleeping job will wake
1105
InternalJob next = sleeping.peek();
1106            if (next == null)
1107                return InternalJob.T_INFINITE;
1108            return next.getStartTime() - System.currentTimeMillis();
1109        }
1110    }
1111
1112    /**
1113     * Returns the next job to be run, or null if no jobs are waiting to run.
1114     * The worker must call endJob when the job is finished running.
1115     */

1116    protected Job startJob() {
1117        Job job = null;
1118        while (true) {
1119            job = nextJob();
1120            if (job == null)
1121                return null;
1122            //must perform this outside sync block because it is third party code
1123
if (job.shouldRun()) {
1124                //check for listener veto
1125
jobListeners.aboutToRun(job);
1126                //listeners may have canceled or put the job to sleep
1127
synchronized (lock) {
1128                    if (job.getState() == Job.RUNNING) {
1129                        InternalJob internal = job;
1130                        if (internal.isAboutToRunCanceled()) {
1131                            internal.setAboutToRunCanceled(false);
1132                            //fall through and end the job below
1133
} else {
1134                            internal.setProgressMonitor(createMonitor(job));
1135                            //change from ABOUT_TO_RUN to RUNNING
1136
internal.internalSetState(Job.RUNNING);
1137                            break;
1138                        }
1139                    }
1140                }
1141            }
1142            if (job.getState() != Job.SLEEPING) {
1143                //job has been vetoed or canceled, so mark it as done
1144
endJob(job, Status.CANCEL_STATUS, true);
1145                continue;
1146            }
1147        }
1148        jobListeners.running(job);
1149        return job;
1150
1151    }
1152
1153    /* non-Javadoc)
1154     * @see org.eclipse.core.runtime.jobs.IJobManager#suspend()
1155     */

1156    public final void suspend() {
1157        synchronized (lock) {
1158            suspended = true;
1159        }
1160    }
1161
1162    /** (non-Javadoc)
1163     * @deprecated this method should not be used
1164     * @see org.eclipse.core.runtime.jobs.IJobManager#suspend(org.eclipse.core.runtime.jobs.ISchedulingRule, org.eclipse.core.runtime.IProgressMonitor)
1165     */

1166    public final void suspend(ISchedulingRule rule, IProgressMonitor monitor) {
1167        Assert.isNotNull(rule);
1168        implicitJobs.suspend(rule, monitorFor(monitor));
1169    }
1170
1171    /* non-Javadoc)
1172     * @see org.eclipse.core.runtime.jobs.IJobManager#transferRule()
1173     */

1174    public void transferRule(ISchedulingRule rule, Thread JavaDoc destinationThread) {
1175        implicitJobs.transfer(rule, destinationThread);
1176    }
1177
1178    /**
1179     * Validates that the given scheduling rule obeys the constraints of
1180     * scheduling rules as described in the <code>ISchedulingRule</code>
1181     * javadoc specification.
1182     */

1183    private void validateRule(ISchedulingRule rule) {
1184        //null rule always valid
1185
if (rule == null)
1186            return;
1187        //contains method must be reflexive
1188
Assert.isLegal(rule.contains(rule));
1189        //contains method must return false when given an unknown rule
1190
Assert.isLegal(!rule.contains(nullRule));
1191        //isConflicting method must be reflexive
1192
Assert.isLegal(rule.isConflicting(rule));
1193        //isConflicting method must return false when given an unknown rule
1194
Assert.isLegal(!rule.isConflicting(nullRule));
1195    }
1196
1197    /* (non-Javadoc)
1198     * @see Job#wakeUp(long)
1199     */

1200    protected void wakeUp(InternalJob job, long delay) {
1201        Assert.isLegal(delay >= 0, "Scheduling delay is negative"); //$NON-NLS-1$
1202
synchronized (lock) {
1203            //cannot wake up if it is not sleeping
1204
if (job.getState() != Job.SLEEPING)
1205                return;
1206            doSchedule(job, delay);
1207        }
1208        //call the pool outside sync block to avoid deadlock
1209
pool.jobQueued();
1210
1211        //only notify of wake up if immediate
1212
if (delay == 0)
1213            jobListeners.awake((Job) job);
1214    }
1215
1216    /* (non-Javadoc)
1217     * @see IJobFamily#wakeUp(String)
1218     */

1219    public void wakeUp(Object JavaDoc family) {
1220        //don't synchronize because wakeUp calls listeners
1221
for (Iterator it = select(family).iterator(); it.hasNext();) {
1222            wakeUp((InternalJob) it.next(), 0L);
1223        }
1224    }
1225}
1226
Popular Tags