Skip to content

Commit f41ea06

Browse files
committed
Only start the reconciler thread with a job
Currently the Job Framework is not designed for handling more than a few running jobs and certain actions are considerably delayed once a job is active for a longer time. Because of this, now only start the reconciler thread inside a job but perform the actual work in an own thread. Fix eclipse-jdt/eclipse.jdt.ui#2323
1 parent 4b0fa03 commit f41ea06

File tree

2 files changed

+211
-241
lines changed

2 files changed

+211
-241
lines changed

bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/AbstractReconciler.java

Lines changed: 211 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.eclipse.core.runtime.Assert;
1717
import org.eclipse.core.runtime.IProgressMonitor;
1818
import org.eclipse.core.runtime.NullProgressMonitor;
19+
import org.eclipse.core.runtime.Status;
1920
import org.eclipse.core.runtime.jobs.Job;
2021

2122
import org.eclipse.jface.text.DocumentEvent;
@@ -50,6 +51,203 @@
5051
*/
5152
abstract public class AbstractReconciler implements IReconciler {
5253

54+
55+
/**
56+
* Background thread for the reconciling activity.
57+
*/
58+
class BackgroundThread extends Thread {
59+
60+
/** Has the reconciler been canceled. */
61+
private boolean fCanceled= false;
62+
/** Has the reconciler been reset. */
63+
private boolean fReset= false;
64+
/** Some changes need to be processed. */
65+
private boolean fIsDirty= false;
66+
/** Is a reconciling strategy active. */
67+
private boolean fIsActive= false;
68+
69+
private boolean fStarted;
70+
71+
/**
72+
* Creates a new background thread. The thread
73+
* runs with minimal priority.
74+
*
75+
* @param name the thread's name
76+
*/
77+
public BackgroundThread(String name) {
78+
super(name);
79+
setPriority(Thread.MIN_PRIORITY);
80+
setDaemon(true);
81+
}
82+
83+
/**
84+
* Returns whether a reconciling strategy is active right now.
85+
*
86+
* @return <code>true</code> if a activity is active
87+
*/
88+
public boolean isActive() {
89+
return fIsActive;
90+
}
91+
92+
/**
93+
* Returns whether some changes need to be processed.
94+
*
95+
* @return <code>true</code> if changes wait to be processed
96+
* @since 3.0
97+
*/
98+
public synchronized boolean isDirty() {
99+
return fIsDirty;
100+
}
101+
102+
/**
103+
* Cancels the background thread.
104+
*/
105+
public void cancel() {
106+
fCanceled= true;
107+
IProgressMonitor pm= fProgressMonitor;
108+
if (pm != null)
109+
pm.setCanceled(true);
110+
synchronized (fDirtyRegionQueue) {
111+
fDirtyRegionQueue.notifyAll();
112+
}
113+
}
114+
115+
/**
116+
* Suspends the caller of this method until this background thread has
117+
* emptied the dirty region queue.
118+
*/
119+
public void suspendCallerWhileDirty() {
120+
AbstractReconciler.this.signalWaitForFinish();
121+
boolean isDirty;
122+
do {
123+
synchronized (fDirtyRegionQueue) {
124+
isDirty= fDirtyRegionQueue.getSize() > 0;
125+
if (isDirty) {
126+
try {
127+
fDirtyRegionQueue.wait();
128+
} catch (InterruptedException x) {
129+
}
130+
}
131+
}
132+
} while (isDirty);
133+
}
134+
135+
/**
136+
* Reset the background thread as the text viewer has been changed,
137+
*/
138+
public void reset() {
139+
140+
if (fDelay > 0) {
141+
142+
synchronized (this) {
143+
fIsDirty= true;
144+
fReset= true;
145+
}
146+
synchronized (fDirtyRegionQueue) {
147+
fDirtyRegionQueue.notifyAll(); // wake up wait(fDelay);
148+
}
149+
150+
} else {
151+
152+
synchronized (this) {
153+
fIsDirty= true;
154+
}
155+
156+
synchronized (fDirtyRegionQueue) {
157+
fDirtyRegionQueue.notifyAll();
158+
}
159+
}
160+
161+
informNotFinished();
162+
reconcilerReset();
163+
}
164+
165+
/**
166+
* The background activity. Waits until there is something in the
167+
* queue managing the changes that have been applied to the text viewer.
168+
* Removes the first change from the queue and process it.
169+
* <p>
170+
* Calls {@link AbstractReconciler#initialProcess()} on entrance.
171+
* </p>
172+
*/
173+
@Override
174+
public void run() {
175+
176+
delay();
177+
178+
if (fCanceled)
179+
return;
180+
181+
initialProcess();
182+
183+
while (!fCanceled) {
184+
185+
delay();
186+
187+
if (fCanceled)
188+
break;
189+
190+
if (!isDirty()) {
191+
waitFinish= false; //signalWaitForFinish() was called but nothing todo
192+
continue;
193+
}
194+
195+
synchronized (this) {
196+
if (fReset) {
197+
fReset= false;
198+
continue;
199+
}
200+
}
201+
202+
DirtyRegion r= null;
203+
synchronized (fDirtyRegionQueue) {
204+
r= fDirtyRegionQueue.removeNextDirtyRegion();
205+
}
206+
207+
fIsActive= true;
208+
209+
fProgressMonitor.setCanceled(false);
210+
211+
process(r);
212+
213+
synchronized (fDirtyRegionQueue) {
214+
if (0 == fDirtyRegionQueue.getSize()) {
215+
synchronized (this) {
216+
fIsDirty= fProgressMonitor.isCanceled();
217+
}
218+
fDirtyRegionQueue.notifyAll();
219+
}
220+
}
221+
222+
fIsActive= false;
223+
}
224+
}
225+
226+
public void startReconciling() {
227+
if (!isAlive()) {
228+
if (fStarted) {
229+
return;
230+
}
231+
fStarted= true;
232+
Job.createSystem("Delayed Reconciler startup", m -> { //$NON-NLS-1$
233+
try {
234+
start();
235+
return Status.OK_STATUS;
236+
} catch (IllegalThreadStateException e) {
237+
// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=40549
238+
// This is the only instance where the thread is started; since
239+
// we checked that it is not alive, it must be dead already due
240+
// to a run-time exception or error. Exit.
241+
return Status.CANCEL_STATUS;
242+
}
243+
}).schedule();
244+
} else {
245+
reset();
246+
}
247+
248+
}
249+
}
250+
53251
/**
54252
* Internal document listener and text input listener.
55253
*/
@@ -63,7 +261,7 @@ public void documentAboutToBeChanged(DocumentEvent e) {
63261
public void documentChanged(DocumentEvent e) {
64262

65263
if (fThread.isActive() || !fThread.isDirty() && fThread.isAlive()) {
66-
if (!fIsAllowedToModifyDocument && isRunningInReconcilerThread())
264+
if (!fIsAllowedToModifyDocument && Thread.currentThread() == fThread)
67265
throw new UnsupportedOperationException("The reconciler thread is not allowed to modify the document"); //$NON-NLS-1$
68266
aboutToBeReconciledInternal();
69267
}
@@ -126,14 +324,13 @@ public void inputDocumentChanged(IDocument oldInput, IDocument newInput) {
126324
}
127325

128326
/** Queue to manage the changes applied to the text viewer. */
129-
DirtyRegionQueue fDirtyRegionQueue;
327+
private DirtyRegionQueue fDirtyRegionQueue;
130328
/** The background thread. */
131-
private ReconcilerJob fThread;
329+
private BackgroundThread fThread;
132330
/** Internal document and text input listener. */
133331
private Listener fListener;
134-
135332
/** The background thread delay. */
136-
int fDelay= 500;
333+
private int fDelay= 500;
137334
/** Signal that the the background thread should not delay. */
138335
volatile boolean waitFinish;
139336
/** Are there incremental reconciling strategies? */
@@ -280,7 +477,7 @@ public void install(ITextViewer textViewer) {
280477
synchronized (this) {
281478
if (fThread != null)
282479
return;
283-
fThread= new ReconcilerJob(getClass().getName(), this);
480+
fThread= new BackgroundThread(getClass().getName());
284481
}
285482

286483
fDirtyRegionQueue= new DirtyRegionQueue();
@@ -314,9 +511,9 @@ public void uninstall() {
314511

315512
synchronized (this) {
316513
// http://dev.eclipse.org/bugs/show_bug.cgi?id=19135
317-
ReconcilerJob bt= fThread;
514+
BackgroundThread bt= fThread;
318515
fThread= null;
319-
bt.doCancel();
516+
bt.cancel();
320517
}
321518
}
322519
}
@@ -383,7 +580,7 @@ public void signalWaitForFinish() {
383580
}
384581
}
385582

386-
void informNotFinished() {
583+
private void informNotFinished() {
387584
waitFinish= false;
388585
aboutToWork();
389586
}
@@ -394,7 +591,7 @@ private void aboutToBeReconciledInternal() {
394591
}
395592

396593

397-
void delay() {
594+
private void delay() {
398595
synchronized (fDirtyRegionQueue) {
399596
if (waitFinish) {
400597
return; // do not delay when waiting;
@@ -444,11 +641,7 @@ protected synchronized void startReconciling() {
444641
if (fThread == null)
445642
return;
446643

447-
if (!fThread.isAlive()) {
448-
fThread.start();
449-
} else {
450-
fThread.reset();
451-
}
644+
fThread.startReconciling();
452645
}
453646

454647
/**
@@ -464,10 +657,7 @@ protected void reconcilerReset() {
464657
* @return <code>true</code> if running in this reconciler's background thread
465658
* @since 3.4
466659
*/
467-
protected synchronized boolean isRunningInReconcilerThread() {
468-
if (fThread == null) {
469-
return false;
470-
}
471-
return Job.getJobManager().currentJob() == fThread;
660+
protected boolean isRunningInReconcilerThread() {
661+
return Thread.currentThread() == fThread;
472662
}
473-
}
663+
}

0 commit comments

Comments
 (0)