122
|
1 /*******************************************************************************
|
|
2 * Copyright (c) 2004, 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 - Initial API and implementation
|
|
10 * Port to the D programming language:
|
|
11 * Frank Benoit <benoit@tionex.de>
|
|
12 *******************************************************************************/
|
|
13 module dwtx.core.internal.jobs.ThreadJob;
|
|
14
|
|
15 import dwt.dwthelper.utils;
|
|
16 import tango.text.convert.Format;
|
|
17 import tango.io.Stdout;
|
|
18 import tango.core.Thread;
|
|
19 import tango.core.sync.Mutex;
|
|
20 import tango.core.sync.Condition;
|
|
21
|
|
22 import dwtx.core.internal.runtime.RuntimeLog;
|
|
23 import dwtx.core.runtime.Assert;
|
|
24 import dwtx.core.runtime.IProgressMonitor;
|
|
25 import dwtx.core.runtime.IStatus;
|
|
26 import dwtx.core.runtime.OperationCanceledException;
|
|
27 import dwtx.core.runtime.Status;
|
|
28 import dwtx.core.runtime.jobs.ISchedulingRule;
|
|
29 import dwtx.core.runtime.jobs.Job;
|
|
30 import dwtx.core.internal.jobs.JobManager;
|
|
31 import dwtx.core.internal.jobs.InternalJob;
|
|
32 import dwtx.core.internal.jobs.LockManager;
|
|
33
|
|
34 /**
|
|
35 * Captures the implicit job state for a given thread.
|
|
36 */
|
|
37 class ThreadJob : Job {
|
|
38 /**
|
|
39 * The notifier is a shared object used to wake up waiting thread jobs
|
|
40 * when another job completes that is releasing a scheduling rule.
|
|
41 */
|
|
42
|
|
43 static const Mutex mutex;
|
|
44 static const Condition condition;
|
|
45 static this(){
|
|
46 mutex = new Mutex();
|
|
47 condition = new Condition(mutex);
|
|
48 }
|
|
49
|
|
50 private const JobManager manager;
|
|
51 /**
|
|
52 * Set to true if this thread job is running in a thread that did
|
|
53 * not own a rule already. This means it needs to acquire the
|
|
54 * rule during beginRule, and must release the rule during endRule.
|
|
55 */
|
|
56 /+protected+/ bool acquireRule = false;
|
|
57
|
|
58 /**
|
|
59 * Indicates that this thread job did report to the progress manager
|
|
60 * that it will be blocked, and therefore when it begins it must
|
|
61 * be reported to the job manager when it is no longer blocked.
|
|
62 */
|
|
63 bool isBlocked = false;
|
|
64
|
|
65 /**
|
|
66 * True if this ThreadJob has begun execution
|
|
67 */
|
|
68 /+protected+/ bool isRunning_ = false;
|
|
69
|
|
70 /**
|
|
71 * Used for diagnosing mismatched begin/end pairs. This field
|
|
72 * is only used when in debug mode, to capture the stack trace
|
|
73 * of the last call to beginRule.
|
|
74 */
|
|
75 private RuntimeException lastPush = null;
|
|
76 /**
|
|
77 * The actual job that is running in the thread that this
|
|
78 * ThreadJob represents. This will be null if this thread
|
|
79 * job is capturing a rule acquired outside of a job.
|
|
80 */
|
|
81 protected package Job realJob;
|
|
82 /**
|
|
83 * The stack of rules that have been begun in this thread, but not yet ended.
|
|
84 */
|
|
85 private ISchedulingRule[] ruleStack;
|
|
86 /**
|
|
87 * Rule stack pointer.
|
|
88 */
|
|
89 private int top;
|
|
90
|
|
91 this(JobManager manager, ISchedulingRule rule) {
|
|
92 super("Implicit Job"); //$NON-NLS-1$
|
|
93 this.manager = manager;
|
|
94 setSystem(true);
|
|
95 setPriority(Job.INTERACTIVE);
|
|
96 ruleStack = new ISchedulingRule[2];
|
|
97 top = -1;
|
|
98 setRule(rule);
|
|
99 }
|
|
100
|
|
101 /**
|
|
102 * An endRule was called that did not match the last beginRule in
|
|
103 * the stack. Report and log a detailed informational message.
|
|
104 * @param rule The rule that was popped
|
|
105 */
|
|
106 private void illegalPop(ISchedulingRule rule) {
|
|
107 StringBuffer buf = new StringBuffer("Attempted to endRule: "); //$NON-NLS-1$
|
|
108 buf.append(Format("{}",rule));
|
|
109 if (top >= 0 && top < ruleStack.length) {
|
|
110 buf.append(", does not match most recent begin: "); //$NON-NLS-1$
|
|
111 buf.append(Format("{}",ruleStack[top]));
|
|
112 } else {
|
|
113 if (top < 0)
|
|
114 buf.append(", but there was no matching beginRule"); //$NON-NLS-1$
|
|
115 else
|
|
116 buf.append( Format(", but the rule stack was out of bounds: {}", top)); //$NON-NLS-1$
|
|
117 }
|
|
118 buf.append(". See log for trace information if rule tracing is enabled."); //$NON-NLS-1$
|
|
119 String msg = buf.toString();
|
|
120 if (JobManager.DEBUG || JobManager.DEBUG_BEGIN_END) {
|
|
121 Stdout.formatln("{}",msg);
|
|
122 Exception t = lastPush is null ? new IllegalArgumentException("") : lastPush;
|
|
123 IStatus error = new Status(IStatus.ERROR, JobManager.PI_JOBS, 1, msg, t);
|
|
124 RuntimeLog.log(error);
|
|
125 }
|
|
126 Assert.isLegal(false, msg);
|
|
127 }
|
|
128
|
|
129 /**
|
|
130 * Client has attempted to begin a rule that is not contained within
|
|
131 * the outer rule.
|
|
132 */
|
|
133 private void illegalPush(ISchedulingRule pushRule, ISchedulingRule baseRule) {
|
|
134 StringBuffer buf = new StringBuffer("Attempted to beginRule: "); //$NON-NLS-1$
|
|
135 buf.append(Format("{}",pushRule));
|
|
136 buf.append(", does not match outer scope rule: "); //$NON-NLS-1$
|
|
137 buf.append(Format("{}",baseRule));
|
|
138 String msg = buf.toString();
|
|
139 if (JobManager.DEBUG) {
|
|
140 Stdout.formatln("{}",msg);
|
|
141 IStatus error = new Status(IStatus.ERROR, JobManager.PI_JOBS, 1, msg, new IllegalArgumentException(""));
|
|
142 RuntimeLog.log(error);
|
|
143 }
|
|
144 Assert.isLegal(false, msg);
|
|
145
|
|
146 }
|
|
147
|
|
148 /**
|
|
149 * Returns true if the monitor is canceled, and false otherwise.
|
|
150 * Protects the caller from exception in the monitor implementation.
|
|
151 */
|
|
152 private bool isCanceled(IProgressMonitor monitor) {
|
|
153 try {
|
|
154 return monitor.isCanceled();
|
|
155 } catch (RuntimeException e) {
|
|
156 //logged message should not be translated
|
|
157 IStatus status = new Status(IStatus.ERROR, JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, "ThreadJob.isCanceled", e); //$NON-NLS-1$
|
|
158 RuntimeLog.log(status);
|
|
159 }
|
|
160 return false;
|
|
161 }
|
|
162
|
|
163 /**
|
|
164 * Returns true if this thread job was scheduled and actually started running.
|
|
165 */
|
|
166 bool isRunning() {
|
|
167 synchronized(mutex){
|
|
168 return isRunning_;
|
|
169 }
|
|
170 }
|
|
171
|
|
172 /**
|
|
173 * Schedule the job and block the calling thread until the job starts running.
|
|
174 * Returns the ThreadJob instance that was started.
|
|
175 */
|
|
176 ThreadJob joinRun(IProgressMonitor monitor) {
|
|
177 if (isCanceled(monitor))
|
|
178 throw new OperationCanceledException();
|
|
179 //check if there is a blocking thread before waiting
|
|
180 InternalJob blockingJob = manager.findBlockingJob_package(this);
|
|
181 Thread blocker = blockingJob is null ? null : blockingJob.getThread_package();
|
|
182 ThreadJob result = this;
|
|
183 try {
|
|
184 //just return if lock listener decided to grant immediate access
|
|
185 if (manager.getLockManager().aboutToWait(blocker))
|
|
186 return this;
|
|
187 try {
|
|
188 waitStart(monitor, blockingJob);
|
|
189 final Thread getThis = Thread.getThis();
|
|
190 while (true) {
|
|
191 if (isCanceled(monitor))
|
|
192 throw new OperationCanceledException();
|
|
193 //try to run the job
|
|
194 if (manager.runNow_package(this))
|
|
195 return this;
|
|
196 //update blocking job
|
|
197 blockingJob = manager.findBlockingJob_package(this);
|
|
198 //the rule could have been transferred to this thread while we were waiting
|
|
199 blocker = blockingJob is null ? null : blockingJob.getThread_package();
|
|
200 if (blocker is getThis && cast(ThreadJob)blockingJob ) {
|
|
201 //now we are just the nested acquire case
|
|
202 result = cast(ThreadJob) blockingJob;
|
|
203 result.push(getRule());
|
|
204 result.isBlocked = this.isBlocked;
|
|
205 return result;
|
|
206 }
|
|
207 //just return if lock listener decided to grant immediate access
|
|
208 if (manager.getLockManager().aboutToWait(blocker))
|
|
209 return this;
|
|
210 //must lock instance before calling wait
|
|
211 synchronized (mutex) {
|
|
212 try {
|
|
213 condition.wait(0.250);
|
|
214 } catch (InterruptedException e) {
|
|
215 //ignore
|
|
216 }
|
|
217 }
|
|
218 }
|
|
219 } finally {
|
|
220 if (this is result)
|
|
221 waitEnd(monitor);
|
|
222 }
|
|
223 } finally {
|
|
224 manager.getLockManager().aboutToRelease();
|
|
225 }
|
|
226 }
|
|
227
|
|
228 /**
|
|
229 * Pops a rule. Returns true if it was the last rule for this thread
|
|
230 * job, and false otherwise.
|
|
231 */
|
|
232 bool pop(ISchedulingRule rule) {
|
|
233 if (top < 0 || ruleStack[top] !is rule)
|
|
234 illegalPop(rule);
|
|
235 ruleStack[top--] = null;
|
|
236 return top < 0;
|
|
237 }
|
|
238
|
|
239 /**
|
|
240 * Adds a new scheduling rule to the stack of rules for this thread. Throws
|
|
241 * a runtime exception if the new rule is not compatible with the base
|
|
242 * scheduling rule for this thread.
|
|
243 */
|
|
244 void push(ISchedulingRule rule) {
|
|
245 ISchedulingRule baseRule = getRule();
|
|
246 if (++top >= ruleStack.length) {
|
|
247 ISchedulingRule[] newStack = new ISchedulingRule[ruleStack.length * 2];
|
|
248 SimpleType!(ISchedulingRule).arraycopy(ruleStack, 0, newStack, 0, ruleStack.length);
|
|
249 ruleStack = newStack;
|
|
250 }
|
|
251 ruleStack[top] = rule;
|
|
252 if (JobManager.DEBUG_BEGIN_END)
|
|
253 lastPush = new RuntimeException()/+).fillInStackTrace()+/;
|
|
254 //check for containment last because we don't want to fail again on endRule
|
|
255 if (baseRule !is null && rule !is null && !baseRule.contains(rule))
|
|
256 illegalPush(rule, baseRule);
|
|
257 }
|
|
258
|
|
259 /**
|
|
260 * Reset all of this job's fields so it can be reused. Returns false if
|
|
261 * reuse is not possible
|
|
262 */
|
|
263 bool recycle() {
|
|
264 //don't recycle if still running for any reason
|
|
265 if (getState() !is Job.NONE)
|
|
266 return false;
|
|
267 //clear and reset all fields
|
|
268 acquireRule = isRunning_ = isBlocked = false;
|
|
269 realJob = null;
|
|
270 setRule(null);
|
|
271 setThread(null);
|
|
272 if (ruleStack.length !is 2)
|
|
273 ruleStack = new ISchedulingRule[2];
|
|
274 else
|
|
275 ruleStack[0] = ruleStack[1] = null;
|
|
276 top = -1;
|
|
277 return true;
|
|
278 }
|
|
279
|
|
280 /** (non-Javadoc)
|
|
281 * @see dwtx.core.runtime.jobs.Job#run(dwtx.core.runtime.IProgressMonitor)
|
|
282 */
|
|
283 public IStatus run(IProgressMonitor monitor) {
|
|
284 synchronized (this) {
|
|
285 isRunning_ = true;
|
|
286 }
|
|
287 return ASYNC_FINISH;
|
|
288 }
|
|
289
|
|
290 /**
|
|
291 * Records the job that is actually running in this thread, if any
|
|
292 * @param realJob The running job
|
|
293 */
|
|
294 void setRealJob(Job realJob) {
|
|
295 this.realJob = realJob;
|
|
296 }
|
|
297
|
|
298 /**
|
|
299 * Returns true if this job should cause a self-canceling job
|
|
300 * to cancel itself, and false otherwise.
|
|
301 */
|
|
302 bool shouldInterrupt() {
|
|
303 return realJob is null ? true : !realJob.isSystem();
|
|
304 }
|
|
305
|
|
306 /* (non-javadoc)
|
|
307 * For debugging purposes only
|
|
308 */
|
|
309 public String toString() {
|
|
310 StringBuffer buf = new StringBuffer("ThreadJob"); //$NON-NLS-1$
|
|
311 buf.append('(').append(Format("{}",realJob)).append(',').append('[');
|
|
312 for (int i = 0; i <= top && i < ruleStack.length; i++)
|
|
313 buf.append(Format("{}",ruleStack[i])).append(',');
|
|
314 buf.append(']').append(')');
|
|
315 return buf.toString();
|
|
316 }
|
|
317
|
|
318 /**
|
|
319 * Reports that this thread was blocked, but is no longer blocked and is able
|
|
320 * to proceed.
|
|
321 * @param monitor The monitor to report unblocking to.
|
|
322 */
|
|
323 private void waitEnd(IProgressMonitor monitor) {
|
|
324 final LockManager lockManager = manager.getLockManager();
|
|
325 final Thread getThis = Thread.getThis();
|
|
326 if (isRunning()) {
|
|
327 lockManager.addLockThread(getThis, getRule());
|
|
328 //need to re-acquire any locks that were suspended while this thread was blocked on the rule
|
|
329 lockManager.resumeSuspendedLocks(getThis);
|
|
330 } else {
|
|
331 //tell lock manager that this thread gave up waiting
|
|
332 lockManager.removeLockWaitThread(getThis, getRule());
|
|
333 }
|
|
334 }
|
|
335
|
|
336 /**
|
|
337 * Indicates the start of a wait on a scheduling rule. Report the
|
|
338 * blockage to the progress manager and update the lock manager.
|
|
339 * @param monitor The monitor to report blocking to
|
|
340 * @param blockingJob The job that is blocking this thread, or <code>null</code>
|
|
341 */
|
|
342 private void waitStart(IProgressMonitor monitor, InternalJob blockingJob) {
|
|
343 manager.getLockManager().addLockWaitThread(Thread.getThis(), getRule());
|
|
344 isBlocked = true;
|
|
345 manager.reportBlocked(monitor, blockingJob);
|
|
346 }
|
|
347 }
|