Skip to content

Commit a7f0d23

Browse files
committed
Run long-spanning tasks cancellably on background in the (Java) LSP server.
1 parent c2ec6e4 commit a7f0d23

File tree

4 files changed

+599
-239
lines changed

4 files changed

+599
-239
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.netbeans.modules.java.lsp.server.protocol;
20+
21+
import java.util.ArrayList;
22+
import java.util.List;
23+
import java.util.Map.Entry;
24+
import java.util.SortedMap;
25+
import java.util.TreeMap;
26+
import java.util.concurrent.CancellationException;
27+
import java.util.concurrent.CompletableFuture;
28+
import java.util.concurrent.atomic.AtomicBoolean;
29+
import java.util.concurrent.atomic.AtomicReference;
30+
import org.openide.util.RequestProcessor;
31+
32+
public class PriorityQueueRun {
33+
34+
private static final PriorityQueueRun INSTANCE = new PriorityQueueRun();
35+
private static final RequestProcessor WORKER = new RequestProcessor(PriorityQueueRun.class.getName() + "-worker", 1, false, false);
36+
private static final RequestProcessor DELAY = new RequestProcessor(PriorityQueueRun.class.getName() + "-delay", 1, false, false);
37+
38+
public static PriorityQueueRun getInstance() {
39+
return INSTANCE;
40+
}
41+
42+
private final SortedMap<Priority, List<TaskDescription<?, ?>>> priority2Tasks = new TreeMap<>((p1, p2) -> -p1.compareTo(p2));
43+
private Priority currentPriority;
44+
private CancelCheck currentTaskCheck;
45+
46+
public <P, R> CompletableFuture<R> runTask(Priority priority, CancellableTask<P, R> task, P data) {
47+
CompletableFuture<R> result = new CompletableFuture<>();
48+
49+
synchronized (this) {
50+
priority2Tasks.computeIfAbsent(priority, __ -> new ArrayList<>())
51+
.add(new TaskDescription(task, data, result));
52+
53+
scheduleNext();
54+
}
55+
56+
return result;
57+
}
58+
59+
public <P, R> CompletableFuture<R> runTask(Priority priority, CancellableTask<P, R> task, P data, int delay) {
60+
CompletableFuture<R> result = new CompletableFuture<>();
61+
DELAY.post(() -> {
62+
if (result.isCancelled()) {
63+
return ; //already cancelled
64+
}
65+
synchronized (this) {
66+
priority2Tasks.computeIfAbsent(priority, __ -> new ArrayList<>())
67+
.add(new TaskDescription(task, data, result));
68+
69+
scheduleNext();
70+
}
71+
}, delay);
72+
73+
return result;
74+
}
75+
76+
private synchronized void scheduleNext() {
77+
Priority foundPriority = null;
78+
List<TaskDescription<?, ?>> foundTasks = null;
79+
80+
for (Entry<Priority, List<TaskDescription<?, ?>>> e : priority2Tasks.entrySet()) {
81+
if (!e.getValue().isEmpty()) {
82+
foundPriority = e.getKey();
83+
foundTasks = e.getValue();
84+
break;
85+
}
86+
}
87+
88+
if (foundPriority == null) {
89+
return ;
90+
}
91+
92+
if (currentPriority == null) {
93+
Priority thisPriority = currentPriority = foundPriority;
94+
TaskDescription thisTask = foundTasks.remove(0);
95+
CancelCheck thisTaskCheck = currentTaskCheck = new CancelCheck();
96+
97+
WORKER.post(() -> {
98+
thisTask.result.whenComplete((__, exc) -> {
99+
if (exc instanceof CancellationException) {
100+
thisTaskCheck.cancel();
101+
}
102+
});
103+
104+
if (thisTask.result.isCancelled()) {
105+
//nothing to do anymore:
106+
return ;
107+
}
108+
109+
Object result = null;
110+
Throwable exception = null;
111+
112+
try {
113+
result = thisTask.task.compute(thisTask.data, thisTaskCheck);
114+
} catch (Throwable t) {
115+
exception = t;
116+
}
117+
118+
synchronized (PriorityQueueRun.this) {
119+
currentPriority = null;
120+
currentTaskCheck = null;
121+
122+
if (!thisTask.result.isCancelled()) {
123+
if (exception != null) {
124+
thisTask.result.completeExceptionally(exception);
125+
} else if (thisTaskCheck.isCancelled()) {
126+
priority2Tasks.computeIfAbsent(thisPriority, __ -> new ArrayList<>())
127+
.add(0, thisTask);
128+
} else {
129+
thisTask.result.complete(result);
130+
}
131+
}
132+
}
133+
134+
scheduleNext();
135+
});
136+
}
137+
138+
if (currentPriority != null && currentPriority.compareTo(foundPriority) < 0) {
139+
//cancel the currently running task:
140+
currentTaskCheck.cancel();
141+
}
142+
}
143+
144+
private static final class TaskDescription<P, R> {
145+
private final CancellableTask<P, R> task;
146+
private final P data;
147+
private final CompletableFuture<R> result;
148+
149+
public TaskDescription(CancellableTask<P, R> task, P data, CompletableFuture<R> result) {
150+
this.task = task;
151+
this.data = data;
152+
this.result = result;
153+
}
154+
}
155+
156+
public interface CancellableTask<P, R> {
157+
public R compute(P param, CancelCheck cancel) throws Exception;
158+
}
159+
160+
public interface CancelCallback {
161+
public void cancel();
162+
}
163+
164+
public final class CancelCheck {
165+
private final AtomicBoolean cancelled = new AtomicBoolean();
166+
private final AtomicReference<CancelCallback> cancelCallback = new AtomicReference<>();
167+
168+
private CancelCheck() {}
169+
170+
public boolean isCancelled() {
171+
return cancelled.get();
172+
}
173+
174+
public void registerCancel(CancelCallback callback) {
175+
cancelCallback.set(callback);
176+
if (cancelled.get()) {
177+
callback.cancel();
178+
}
179+
}
180+
181+
void cancel() {
182+
cancelled.set(true);
183+
184+
CancelCallback callback = cancelCallback.get();
185+
186+
if (callback != null) {
187+
callback.cancel();
188+
}
189+
}
190+
}
191+
192+
public enum Priority {
193+
BELOW_LOW,
194+
LOW,
195+
NORMAL,
196+
HIGH,
197+
HIGHER;
198+
}
199+
}

0 commit comments

Comments
 (0)