Skip to content

Commit fcf11f2

Browse files
committed
Fine grain locking to avoid deadlocks in CU cache
1 parent 9c4aa05 commit fcf11f2

File tree

2 files changed

+62
-43
lines changed

2 files changed

+62
-43
lines changed

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/CompilationUnitCache.java

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -216,8 +216,6 @@ public <T> T withCompilationUnit(IJavaProject project, URI uri, Function<Compila
216216
logger.info("CU Cache: work item submitted for doc {}", uri.toASCIIString());
217217

218218
if (project != null) {
219-
ReadLock lock = environmentCacheLock.readLock();
220-
lock.lock();
221219
try {
222220
CompilationUnit cu = null;
223221
try {
@@ -234,10 +232,16 @@ public <T> T withCompilationUnit(IJavaProject project, URI uri, Function<Compila
234232
}
235233

236234
if (cu != null) {
237-
projectToDocs.get(project.getLocationUri(), () -> new HashSet<>()).add(uri);
238-
239-
logger.debug("CU Cache: start work on AST for {}", uri.toString());
235+
ReadLock lock = environmentCacheLock.readLock();
236+
lock.lock();
237+
try {
238+
logger.info("CU Cache: start work on AST for {}", uri.toString());
240239
return requestor.apply(cu);
240+
} finally {
241+
logger.info("CU Cache: end work on AST for {}", uri.toString());
242+
lock.unlock();
243+
}
244+
241245
}
242246
}
243247
catch (CancellationException e) {
@@ -246,10 +250,6 @@ public <T> T withCompilationUnit(IJavaProject project, URI uri, Function<Compila
246250
catch (Exception e) {
247251
logger.error("", e);
248252
}
249-
finally {
250-
logger.debug("CU Cache: end work on AST for {}", uri.toString());
251-
lock.unlock();
252-
}
253253
}
254254

255255
return requestor.apply(null);
@@ -259,34 +259,49 @@ private synchronized CompletableFuture<CompilationUnit> requestCU(IJavaProject p
259259
CompletableFuture<CompilationUnit> cuFuture = uriToCu.getIfPresent(uri);
260260
if (cuFuture == null) {
261261
cuFuture = CompletableFuture.supplyAsync(() -> {
262+
ReadLock lock = environmentCacheLock.readLock();
263+
lock.lock();
264+
logger.info("Started parsing CU for " + uri);
262265

263266
try {
264-
logger.info("Started parsing CU for " + uri);
265267
Tuple2<List<Classpath>, INameEnvironmentWithProgress> lookupEnvTuple = loadLookupEnvTuple(project);
266268
String uriStr = uri.toASCIIString();
267269
String unitName = uriStr.substring(uriStr.lastIndexOf("/") + 1); // skip over '/'
268270
CompilationUnit cUnit = parse2(fetchContent(uri).toCharArray(), uriStr, unitName, lookupEnvTuple.getT1(), lookupEnvTuple.getT2(),
269271
annotationHierarchies.get(project.getLocationUri(), AnnotationHierarchies::new));
270-
271272
logger.debug("CU Cache: created new AST for {}", uri.toASCIIString());
272-
273273
logger.info("Parsed successfully CU for " + uri);
274274
return cUnit;
275275
} catch (Throwable t) {
276276
// Complete future exceptionally
277277
throw new CompletionException(t);
278+
} finally {
279+
logger.info("Finished parsing CU for {}", uri);
280+
lock.unlock();
278281
}
279282
}, createCuExecutorThreadPool);
280283
// Cache the future
281284
uriToCu.put(uri, cuFuture);
282285
// If CU future completed exceptionally invalidate the cache entry
283-
cuFuture.exceptionally(t -> {
284-
if (!(t instanceof CancellationException)) {
285-
logger.error("", t);
286-
}
287-
uriToCu.invalidate(uri);
288-
return null;
289-
});
286+
cuFuture
287+
.thenAccept(cu -> {
288+
synchronized(CompilationUnitCache.this) {
289+
try {
290+
projectToDocs.get(project.getLocationUri(), () -> new HashSet<>()).add(uri);
291+
} catch (ExecutionException e) {
292+
// shouldn't happen
293+
}
294+
}
295+
})
296+
.exceptionally(t -> {
297+
if (!(t instanceof CancellationException)) {
298+
logger.error("", t);
299+
}
300+
synchronized(CompilationUnitCache.this) {
301+
uriToCu.invalidate(uri);
302+
}
303+
return null;
304+
});
290305
}
291306
return cuFuture;
292307
}

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/jdt/ls/JdtLsProjectCache.java

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -339,42 +339,46 @@ public void initialize(InitializeParams p, ServerCapabilities cap) {
339339
private class JstLsClasspathListener implements ClasspathListener {
340340

341341
@Override
342-
public void changed(Event event) {
342+
public synchronized void changed(Event event) {
343343
log.debug("claspath event received {}", event);
344344
server.doOnInitialized(() -> {
345345
try {
346-
synchronized (table) {
347-
String uri = UriUtil.normalize(event.projectUri);
348-
log.debug("uri = {}", uri);
349-
if (event.deleted) {
350-
log.debug("event.deleted = true");
351-
IJavaProject deleted = table.remove(uri);
352-
if (deleted!=null) {
353-
log.debug("removed from table = true");
354-
notifyDelete(deleted);
355-
} else {
356-
log.warn("Deleted project not removed because uri {} not found in {}", uri, table.keySet());
357-
}
346+
String uri = UriUtil.normalize(event.projectUri);
347+
log.debug("uri = {}", uri);
348+
if (event.deleted) {
349+
log.debug("event.deleted = true");
350+
IJavaProject deleted;
351+
synchronized (table) {
352+
deleted = table.remove(uri);
353+
}
354+
if (deleted!=null) {
355+
log.debug("removed from table = true");
356+
notifyDelete(deleted);
358357
} else {
359-
log.debug("deleted = false");
360-
URI projectUri = new URI(uri);
361-
ClasspathData classpath = new ClasspathData(event.name, event.classpath.getEntries(), event.classpath.getJavaVersion());
362-
IJavaProject oldProject = table.get(uri);
358+
log.warn("Deleted project not removed because uri {} not found in {}", uri, table.keySet());
359+
}
360+
} else {
361+
log.debug("deleted = false");
362+
URI projectUri = new URI(uri);
363+
ClasspathData classpath = new ClasspathData(event.name, event.classpath.getEntries(), event.classpath.getJavaVersion());
364+
IJavaProject oldProject, newProject;
365+
synchronized(table) {
366+
oldProject = table.get(uri);
363367
if (oldProject != null && classpath.equals(oldProject.getClasspath())) {
364368
// nothing has changed
365369
return;
366370
}
367-
IProjectBuild projectBuild = from(event.projectBuild);
368-
IJavaProject newProject = IS_JANDEX_INDEX
371+
IProjectBuild projectBuild = from(event.projectBuild);
372+
newProject = IS_JANDEX_INDEX
369373
? new JavaProject(getFileObserver(), projectUri, classpath,
370374
JdtLsProjectCache.this, projectBuild)
371375
: new JdtLsJavaProject(server.getClient(), projectUri, classpath, JdtLsProjectCache.this, projectBuild);
372376
table.put(uri, newProject);
373-
if (oldProject != null) {
374-
notifyChanged(newProject);
375-
} else {
376-
notifyCreated(newProject);
377-
}
377+
}
378+
if (oldProject != null) {
379+
notifyChanged(newProject);
380+
} else {
381+
notifyCreated(newProject);
378382
}
379383
}
380384
} catch (Exception e) {

0 commit comments

Comments
 (0)