aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/language-web/src/main/java
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2023-08-20 19:41:32 +0200
committerLibravatar Kristóf Marussy <kristof@marussy.com>2023-08-20 20:29:02 +0200
commita3f1e6872f4f768d14899a1e70bbdc14f32e478d (patch)
treeb2daf0c81724f31ee190f5d63eb42988086dabf2 /subprojects/language-web/src/main/java
parentfix: nullary model initialization (diff)
downloadrefinery-a3f1e6872f4f768d14899a1e70bbdc14f32e478d.tar.gz
refinery-a3f1e6872f4f768d14899a1e70bbdc14f32e478d.tar.zst
refinery-a3f1e6872f4f768d14899a1e70bbdc14f32e478d.zip
feat: improve semantics error reporting
Also makes model seeds cancellable to reduce server load during semantic analysis.
Diffstat (limited to 'subprojects/language-web/src/main/java')
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/CancellableSeed.java99
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsInternalErrorResult.java (renamed from subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsErrorResult.java)2
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsIssuesResult.java13
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsResult.java3
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsService.java43
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java44
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocument.java3
7 files changed, 183 insertions, 24 deletions
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/CancellableSeed.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/CancellableSeed.java
new file mode 100644
index 00000000..aa14f39d
--- /dev/null
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/CancellableSeed.java
@@ -0,0 +1,99 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.web.semantics;
7
8import tools.refinery.store.map.AnyVersionedMap;
9import tools.refinery.store.map.Cursor;
10import tools.refinery.store.reasoning.representation.PartialSymbol;
11import tools.refinery.store.reasoning.seed.ModelSeed;
12import tools.refinery.store.reasoning.seed.Seed;
13import tools.refinery.store.tuple.Tuple;
14import tools.refinery.viatra.runtime.CancellationToken;
15
16import java.util.Set;
17
18class CancellableSeed<T> implements Seed<T> {
19 private final CancellationToken cancellationToken;
20 private final Seed<T> seed;
21
22 private CancellableSeed(CancellationToken cancellationToken, Seed<T> seed) {
23 this.cancellationToken = cancellationToken;
24 this.seed = seed;
25 }
26
27 @Override
28 public int arity() {
29 return seed.arity();
30 }
31
32 @Override
33 public Class<T> valueType() {
34 return seed.valueType();
35 }
36
37 @Override
38 public T reducedValue() {
39 return seed.reducedValue();
40 }
41
42 @Override
43 public T get(Tuple key) {
44 return seed.get(key);
45 }
46
47 @Override
48 public Cursor<Tuple, T> getCursor(T defaultValue, int nodeCount) {
49 return new CancellableCursor<>(cancellationToken, seed.getCursor(defaultValue, nodeCount));
50 }
51
52 public static ModelSeed wrap(CancellationToken cancellationToken, ModelSeed modelSeed) {
53 var builder = ModelSeed.builder(modelSeed.getNodeCount());
54 for (var partialSymbol : modelSeed.getSeededSymbols()) {
55 wrap(cancellationToken, (PartialSymbol<?, ?>) partialSymbol, modelSeed, builder);
56 }
57 return builder.build();
58 }
59
60 private static <A, C> void wrap(CancellationToken cancellationToken, PartialSymbol<A, C> partialSymbol,
61 ModelSeed originalModelSeed, ModelSeed.Builder builder) {
62 var originalSeed = originalModelSeed.getSeed(partialSymbol);
63 builder.seed(partialSymbol, new CancellableSeed<>(cancellationToken, originalSeed));
64 }
65
66 private record CancellableCursor<T>(CancellationToken cancellationToken, Cursor<Tuple, T> cursor)
67 implements Cursor<Tuple, T> {
68 @Override
69 public Tuple getKey() {
70 return cursor.getKey();
71 }
72
73 @Override
74 public T getValue() {
75 return cursor.getValue();
76 }
77
78 @Override
79 public boolean isTerminated() {
80 return cursor.isTerminated();
81 }
82
83 @Override
84 public boolean move() {
85 cancellationToken.checkCancelled();
86 return cursor.move();
87 }
88
89 @Override
90 public boolean isDirty() {
91 return cursor.isDirty();
92 }
93
94 @Override
95 public Set<AnyVersionedMap> getDependingMaps() {
96 return cursor.getDependingMaps();
97 }
98 }
99}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsErrorResult.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsInternalErrorResult.java
index ce34ef6c..ff592e93 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsErrorResult.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsInternalErrorResult.java
@@ -5,5 +5,5 @@
5 */ 5 */
6package tools.refinery.language.web.semantics; 6package tools.refinery.language.web.semantics;
7 7
8public record SemanticsErrorResult(String error) implements SemanticsResult { 8public record SemanticsInternalErrorResult(String error) implements SemanticsResult {
9} 9}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsIssuesResult.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsIssuesResult.java
new file mode 100644
index 00000000..644bd179
--- /dev/null
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsIssuesResult.java
@@ -0,0 +1,13 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.web.semantics;
7
8import org.eclipse.xtext.web.server.validation.ValidationResult;
9
10import java.util.List;
11
12public record SemanticsIssuesResult(List<ValidationResult.Issue> issues) implements SemanticsResult {
13}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsResult.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsResult.java
index 92639578..a2e19a2f 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsResult.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsResult.java
@@ -7,5 +7,6 @@ package tools.refinery.language.web.semantics;
7 7
8import org.eclipse.xtext.web.server.IServiceResult; 8import org.eclipse.xtext.web.server.IServiceResult;
9 9
10public sealed interface SemanticsResult extends IServiceResult permits SemanticsSuccessResult, SemanticsErrorResult { 10public sealed interface SemanticsResult extends IServiceResult permits SemanticsSuccessResult,
11 SemanticsInternalErrorResult, SemanticsIssuesResult {
11} 12}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsService.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsService.java
index 39191162..56b2cbc1 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsService.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsService.java
@@ -5,9 +5,11 @@
5 */ 5 */
6package tools.refinery.language.web.semantics; 6package tools.refinery.language.web.semantics;
7 7
8import com.google.gson.JsonObject;
8import com.google.inject.Inject; 9import com.google.inject.Inject;
9import com.google.inject.Provider; 10import com.google.inject.Provider;
10import com.google.inject.Singleton; 11import com.google.inject.Singleton;
12import org.eclipse.xtext.ide.ExecutorServiceProvider;
11import org.eclipse.xtext.service.OperationCanceledManager; 13import org.eclipse.xtext.service.OperationCanceledManager;
12import org.eclipse.xtext.util.CancelIndicator; 14import org.eclipse.xtext.util.CancelIndicator;
13import org.eclipse.xtext.web.server.model.AbstractCachedService; 15import org.eclipse.xtext.web.server.model.AbstractCachedService;
@@ -19,6 +21,7 @@ import org.slf4j.LoggerFactory;
19import tools.refinery.language.model.problem.Problem; 21import tools.refinery.language.model.problem.Problem;
20import tools.refinery.language.web.xtext.server.push.PushWebDocument; 22import tools.refinery.language.web.xtext.server.push.PushWebDocument;
21 23
24import java.util.List;
22import java.util.concurrent.*; 25import java.util.concurrent.*;
23 26
24@Singleton 27@Singleton
@@ -34,7 +37,12 @@ public class SemanticsService extends AbstractCachedService<SemanticsResult> {
34 @Inject 37 @Inject
35 private ValidationService validationService; 38 private ValidationService validationService;
36 39
37 private final ExecutorService executorService = Executors.newCachedThreadPool(); 40 private ExecutorService executorService;
41
42 @Inject
43 public void setExecutorServiceProvider(ExecutorServiceProvider provider) {
44 executorService = provider.get(this.getClass().getName());
45 }
38 46
39 @Override 47 @Override
40 public SemanticsResult compute(IXtextWebDocument doc, CancelIndicator cancelIndicator) { 48 public SemanticsResult compute(IXtextWebDocument doc, CancelIndicator cancelIndicator) {
@@ -42,12 +50,15 @@ public class SemanticsService extends AbstractCachedService<SemanticsResult> {
42 if (LOG.isTraceEnabled()) { 50 if (LOG.isTraceEnabled()) {
43 start = System.currentTimeMillis(); 51 start = System.currentTimeMillis();
44 } 52 }
45 var problem = getProblem(doc, cancelIndicator); 53 if (hasError(doc, cancelIndicator)) {
46 if (problem == null) {
47 return null; 54 return null;
48 } 55 }
56 var problem = getProblem(doc);
57 if (problem == null) {
58 return new SemanticsSuccessResult(List.of(), new JsonObject());
59 }
49 var worker = workerProvider.get(); 60 var worker = workerProvider.get();
50 worker.setProblem(problem,cancelIndicator); 61 worker.setProblem(problem, cancelIndicator);
51 var future = executorService.submit(worker); 62 var future = executorService.submit(worker);
52 SemanticsResult result = null; 63 SemanticsResult result = null;
53 try { 64 try {
@@ -58,11 +69,19 @@ public class SemanticsService extends AbstractCachedService<SemanticsResult> {
58 Thread.currentThread().interrupt(); 69 Thread.currentThread().interrupt();
59 } catch (ExecutionException e) { 70 } catch (ExecutionException e) {
60 operationCanceledManager.propagateAsErrorIfCancelException(e.getCause()); 71 operationCanceledManager.propagateAsErrorIfCancelException(e.getCause());
61 throw new IllegalStateException(e); 72 LOG.debug("Error while computing semantics", e);
73 if (e.getCause() instanceof Error error) {
74 throw error;
75 }
76 String message = e.getMessage();
77 if (message == null) {
78 message = "Partial interpretation error";
79 }
80 return new SemanticsInternalErrorResult(message);
62 } catch (TimeoutException e) { 81 } catch (TimeoutException e) {
63 future.cancel(true); 82 future.cancel(true);
64 LOG.trace("Semantics service timeout", e); 83 LOG.trace("Semantics service timeout", e);
65 return new SemanticsErrorResult("Partial interpretation timed out"); 84 return new SemanticsInternalErrorResult("Partial interpretation timed out");
66 } 85 }
67 if (LOG.isTraceEnabled()) { 86 if (LOG.isTraceEnabled()) {
68 long end = System.currentTimeMillis(); 87 long end = System.currentTimeMillis();
@@ -72,17 +91,17 @@ public class SemanticsService extends AbstractCachedService<SemanticsResult> {
72 return result; 91 return result;
73 } 92 }
74 93
75 @Nullable 94 private boolean hasError(IXtextWebDocument doc, CancelIndicator cancelIndicator) {
76 private Problem getProblem(IXtextWebDocument doc, CancelIndicator cancelIndicator) {
77 if (!(doc instanceof PushWebDocument pushDoc)) { 95 if (!(doc instanceof PushWebDocument pushDoc)) {
78 throw new IllegalArgumentException("Unexpected IXtextWebDocument: " + doc); 96 throw new IllegalArgumentException("Unexpected IXtextWebDocument: " + doc);
79 } 97 }
80 var validationResult = pushDoc.getCachedServiceResult(validationService, cancelIndicator, true); 98 var validationResult = pushDoc.getCachedServiceResult(validationService, cancelIndicator, true);
81 boolean hasError = validationResult.getIssues().stream() 99 return validationResult.getIssues().stream()
82 .anyMatch(issue -> "error".equals(issue.getSeverity())); 100 .anyMatch(issue -> "error".equals(issue.getSeverity()));
83 if (hasError) { 101 }
84 return null; 102
85 } 103 @Nullable
104 private Problem getProblem(IXtextWebDocument doc) {
86 var contents = doc.getResource().getContents(); 105 var contents = doc.getResource().getContents();
87 if (contents.isEmpty()) { 106 if (contents.isEmpty()) {
88 return null; 107 return null;
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java
index 25589260..43d0238c 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java
@@ -8,13 +8,19 @@ package tools.refinery.language.web.semantics;
8import com.google.gson.JsonArray; 8import com.google.gson.JsonArray;
9import com.google.gson.JsonObject; 9import com.google.gson.JsonObject;
10import com.google.inject.Inject; 10import com.google.inject.Inject;
11import org.eclipse.emf.common.util.Diagnostic;
12import org.eclipse.emf.ecore.EObject;
11import org.eclipse.xtext.service.OperationCanceledManager; 13import org.eclipse.xtext.service.OperationCanceledManager;
12import org.eclipse.xtext.util.CancelIndicator; 14import org.eclipse.xtext.util.CancelIndicator;
13import org.slf4j.Logger; 15import org.eclipse.xtext.validation.CheckType;
14import org.slf4j.LoggerFactory; 16import org.eclipse.xtext.validation.FeatureBasedDiagnostic;
17import org.eclipse.xtext.validation.IDiagnosticConverter;
18import org.eclipse.xtext.validation.Issue;
19import org.eclipse.xtext.web.server.validation.ValidationResult;
15import tools.refinery.language.model.problem.Problem; 20import tools.refinery.language.model.problem.Problem;
16import tools.refinery.language.semantics.model.ModelInitializer; 21import tools.refinery.language.semantics.model.ModelInitializer;
17import tools.refinery.language.semantics.model.SemanticsUtils; 22import tools.refinery.language.semantics.model.SemanticsUtils;
23import tools.refinery.language.semantics.model.TracedException;
18import tools.refinery.store.model.Model; 24import tools.refinery.store.model.Model;
19import tools.refinery.store.model.ModelStore; 25import tools.refinery.store.model.ModelStore;
20import tools.refinery.store.query.viatra.ViatraModelQueryAdapter; 26import tools.refinery.store.query.viatra.ViatraModelQueryAdapter;
@@ -22,17 +28,19 @@ import tools.refinery.store.reasoning.ReasoningAdapter;
22import tools.refinery.store.reasoning.ReasoningStoreAdapter; 28import tools.refinery.store.reasoning.ReasoningStoreAdapter;
23import tools.refinery.store.reasoning.literal.Concreteness; 29import tools.refinery.store.reasoning.literal.Concreteness;
24import tools.refinery.store.reasoning.representation.PartialRelation; 30import tools.refinery.store.reasoning.representation.PartialRelation;
31import tools.refinery.store.reasoning.translator.TranslationException;
25import tools.refinery.store.representation.TruthValue; 32import tools.refinery.store.representation.TruthValue;
26import tools.refinery.store.tuple.Tuple; 33import tools.refinery.store.tuple.Tuple;
27import tools.refinery.viatra.runtime.CancellationToken; 34import tools.refinery.viatra.runtime.CancellationToken;
28 35
36import java.util.ArrayList;
29import java.util.Arrays; 37import java.util.Arrays;
30import java.util.List; 38import java.util.List;
31import java.util.TreeMap; 39import java.util.TreeMap;
32import java.util.concurrent.Callable; 40import java.util.concurrent.Callable;
33 41
34class SemanticsWorker implements Callable<SemanticsResult> { 42class SemanticsWorker implements Callable<SemanticsResult> {
35 private static final Logger LOG = LoggerFactory.getLogger(SemanticsWorker.class); 43 private static final String DIAGNOSTIC_ID = "tools.refinery.language.semantics.SemanticError";
36 44
37 @Inject 45 @Inject
38 private SemanticsUtils semanticsUtils; 46 private SemanticsUtils semanticsUtils;
@@ -41,6 +49,9 @@ class SemanticsWorker implements Callable<SemanticsResult> {
41 private OperationCanceledManager operationCanceledManager; 49 private OperationCanceledManager operationCanceledManager;
42 50
43 @Inject 51 @Inject
52 private IDiagnosticConverter diagnosticConverter;
53
54 @Inject
44 private ModelInitializer initializer; 55 private ModelInitializer initializer;
45 56
46 private Problem problem; 57 private Problem problem;
@@ -71,15 +82,17 @@ class SemanticsWorker implements Callable<SemanticsResult> {
71 cancellationToken.checkCancelled(); 82 cancellationToken.checkCancelled();
72 var store = builder.build(); 83 var store = builder.build();
73 cancellationToken.checkCancelled(); 84 cancellationToken.checkCancelled();
74 var model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(modelSeed); 85 var cancellableModelSeed = CancellableSeed.wrap(cancellationToken, modelSeed);
86 var model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(cancellableModelSeed);
75 cancellationToken.checkCancelled(); 87 cancellationToken.checkCancelled();
76 var partialInterpretation = getPartialInterpretation(initializer, model); 88 var partialInterpretation = getPartialInterpretation(initializer, model);
77 89
78 return new SemanticsSuccessResult(nodeTrace, partialInterpretation); 90 return new SemanticsSuccessResult(nodeTrace, partialInterpretation);
79 } catch (RuntimeException e) { 91 } catch (TracedException e) {
80 LOG.debug("Error while computing semantics", e); 92 return getTracedErrorResult(e.getSourceElement(), e.getMessage());
81 var message = e.getMessage(); 93 } catch (TranslationException e) {
82 return new SemanticsErrorResult(message == null ? "Partial interpretation error" : e.getMessage()); 94 var sourceElement = initializer.getInverseTrace(e.getPartialSymbol());
95 return getTracedErrorResult(sourceElement, e.getMessage());
83 } 96 }
84 } 97 }
85 98
@@ -130,4 +143,19 @@ class SemanticsWorker implements Callable<SemanticsResult> {
130 json.add(value.toString()); 143 json.add(value.toString());
131 return json; 144 return json;
132 } 145 }
146
147 private SemanticsResult getTracedErrorResult(EObject sourceElement, String message) {
148 if (sourceElement == null || !problem.eResource().equals(sourceElement.eResource())) {
149 return new SemanticsInternalErrorResult(message);
150 }
151 var diagnostic = new FeatureBasedDiagnostic(Diagnostic.ERROR, message, sourceElement, null, 0,
152 CheckType.EXPENSIVE, DIAGNOSTIC_ID);
153 var xtextIssues = new ArrayList<Issue>();
154 diagnosticConverter.convertValidatorDiagnostic(diagnostic, xtextIssues::add);
155 var issues = xtextIssues.stream()
156 .map(issue -> new ValidationResult.Issue(issue.getMessage(), "error", issue.getLineNumber(),
157 issue.getColumn(), issue.getOffset(), issue.getLength()))
158 .toList();
159 return new SemanticsIssuesResult(issues);
160 }
133} 161}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocument.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocument.java
index 1542c694..2d43fb26 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocument.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocument.java
@@ -53,10 +53,9 @@ public class PushWebDocument extends XtextWebDocument {
53 public <T extends IServiceResult> void precomputeServiceResult(AbstractCachedService<T> service, String serviceName, 53 public <T extends IServiceResult> void precomputeServiceResult(AbstractCachedService<T> service, String serviceName,
54 CancelIndicator cancelIndicator, boolean logCacheMiss) { 54 CancelIndicator cancelIndicator, boolean logCacheMiss) {
55 var serviceClass = service.getClass(); 55 var serviceClass = service.getClass();
56 var previousResult = precomputedServices.get(serviceClass);
57 var result = getCachedServiceResult(service, cancelIndicator, logCacheMiss); 56 var result = getCachedServiceResult(service, cancelIndicator, logCacheMiss);
58 precomputedServices.put(serviceClass, result); 57 precomputedServices.put(serviceClass, result);
59 if (result != null && !result.equals(previousResult)) { 58 if (result != null) {
60 notifyPrecomputationListeners(serviceName, result); 59 notifyPrecomputationListeners(serviceName, result);
61 } 60 }
62 } 61 }