Skip to content

Commit 6a2d98d

Browse files
committed
Use a separate ExecutorService in AsyncCompletionProposalPopup
This is necessary in order to effectively cancel all running futures in the popup (when one closes the popup).
1 parent 7d4cca3 commit 6a2d98d

File tree

1 file changed

+60
-2
lines changed

1 file changed

+60
-2
lines changed

bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/AsyncCompletionProposalPopup.java

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,15 @@
2323
import java.util.List;
2424
import java.util.Optional;
2525
import java.util.Set;
26+
import java.util.concurrent.Callable;
27+
import java.util.concurrent.CancellationException;
2628
import java.util.concurrent.CompletableFuture;
2729
import java.util.concurrent.ExecutionException;
30+
import java.util.concurrent.ExecutorService;
31+
import java.util.concurrent.FutureTask;
32+
import java.util.concurrent.LinkedBlockingQueue;
33+
import java.util.concurrent.ThreadFactory;
34+
import java.util.concurrent.ThreadPoolExecutor;
2835
import java.util.concurrent.TimeUnit;
2936
import java.util.concurrent.TimeoutException;
3037
import java.util.concurrent.atomic.AtomicInteger;
@@ -60,6 +67,22 @@ class AsyncCompletionProposalPopup extends CompletionProposalPopup {
6067

6168
private static final int MAX_WAIT_IN_MS= 50; // TODO make it a preference
6269

70+
private static final ExecutorService EXECUTOR= new ThreadPoolExecutor(
71+
Runtime.getRuntime().availableProcessors(),
72+
Runtime.getRuntime().availableProcessors(),
73+
3L, TimeUnit.SECONDS,
74+
new LinkedBlockingQueue<>(),
75+
new ThreadFactory() {
76+
AtomicInteger count= new AtomicInteger(1);
77+
78+
@Override
79+
public Thread newThread(Runnable r) {
80+
Thread t= new Thread(r, AsyncCompletionProposalPopup.class.getSimpleName() + "-worker-" + count.getAndIncrement()); //$NON-NLS-1$
81+
t.setDaemon(true); // No need to keep the JVM running just because of the completion proposals
82+
return t;
83+
}
84+
});
85+
6386
/**
6487
* This is only used and set when populating the dialog is async (ie computation takes more than
6588
* MAX_WAIT_IN_MS
@@ -372,7 +395,7 @@ protected List<CompletableFuture<List<ICompletionProposal>>> buildCompletionFutu
372395
}
373396
List<CompletableFuture<List<ICompletionProposal>>> futures = new ArrayList<>(processors.size());
374397
for (IContentAssistProcessor processor : processors) {
375-
futures.add(CompletableFuture.supplyAsync(() -> {
398+
futures.add(submitInterruptible(() -> {
376399
AtomicReference<List<ICompletionProposal>> result= new AtomicReference<>();
377400
SafeRunner.run(() -> {
378401
ICompletionProposal[] proposals= processor.computeCompletionProposals(fViewer, invocationOffset);
@@ -389,11 +412,46 @@ protected List<CompletableFuture<List<ICompletionProposal>>> buildCompletionFutu
389412
return Collections.emptyList();
390413
}
391414
return proposals;
392-
}));
415+
}, EXECUTOR));
393416
}
394417
return futures;
395418
}
396419

420+
/**
421+
* Submit a task in such a way that it actually reacts to cancellation (i.e. calls to
422+
* {@code future.cancel(true)})
423+
*
424+
* @param executor Do not use the common pool here since that one does not cancel (interrupts)
425+
* worker threads
426+
* @return an interruptible future.
427+
*/
428+
private static <T> CompletableFuture<T> submitInterruptible(
429+
Callable<T> task, ExecutorService executor) {
430+
431+
CompletableFuture<T> cf= new CompletableFuture<>();
432+
FutureTask<T> ft= new FutureTask<>(task);
433+
434+
executor.submit(() -> {
435+
try {
436+
ft.run(); // executes in this thread
437+
cf.complete(ft.get());
438+
} catch (CancellationException e) {
439+
cf.cancel(true);
440+
} catch (Exception e) {
441+
cf.completeExceptionally(e);
442+
}
443+
});
444+
445+
// make canceling the CF also cancel the FutureTask
446+
cf.whenComplete((r, t) -> {
447+
if (cf.isCancelled()) {
448+
ft.cancel(true); // this actually interrupts
449+
}
450+
});
451+
452+
return cf;
453+
}
454+
397455
private String getTokenContentType(int invocationOffset) throws BadLocationException {
398456
if (fContentAssistSubjectControl != null) {
399457
IDocument document= fContentAssistSubjectControl.getDocument();

0 commit comments

Comments
 (0)