From b7a46b805bd7fbb3b21a48a035698ab11fadcb7c Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Sat, 19 Aug 2023 14:39:39 +0200 Subject: feat: interruptible VIATRA engine Reduce server load by introducing a timeout for semantics analysis. --- .../language/web/semantics/SemanticsService.java | 129 ++++------------- .../language/web/semantics/SemanticsWorker.java | 133 ++++++++++++++++++ .../web/xtext/server/TransactionExecutor.java | 46 ++++-- .../web/xtext/server/push/PushWebDocument.java | 13 +- .../xtext/server/push/PushWebDocumentProvider.java | 9 +- .../language/web/xtext/servlet/XtextWebSocket.java | 14 +- .../query/viatra/ViatraModelQueryBuilder.java | 9 +- .../internal/ViatraModelQueryAdapterImpl.java | 5 + .../internal/ViatraModelQueryBuilderImpl.java | 10 +- .../internal/ViatraModelQueryStoreAdapterImpl.java | 9 +- .../internal/context/RelationalRuntimeContext.java | 9 ++ .../viatra/runtime/rete/network/ReteContainer.java | 38 ++--- .../viatra/runtime/rete/network/StandardNode.java | 18 +-- .../refinery/viatra/runtime/CancellationToken.java | 13 ++ .../matchers/context/IQueryRuntimeContext.java | 156 +++++++++++---------- 15 files changed, 379 insertions(+), 232 deletions(-) create mode 100644 subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/CancellationToken.java (limited to 'subprojects') 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 2495430e..39191162 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,8 +5,6 @@ */ package tools.refinery.language.web.semantics; -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; @@ -14,43 +12,29 @@ import org.eclipse.xtext.service.OperationCanceledManager; import org.eclipse.xtext.util.CancelIndicator; import org.eclipse.xtext.web.server.model.AbstractCachedService; import org.eclipse.xtext.web.server.model.IXtextWebDocument; -import org.eclipse.xtext.web.server.model.XtextWebDocument; import org.eclipse.xtext.web.server.validation.ValidationService; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import tools.refinery.language.model.problem.Problem; -import tools.refinery.language.semantics.model.ModelInitializer; -import tools.refinery.language.semantics.model.SemanticsUtils; -import tools.refinery.store.model.Model; -import tools.refinery.store.model.ModelStore; -import tools.refinery.store.query.viatra.ViatraModelQueryAdapter; -import tools.refinery.store.reasoning.ReasoningAdapter; -import tools.refinery.store.reasoning.ReasoningStoreAdapter; -import tools.refinery.store.reasoning.literal.Concreteness; -import tools.refinery.store.reasoning.representation.PartialRelation; -import tools.refinery.store.representation.TruthValue; -import tools.refinery.store.tuple.Tuple; +import tools.refinery.language.web.xtext.server.push.PushWebDocument; -import java.util.Arrays; -import java.util.List; -import java.util.TreeMap; +import java.util.concurrent.*; @Singleton public class SemanticsService extends AbstractCachedService { private static final Logger LOG = LoggerFactory.getLogger(SemanticsService.class); @Inject - private SemanticsUtils semanticsUtils; + private Provider workerProvider; @Inject - private ValidationService validationService; + private OperationCanceledManager operationCanceledManager; @Inject - private Provider initializerProvider; + private ValidationService validationService; - @Inject - private OperationCanceledManager operationCanceledManager; + private final ExecutorService executorService = Executors.newCachedThreadPool(); @Override public SemanticsResult compute(IXtextWebDocument doc, CancelIndicator cancelIndicator) { @@ -58,44 +42,42 @@ public class SemanticsService extends AbstractCachedService { if (LOG.isTraceEnabled()) { start = System.currentTimeMillis(); } - Problem problem = getProblem(doc, cancelIndicator); + var problem = getProblem(doc, cancelIndicator); if (problem == null) { return null; } - var initializer = initializerProvider.get(); - var builder = ModelStore.builder() - .with(ViatraModelQueryAdapter.builder()) - .with(ReasoningAdapter.builder() - .requiredInterpretations(Concreteness.PARTIAL)); - operationCanceledManager.checkCanceled(cancelIndicator); + var worker = workerProvider.get(); + worker.setProblem(problem,cancelIndicator); + var future = executorService.submit(worker); + SemanticsResult result = null; try { - var modelSeed = initializer.createModel(problem, builder); - operationCanceledManager.checkCanceled(cancelIndicator); - var nodeTrace = getNodeTrace(initializer); - operationCanceledManager.checkCanceled(cancelIndicator); - var store = builder.build(); - operationCanceledManager.checkCanceled(cancelIndicator); - var model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(modelSeed); - operationCanceledManager.checkCanceled(cancelIndicator); - var partialInterpretation = getPartialInterpretation(initializer, model, cancelIndicator); - if (LOG.isTraceEnabled()) { - long end = System.currentTimeMillis(); - LOG.trace("Computed semantics for {} ({}) in {}ms", doc.getResourceId(), doc.getStateId(), - end - start); - } - return new SemanticsSuccessResult(nodeTrace, partialInterpretation); - } catch (RuntimeException e) { - LOG.debug("Error while computing semantics", e); - return new SemanticsErrorResult(e.getMessage()); + result = future.get(2, TimeUnit.SECONDS); + } catch (InterruptedException e) { + future.cancel(true); + LOG.error("Semantics service interrupted", e); + Thread.currentThread().interrupt(); + } catch (ExecutionException e) { + operationCanceledManager.propagateAsErrorIfCancelException(e.getCause()); + throw new IllegalStateException(e); + } catch (TimeoutException e) { + future.cancel(true); + LOG.trace("Semantics service timeout", e); + return new SemanticsErrorResult("Partial interpretation timed out"); } + if (LOG.isTraceEnabled()) { + long end = System.currentTimeMillis(); + LOG.trace("Computed semantics for {} ({}) in {}ms", doc.getResourceId(), doc.getStateId(), + end - start); + } + return result; } @Nullable private Problem getProblem(IXtextWebDocument doc, CancelIndicator cancelIndicator) { - if (!(doc instanceof XtextWebDocument webDoc)) { + if (!(doc instanceof PushWebDocument pushDoc)) { throw new IllegalArgumentException("Unexpected IXtextWebDocument: " + doc); } - var validationResult = webDoc.getCachedServiceResult(validationService, cancelIndicator, true); + var validationResult = pushDoc.getCachedServiceResult(validationService, cancelIndicator, true); boolean hasError = validationResult.getIssues().stream() .anyMatch(issue -> "error".equals(issue.getSeverity())); if (hasError) { @@ -111,53 +93,4 @@ public class SemanticsService extends AbstractCachedService { } return problem; } - - private List getNodeTrace(ModelInitializer initializer) { - var nodeTrace = new String[initializer.getNodeCount()]; - for (var entry : initializer.getNodeTrace().keyValuesView()) { - var node = entry.getOne(); - var index = entry.getTwo(); - nodeTrace[index] = semanticsUtils.getName(node).orElse(null); - } - return Arrays.asList(nodeTrace); - } - - private JsonObject getPartialInterpretation(ModelInitializer initializer, Model model, - CancelIndicator cancelIndicator) { - var adapter = model.getAdapter(ReasoningAdapter.class); - var json = new JsonObject(); - for (var entry : initializer.getRelationTrace().entrySet()) { - var relation = entry.getKey(); - var partialSymbol = entry.getValue(); - var tuples = getTuplesJson(adapter, partialSymbol); - var name = semanticsUtils.getName(relation).orElse(partialSymbol.name()); - json.add(name, tuples); - operationCanceledManager.checkCanceled(cancelIndicator); - } - return json; - } - - private static JsonArray getTuplesJson(ReasoningAdapter adapter, PartialRelation partialSymbol) { - var interpretation = adapter.getPartialInterpretation(Concreteness.PARTIAL, partialSymbol); - var cursor = interpretation.getAll(); - var map = new TreeMap(); - while (cursor.move()) { - map.put(cursor.getKey(), cursor.getValue()); - } - var tuples = new JsonArray(); - for (var entry : map.entrySet()) { - tuples.add(toArray(entry.getKey(), entry.getValue())); - } - return tuples; - } - - private static JsonArray toArray(Tuple tuple, TruthValue value) { - int arity = tuple.getSize(); - var json = new JsonArray(arity + 1); - for (int i = 0; i < arity; i++) { - json.add(tuple.get(i)); - } - json.add(value.toString()); - return json; - } } 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 new file mode 100644 index 00000000..25589260 --- /dev/null +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java @@ -0,0 +1,133 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.language.web.semantics; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.inject.Inject; +import org.eclipse.xtext.service.OperationCanceledManager; +import org.eclipse.xtext.util.CancelIndicator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import tools.refinery.language.model.problem.Problem; +import tools.refinery.language.semantics.model.ModelInitializer; +import tools.refinery.language.semantics.model.SemanticsUtils; +import tools.refinery.store.model.Model; +import tools.refinery.store.model.ModelStore; +import tools.refinery.store.query.viatra.ViatraModelQueryAdapter; +import tools.refinery.store.reasoning.ReasoningAdapter; +import tools.refinery.store.reasoning.ReasoningStoreAdapter; +import tools.refinery.store.reasoning.literal.Concreteness; +import tools.refinery.store.reasoning.representation.PartialRelation; +import tools.refinery.store.representation.TruthValue; +import tools.refinery.store.tuple.Tuple; +import tools.refinery.viatra.runtime.CancellationToken; + +import java.util.Arrays; +import java.util.List; +import java.util.TreeMap; +import java.util.concurrent.Callable; + +class SemanticsWorker implements Callable { + private static final Logger LOG = LoggerFactory.getLogger(SemanticsWorker.class); + + @Inject + private SemanticsUtils semanticsUtils; + + @Inject + private OperationCanceledManager operationCanceledManager; + + @Inject + private ModelInitializer initializer; + + private Problem problem; + + private CancellationToken cancellationToken; + + public void setProblem(Problem problem, CancelIndicator parentIndicator) { + this.problem = problem; + cancellationToken = () -> { + if (Thread.interrupted() || parentIndicator.isCanceled()) { + operationCanceledManager.throwOperationCanceledException(); + } + }; + } + + @Override + public SemanticsResult call() { + var builder = ModelStore.builder() + .with(ViatraModelQueryAdapter.builder() + .cancellationToken(cancellationToken)) + .with(ReasoningAdapter.builder() + .requiredInterpretations(Concreteness.PARTIAL)); + cancellationToken.checkCancelled(); + try { + var modelSeed = initializer.createModel(problem, builder); + cancellationToken.checkCancelled(); + var nodeTrace = getNodeTrace(initializer); + cancellationToken.checkCancelled(); + var store = builder.build(); + cancellationToken.checkCancelled(); + var model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(modelSeed); + cancellationToken.checkCancelled(); + var partialInterpretation = getPartialInterpretation(initializer, model); + + return new SemanticsSuccessResult(nodeTrace, partialInterpretation); + } catch (RuntimeException e) { + LOG.debug("Error while computing semantics", e); + var message = e.getMessage(); + return new SemanticsErrorResult(message == null ? "Partial interpretation error" : e.getMessage()); + } + } + + private List getNodeTrace(ModelInitializer initializer) { + var nodeTrace = new String[initializer.getNodeCount()]; + for (var entry : initializer.getNodeTrace().keyValuesView()) { + var node = entry.getOne(); + var index = entry.getTwo(); + nodeTrace[index] = semanticsUtils.getName(node).orElse(null); + } + return Arrays.asList(nodeTrace); + } + + private JsonObject getPartialInterpretation(ModelInitializer initializer, Model model) { + var adapter = model.getAdapter(ReasoningAdapter.class); + var json = new JsonObject(); + for (var entry : initializer.getRelationTrace().entrySet()) { + var relation = entry.getKey(); + var partialSymbol = entry.getValue(); + var tuples = getTuplesJson(adapter, partialSymbol); + var name = semanticsUtils.getName(relation).orElse(partialSymbol.name()); + json.add(name, tuples); + cancellationToken.checkCancelled(); + } + return json; + } + + private static JsonArray getTuplesJson(ReasoningAdapter adapter, PartialRelation partialSymbol) { + var interpretation = adapter.getPartialInterpretation(Concreteness.PARTIAL, partialSymbol); + var cursor = interpretation.getAll(); + var map = new TreeMap(); + while (cursor.move()) { + map.put(cursor.getKey(), cursor.getValue()); + } + var tuples = new JsonArray(); + for (var entry : map.entrySet()) { + tuples.add(toArray(entry.getKey(), entry.getValue())); + } + return tuples; + } + + private static JsonArray toArray(Tuple tuple, TruthValue value) { + int arity = tuple.getSize(); + var json = new JsonArray(arity + 1); + for (int i = 0; i < arity; i++) { + json.add(tuple.get(i)); + } + json.add(value.toString()); + return json; + } +} diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/TransactionExecutor.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/TransactionExecutor.java index 2c0e9329..74456604 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/TransactionExecutor.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/TransactionExecutor.java @@ -42,6 +42,8 @@ public class TransactionExecutor implements IDisposable, PrecomputationListener private final List pendingPushMessages = new ArrayList<>(); + private volatile boolean disposed; + public TransactionExecutor(ISession session, IResourceServiceProvider.Registry resourceServiceProviderRegistry) { this.session = session; this.resourceServiceProviderRegistry = resourceServiceProviderRegistry; @@ -52,10 +54,13 @@ public class TransactionExecutor implements IDisposable, PrecomputationListener } public void handleRequest(XtextWebRequest request) throws ResponseHandlerException { + if (disposed) { + return; + } var serviceContext = new SimpleServiceContext(session, request.getRequestData()); var ping = serviceContext.getParameter("ping"); if (ping != null) { - responseHandler.onResponse(new XtextWebOkResponse(request, new PongResult(ping))); + onResponse(new XtextWebOkResponse(request, new PongResult(ping))); return; } synchronized (callPendingLock) { @@ -72,23 +77,36 @@ public class TransactionExecutor implements IDisposable, PrecomputationListener var serviceDispatcher = injector.getInstance(XtextServiceDispatcher.class); var service = serviceDispatcher.getService(new SubscribingServiceContext(serviceContext, this)); var serviceResult = service.getService().apply(); - responseHandler.onResponse(new XtextWebOkResponse(request, serviceResult)); + onResponse(new XtextWebOkResponse(request, serviceResult)); } catch (InvalidRequestException e) { - responseHandler.onResponse(new XtextWebErrorResponse(request, XtextWebErrorKind.REQUEST_ERROR, e)); + onResponse(new XtextWebErrorResponse(request, XtextWebErrorKind.REQUEST_ERROR, e)); } catch (RuntimeException e) { - responseHandler.onResponse(new XtextWebErrorResponse(request, XtextWebErrorKind.SERVER_ERROR, e)); + onResponse(new XtextWebErrorResponse(request, XtextWebErrorKind.SERVER_ERROR, e)); } finally { - synchronized (callPendingLock) { - for (var message : pendingPushMessages) { - try { - responseHandler.onResponse(message); - } catch (ResponseHandlerException | RuntimeException e) { - LOG.error("Error while flushing push message", e); - } + flushPendingPushMessages(); + } + } + + private void onResponse(XtextWebResponse response) throws ResponseHandlerException { + if (!disposed) { + responseHandler.onResponse(response); + } + } + + private void flushPendingPushMessages() { + synchronized (callPendingLock) { + for (var message : pendingPushMessages) { + if (disposed) { + return; + } + try { + responseHandler.onResponse(message); + } catch (ResponseHandlerException | RuntimeException e) { + LOG.error("Error while flushing push message", e); } - pendingPushMessages.clear(); - callPending = false; } + pendingPushMessages.clear(); + callPending = false; } } @@ -164,10 +182,12 @@ public class TransactionExecutor implements IDisposable, PrecomputationListener @Override public void dispose() { + disposed = true; for (var subscription : subscriptions.values()) { var document = subscription.get(); if (document != null) { document.removePrecomputationListener(this); + document.cancelBackgroundWork(); } } } 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 dfbd4878..1542c694 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 @@ -27,11 +27,11 @@ public class PushWebDocument extends XtextWebDocument { private final Map, IServiceResult> precomputedServices = new HashMap<>(); + private final DocumentSynchronizer synchronizer; + public PushWebDocument(String resourceId, DocumentSynchronizer synchronizer) { super(resourceId, synchronizer); - if (resourceId == null) { - throw new IllegalArgumentException("resourceId must not be null"); - } + this.synchronizer = synchronizer; } public void addPrecomputationListener(PrecomputationListener listener) { @@ -63,6 +63,9 @@ public class PushWebDocument extends XtextWebDocument { private void notifyPrecomputationListeners(String serviceName, T result) { var resourceId = getResourceId(); + if (resourceId == null) { + return; + } var stateId = getStateId(); List copyOfListeners; synchronized (precomputationListeners) { @@ -83,4 +86,8 @@ public class PushWebDocument extends XtextWebDocument { } } } + + public void cancelBackgroundWork() { + synchronizer.setCanceled(true); + } } diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentProvider.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentProvider.java index b6f4fb43..ec6204ef 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentProvider.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentProvider.java @@ -27,12 +27,7 @@ public class PushWebDocumentProvider implements IWebDocumentProvider { @Override public XtextWebDocument get(String resourceId, IServiceContext serviceContext) { - if (resourceId == null) { - return new XtextWebDocument(null, synchronizerProvider.get()); - } else { - // We only need to send push messages if a resourceId is specified. - return new PushWebDocument(resourceId, - serviceContext.getSession().get(DocumentSynchronizer.class, () -> this.synchronizerProvider.get())); - } + return new PushWebDocument(resourceId, + serviceContext.getSession().get(DocumentSynchronizer.class, () -> this.synchronizerProvider.get())); } } diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextWebSocket.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextWebSocket.java index 043d318c..923fecd6 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextWebSocket.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextWebSocket.java @@ -70,10 +70,11 @@ public class XtextWebSocket implements ResponseHandler { @OnWebSocketError public void onError(Throwable error) { + executor.dispose(); if (webSocketSession == null) { return; } - LOG.error("Internal websocket error in connection from" + webSocketSession.getRemoteSocketAddress(), error); + LOG.error("Internal websocket error in connection from " + webSocketSession.getRemoteSocketAddress(), error); } @OnWebSocketMessage @@ -86,14 +87,18 @@ public class XtextWebSocket implements ResponseHandler { try { request = gson.fromJson(reader, XtextWebRequest.class); } catch (JsonIOException e) { - LOG.error("Cannot read from websocket from" + webSocketSession.getRemoteSocketAddress(), e); + LOG.error("Cannot read from websocket from " + webSocketSession.getRemoteSocketAddress(), e); if (webSocketSession.isOpen()) { + executor.dispose(); webSocketSession.close(StatusCode.SERVER_ERROR, "Cannot read payload", Callback.NOOP); } return; } catch (JsonParseException e) { - LOG.warn("Malformed websocket request from" + webSocketSession.getRemoteSocketAddress(), e); - webSocketSession.close(XtextStatusCode.INVALID_JSON, "Invalid JSON payload", Callback.NOOP); + LOG.warn("Malformed websocket request from " + webSocketSession.getRemoteSocketAddress(), e); + if (webSocketSession.isOpen()) { + executor.dispose(); + webSocketSession.close(XtextStatusCode.INVALID_JSON, "Invalid JSON payload", Callback.NOOP); + } return; } try { @@ -101,6 +106,7 @@ public class XtextWebSocket implements ResponseHandler { } catch (ResponseHandlerException e) { LOG.warn("Cannot write websocket response", e); if (webSocketSession.isOpen()) { + executor.dispose(); webSocketSession.close(StatusCode.SERVER_ERROR, "Cannot write response", Callback.NOOP); } } diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryBuilder.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryBuilder.java index d31325f1..6b3be115 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryBuilder.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryBuilder.java @@ -5,14 +5,15 @@ */ package tools.refinery.store.query.viatra; -import tools.refinery.viatra.runtime.api.ViatraQueryEngineOptions; -import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactory; -import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; import tools.refinery.store.model.ModelStore; import tools.refinery.store.query.ModelQueryBuilder; import tools.refinery.store.query.dnf.AnyQuery; import tools.refinery.store.query.dnf.Dnf; import tools.refinery.store.query.rewriter.DnfRewriter; +import tools.refinery.viatra.runtime.CancellationToken; +import tools.refinery.viatra.runtime.api.ViatraQueryEngineOptions; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactory; +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; import java.util.Collection; import java.util.function.Function; @@ -29,6 +30,8 @@ public interface ViatraModelQueryBuilder extends ModelQueryBuilder { ViatraModelQueryBuilder searchBackend(IQueryBackendFactory queryBackendFactory); + ViatraModelQueryBuilder cancellationToken(CancellationToken cancellationToken); + @Override default ViatraModelQueryBuilder queries(AnyQuery... queries) { ModelQueryBuilder.super.queries(queries); diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryAdapterImpl.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryAdapterImpl.java index f1209f69..ad754988 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryAdapterImpl.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryAdapterImpl.java @@ -18,6 +18,7 @@ import tools.refinery.store.query.viatra.ViatraModelQueryAdapter; import tools.refinery.store.query.viatra.internal.matcher.FunctionalViatraMatcher; import tools.refinery.store.query.viatra.internal.matcher.RawPatternMatcher; import tools.refinery.store.query.viatra.internal.matcher.RelationalViatraMatcher; +import tools.refinery.viatra.runtime.CancellationToken; import tools.refinery.viatra.runtime.api.AdvancedViatraQueryEngine; import tools.refinery.viatra.runtime.api.GenericQueryGroup; import tools.refinery.viatra.runtime.api.IQuerySpecification; @@ -81,6 +82,10 @@ public class ViatraModelQueryAdapterImpl implements ViatraModelQueryAdapter, Mod return storeAdapter; } + public CancellationToken getCancellationToken() { + return storeAdapter.getCancellationToken(); + } + @Override public ResultSet getResultSet(Query query) { var canonicalQuery = storeAdapter.getCanonicalQuery(query); diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryBuilderImpl.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryBuilderImpl.java index cfdc43ba..bb0630f3 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryBuilderImpl.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryBuilderImpl.java @@ -17,6 +17,7 @@ import tools.refinery.store.query.viatra.ViatraModelQueryBuilder; import tools.refinery.store.query.viatra.internal.localsearch.FlatCostFunction; import tools.refinery.store.query.viatra.internal.matcher.RawPatternMatcher; import tools.refinery.store.query.viatra.internal.pquery.Dnf2PQuery; +import tools.refinery.viatra.runtime.CancellationToken; import tools.refinery.viatra.runtime.api.IQuerySpecification; import tools.refinery.viatra.runtime.api.ViatraQueryEngineOptions; import tools.refinery.viatra.runtime.localsearch.matcher.integration.LocalSearchGenericBackendFactory; @@ -35,6 +36,7 @@ public class ViatraModelQueryBuilderImpl extends AbstractModelAdapterBuilder queries = new LinkedHashSet<>(); @@ -84,6 +86,12 @@ public class ViatraModelQueryBuilderImpl extends AbstractModelAdapterBuilder queries) { checkNotConfigured(); @@ -136,7 +144,7 @@ public class ViatraModelQueryBuilderImpl extends AbstractModelAdapterBuilder> querySpecifications; private final Set vacuousQueries; private final Set allQueries; + private final CancellationToken cancellationToken; ViatraModelQueryStoreAdapterImpl(ModelStore store, ViatraQueryEngineOptions engineOptions, Map inputKeys, Map canonicalQueryMap, Map> querySpecifications, - Set vacuousQueries) { + Set vacuousQueries, CancellationToken cancellationToken) { this.store = store; this.engineOptions = engineOptions; this.inputKeys = inputKeys; this.canonicalQueryMap = canonicalQueryMap; this.querySpecifications = querySpecifications; this.vacuousQueries = vacuousQueries; + this.cancellationToken = cancellationToken; var mutableAllQueries = new LinkedHashSet(querySpecifications.size() + vacuousQueries.size()); mutableAllQueries.addAll(querySpecifications.keySet()); mutableAllQueries.addAll(vacuousQueries); @@ -62,6 +65,10 @@ public class ViatraModelQueryStoreAdapterImpl implements ViatraModelQueryStoreAd return allQueries; } + public CancellationToken getCancellationToken() { + return cancellationToken; + } + @Override public Query getCanonicalQuery(Query query) { // We know that canonical forms of queries do not change output types. diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalRuntimeContext.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalRuntimeContext.java index d1fa5239..dadab5dd 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalRuntimeContext.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalRuntimeContext.java @@ -5,6 +5,7 @@ */ package tools.refinery.store.query.viatra.internal.context; +import tools.refinery.viatra.runtime.CancellationToken; import tools.refinery.viatra.runtime.matchers.context.*; import tools.refinery.viatra.runtime.matchers.tuple.ITuple; import tools.refinery.viatra.runtime.matchers.tuple.Tuple; @@ -32,10 +33,13 @@ public class RelationalRuntimeContext implements IQueryRuntimeContext { private final Model model; + private final CancellationToken cancellationToken; + RelationalRuntimeContext(ViatraModelQueryAdapterImpl adapter) { model = adapter.getModel(); metaContext = new RelationalQueryMetaContext(adapter.getStoreAdapter().getInputKeys()); modelUpdateListener = new ModelUpdateListener(adapter); + cancellationToken = adapter.getCancellationToken(); } @Override @@ -192,4 +196,9 @@ public class RelationalRuntimeContext implements IQueryRuntimeContext { public void executeAfterTraversal(Runnable runnable) { runnable.run(); } + + @Override + public CancellationToken getCancellationToken() { + return cancellationToken; + } } diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ReteContainer.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ReteContainer.java index 16e290fd..79e0526d 100644 --- a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ReteContainer.java +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ReteContainer.java @@ -1,26 +1,17 @@ /******************************************************************************* * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * Copyright (c) 2023 The Refinery Authors * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-v20.html. - * + * * SPDX-License-Identifier: EPL-2.0 *******************************************************************************/ package tools.refinery.viatra.runtime.rete.network; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Deque; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.LinkedList; -import java.util.Map; -import java.util.Set; -import java.util.function.Function; - import org.apache.log4j.Logger; +import tools.refinery.viatra.runtime.CancellationToken; import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext; import tools.refinery.viatra.runtime.matchers.tuple.Tuple; import tools.refinery.viatra.runtime.matchers.util.Clearable; @@ -42,6 +33,9 @@ import tools.refinery.viatra.runtime.rete.single.SingleInputNode; import tools.refinery.viatra.runtime.rete.single.TrimmerNode; import tools.refinery.viatra.runtime.rete.util.Options; +import java.util.*; +import java.util.function.Function; + /** * @author Gabor Bergmann * @@ -79,6 +73,8 @@ public final class ReteContainer { protected final TimelyConfiguration timelyConfiguration; + private final CancellationToken cancellationToken; + /** * @param threaded * false if operating in a single-threaded environment @@ -88,6 +84,7 @@ public final class ReteContainer { this.network = network; this.backendContext = network.getEngine().getBackendContext(); this.timelyConfiguration = network.getEngine().getTimelyConfiguration(); + cancellationToken = backendContext.getRuntimeContext().getCancellationToken(); this.delayedCommandQueue = new LinkedHashSet(); this.delayedCommandBuffer = new LinkedHashSet(); @@ -395,10 +392,10 @@ public final class ReteContainer { /** * Retrieves a safe copy of the contents of a supplier. - * + * *

Note that there may be multiple copies of a Tuple in case of a {@link TrimmerNode}, so the result is not always a set. - * - * @param flush if true, a flush is performed before pulling the contents + * + * @param flush if true, a flush is performed before pulling the contents * @since 2.3 */ public Collection pullContents(final Supplier supplier, final boolean flush) { @@ -424,7 +421,7 @@ public final class ReteContainer { /** * Retrieves the contents of a SingleInputNode's parentage. - * + * * @since 2.3 */ public Collection pullPropagatedContents(final SingleInputNode supplier, final boolean flush) { @@ -438,7 +435,7 @@ public final class ReteContainer { /** * Retrieves the timestamp-aware contents of a SingleInputNode's parentage. - * + * * @since 2.3 */ public Map> pullPropagatedContentsWithTimestamp(final SingleInputNode supplier, @@ -541,7 +538,7 @@ public final class ReteContainer { /** * Sends out all pending messages to their receivers. The delivery is governed by the communication tracker. - * + * * @since 1.6 */ public void deliverMessagesSingleThreaded() { @@ -620,7 +617,7 @@ public final class ReteContainer { /** * Returns an addressed node at this container. - * + * * @pre: address.container == this, e.g. address MUST be local * @throws IllegalArgumentException * if address is non-local @@ -726,4 +723,7 @@ public final class ReteContainer { return network.getInputConnector(); } + public void checkCancelled() { + cancellationToken.checkCancelled(); + } } diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/StandardNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/StandardNode.java index e7ec36dc..7dc7c4bc 100644 --- a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/StandardNode.java +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/StandardNode.java @@ -1,19 +1,15 @@ /******************************************************************************* * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * Copyright (c) 2023 The Refinery Authors * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-v20.html. - * + * * SPDX-License-Identifier: EPL-2.0 *******************************************************************************/ package tools.refinery.viatra.runtime.rete.network; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - import tools.refinery.viatra.runtime.matchers.tuple.Tuple; import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; @@ -24,11 +20,16 @@ import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; import tools.refinery.viatra.runtime.rete.traceability.TraceInfo; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + /** * Base implementation for a supplier node. - * + * * @author Gabor Bergmann - * + * */ public abstract class StandardNode extends BaseNode implements Supplier, NetworkStructureChangeSensitiveNode { protected final List children = CollectionsFactory.createObserverList(); @@ -45,6 +46,7 @@ public abstract class StandardNode extends BaseNode implements Supplier, Network * @since 2.4 */ protected void propagateUpdate(final Direction direction, final Tuple updateElement, final Timestamp timestamp) { + reteContainer.checkCancelled(); for (final Mailbox childMailbox : childMailboxes) { childMailbox.postMessage(direction, updateElement, timestamp); } diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/CancellationToken.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/CancellationToken.java new file mode 100644 index 00000000..a2ae41e3 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/CancellationToken.java @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.viatra.runtime; + +@FunctionalInterface +public interface CancellationToken { + CancellationToken NONE = () -> {}; + + void checkCancelled(); +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryRuntimeContext.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryRuntimeContext.java index c2e90614..61359c1b 100644 --- a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryRuntimeContext.java +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryRuntimeContext.java @@ -1,82 +1,84 @@ /******************************************************************************* * Copyright (c) 2010-2015, Bergmann Gabor, Istvan Rath and Daniel Varro + * Copyright (c) 2023 The Refinery Authors * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-v20.html. - * + * * SPDX-License-Identifier: EPL-2.0 *******************************************************************************/ package tools.refinery.viatra.runtime.matchers.context; -import java.lang.reflect.InvocationTargetException; -import java.util.Optional; -import java.util.concurrent.Callable; - +import tools.refinery.viatra.runtime.CancellationToken; import tools.refinery.viatra.runtime.matchers.planning.helpers.StatisticsHelper; import tools.refinery.viatra.runtime.matchers.tuple.ITuple; import tools.refinery.viatra.runtime.matchers.tuple.Tuple; import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; import tools.refinery.viatra.runtime.matchers.util.Accuracy; +import java.lang.reflect.InvocationTargetException; +import java.util.Optional; +import java.util.concurrent.Callable; + /** * Provides instance model information (relations corresponding to input keys) to query evaluator backends at runtime. * Implementors shall extend {@link AbstractQueryRuntimeContext} instead directly this interface. - * + * * @author Bergmann Gabor * @noimplement This interface is not intended to be implemented by clients. Extend {@link AbstractQueryRuntimeContext} instead. */ public interface IQueryRuntimeContext { - /** + /** * Provides metamodel-specific info independent of the runtime instance model. */ public IQueryMetaContext getMetaContext(); - - + + /** * The given callable will be executed, and all model traversals will be delayed until the execution is done. If * there are any outstanding information to be read from the model, a single coalesced model traversal will * initialize the caches and deliver the notifications. - * + * *

Calls may be nested. A single coalesced traversal will happen at the end of the outermost call. - * - *

Caution: results returned by the runtime context may be incomplete during the coalescing period, to be corrected by notifications sent during the final coalesced traversal. - * For example, if a certain input key is not cached yet, an empty relation may be reported during callable.call(); the cache will be constructed after the call terminates and notifications will deliver the entire content of the relation. + * + *

Caution: results returned by the runtime context may be incomplete during the coalescing period, to be corrected by notifications sent during the final coalesced traversal. + * For example, if a certain input key is not cached yet, an empty relation may be reported during callable.call(); the cache will be constructed after the call terminates and notifications will deliver the entire content of the relation. * Non-incremental query backends should therefore never enumerate input keys while coalesced (verify using {@link #isCoalescing()}). - * + * * @param callable */ - public abstract V coalesceTraversals(Callable callable) throws InvocationTargetException; + public abstract V coalesceTraversals(Callable callable) throws InvocationTargetException; /** * @return true iff currently within a coalescing section (i.e. within the callable of a call to {@link #coalesceTraversals(Callable)}). */ public boolean isCoalescing(); - + /** * Returns true if index is available for the given key providing the given service. * @throws IllegalArgumentException if key is not enumerable or an unknown type, see {@link IQueryMetaContext#isEnumerable(IInputKey)}. * @since 1.4 */ public boolean isIndexed(IInputKey key, IndexingService service); - + /** - * If the given (enumerable) input key is not yet indexed, the model will be traversed - * (after the end of the outermost coalescing block, see {@link IQueryRuntimeContext#coalesceTraversals(Callable)}) + * If the given (enumerable) input key is not yet indexed, the model will be traversed + * (after the end of the outermost coalescing block, see {@link IQueryRuntimeContext#coalesceTraversals(Callable)}) * so that the index can be built. It is possible that the base indexer will select a higher indexing level merging * multiple indexing requests to an appropriate level. - * + * *

Postcondition: After invoking this method, {@link #getIndexed(IInputKey, IndexingService)} for the same key * and service will be guaranteed to return the requested or a highing indexing level as soon as {@link #isCoalescing()} first returns false. - * + * *

Precondition: the given key is enumerable, see {@link IQueryMetaContext#isEnumerable(IInputKey)}. * @throws IllegalArgumentException if key is not enumerable or an unknown type, see {@link IQueryMetaContext#isEnumerable(IInputKey)}. * @since 1.4 */ public void ensureIndexed(IInputKey key, IndexingService service); - + /** * Returns the number of tuples in the extensional relation identified by the input key seeded with the given mask and tuple. - * - * @param key an input key + * + * @param key an input key * @param seedMask * a mask that extracts those parameters of the input key (from the entire parameter list) that should be * bound to a fixed value; must not be null. Note: any given index must occur at most once in seedMask. @@ -84,59 +86,59 @@ public interface IQueryRuntimeContext { * the tuple of fixed values restricting the match set to be considered, in the same order as given in * parameterSeedMask, so that for each considered match tuple, * projectedParameterSeed.equals(parameterSeedMask.transform(match)) should hold. Must not be null. - * + * * @return the number of tuples in the model for the given key and seed - * + * *

Precondition: the given key is enumerable, see {@link IQueryMetaContext#isEnumerable(IInputKey)}. * @throws IllegalArgumentException if key is not enumerable, see {@link IQueryMetaContext#isEnumerable(IInputKey)}. * @since 1.7 */ public int countTuples(IInputKey key, TupleMask seedMask, ITuple seed); - - + + /** * Gives an estimate of the number of different groups the tuples of the given relation are projected into by the given mask * (e.g. for an identity mask, this means the full relation size). The estimate must meet the required accuracy. - * - *

Must accept any input key, even non-enumerables or those not recognized by this runtime context. - * If there is insufficient information to provide an answer up to the required precision, {@link Optional#empty()} is returned. - * + * + *

Must accept any input key, even non-enumerables or those not recognized by this runtime context. + * If there is insufficient information to provide an answer up to the required precision, {@link Optional#empty()} is returned. + * *

PRE: {@link TupleMask#isNonrepeating()} must hold for the group mask. - * + * * @return if available, an estimate of the cardinality of the projection of the given extensional relation, with the desired accuracy. - * + * * @since 2.1 */ public Optional estimateCardinality(IInputKey key, TupleMask groupMask, Accuracy requiredAccuracy); - - + + /** * Gives an estimate of the average size of different groups the tuples of the given relation are projected into by the given mask - * (e.g. for an identity mask, this means 1, while for an empty mask, the result is the full relation size). + * (e.g. for an identity mask, this means 1, while for an empty mask, the result is the full relation size). * The estimate must meet the required accuracy. - * - *

Must accept any input key, even non-enumerables or those not recognized by this runtime context. + * + *

Must accept any input key, even non-enumerables or those not recognized by this runtime context. * If there is insufficient information to provide an answer up to the required precision, {@link Optional#empty()} may be returned. - * + * *

For an empty relation, zero is acceptable as an exact answer. - * + * *

PRE: {@link TupleMask#isNonrepeating()} must hold for the group mask. - * + * * @return if available, an estimate of the average size of each projection group of the given extensional relation, with the desired accuracy. - * + * * @since 2.1 */ public default Optional estimateAverageBucketSize(IInputKey key, TupleMask groupMask, Accuracy requiredAccuracy) { if (key.isEnumerable()) { - return StatisticsHelper.estimateAverageBucketSize(groupMask, requiredAccuracy, + return StatisticsHelper.estimateAverageBucketSize(groupMask, requiredAccuracy, (mask, accuracy) -> this.estimateCardinality(key, mask, accuracy)); } else return groupMask.isIdentity() ? Optional.of(1.0) : Optional.empty(); } - - + + /** * Returns the tuples in the extensional relation identified by the input key, optionally seeded with the given tuple. - * + * * @param key an input key * @param seedMask * a mask that extracts those parameters of the input key (from the entire parameter list) that should be @@ -144,23 +146,23 @@ public interface IQueryRuntimeContext { * @param seed * the tuple of fixed values restricting the match set to be considered, in the same order as given in * parameterSeedMask, so that for each considered match tuple, - * projectedParameterSeed.equals(parameterSeedMask.transform(match)) should hold. Must not be null. + * projectedParameterSeed.equals(parameterSeedMask.transform(match)) should hold. Must not be null. * @return the tuples in the model for the given key and seed - * + * *

Precondition: the given key is enumerable, see {@link IQueryMetaContext#isEnumerable(IInputKey)}. * @throws IllegalArgumentException if key is not enumerable, see {@link IQueryMetaContext#isEnumerable(IInputKey)}. * @since 1.7 */ public Iterable enumerateTuples(IInputKey key, TupleMask seedMask, ITuple seed); - + /** * Simpler form of {@link #enumerateTuples(IInputKey, TupleMask, Tuple)} in the case where all values of the tuples * are bound by the seed except for one. - * + * *

* Selects the tuples in the extensional relation identified by the input key, optionally seeded with the given * tuple, and then returns the single value from each tuple which is not bound by the ssed mask. - * + * * @param key * an input key * @param seedMask @@ -172,7 +174,7 @@ public interface IQueryRuntimeContext { * parameterSeedMask, so that for each considered match tuple, * projectedParameterSeed.equals(parameterSeedMask.transform(match)) should hold. Must not be null. * @return the objects in the model for the given key and seed - * + * *

* Precondition: the given key is enumerable, see {@link IQueryMetaContext#isEnumerable(IInputKey)}. * @throws IllegalArgumentException @@ -180,17 +182,17 @@ public interface IQueryRuntimeContext { * @since 1.7 */ public Iterable enumerateValues(IInputKey key, TupleMask seedMask, ITuple seed); - + /** * Simpler form of {@link #enumerateTuples(IInputKey, TupleMask, Tuple)} in the case where all values of the tuples * are bound by the seed. - * + * *

* Returns whether the given tuple is in the extensional relation identified by the input key. - * + * *

* Note: this call works for non-enumerable input keys as well. - * + * * @param key * an input key * @param seed @@ -202,31 +204,31 @@ public interface IQueryRuntimeContext { */ public boolean containsTuple(IInputKey key, ITuple seed); - + /** * Subscribes for updates in the extensional relation identified by the input key, optionally seeded with the given tuple. - *

This should be called after invoking - * + *

This should be called after invoking + * * @param key an input key - * @param seed can be null or a tuple with matching arity; - * if non-null, only those updates in the model are notified about - * that match the seed at positions where the seed is non-null. + * @param seed can be null or a tuple with matching arity; + * if non-null, only those updates in the model are notified about + * that match the seed at positions where the seed is non-null. * @param listener will be notified of future changes - * + * *

Precondition: the given key is enumerable, see {@link IQueryMetaContext#isEnumerable(IInputKey)}. * @throws IllegalArgumentException if key is not enumerable, see {@link IQueryMetaContext#isEnumerable(IInputKey)}. */ public void addUpdateListener(IInputKey key, Tuple seed, IQueryRuntimeContextListener listener); - + /** * Unsubscribes from updates in the extensional relation identified by the input key, optionally seeded with the given tuple. - * + * * @param key an input key - * @param seed can be null or a tuple with matching arity; - * if non-null, only those updates in the model are notified about - * that match the seed at positions where the seed is non-null. + * @param seed can be null or a tuple with matching arity; + * if non-null, only those updates in the model are notified about + * that match the seed at positions where the seed is non-null. * @param listener will no longer be notified of future changes - * + * *

Precondition: the given key is enumerable, see {@link IQueryMetaContext#isEnumerable(IInputKey)}. * @throws IllegalArgumentException if key is not enumerable, see {@link IQueryMetaContext#isEnumerable(IInputKey)}. */ @@ -234,16 +236,16 @@ public interface IQueryRuntimeContext { /* TODO: uniqueness */ - + /** - * Wraps the external element into the internal representation that is to be used by the query backend + * Wraps the external element into the internal representation that is to be used by the query backend *

model element -> internal object. *

null must be mapped to null. */ public Object wrapElement(Object externalElement); /** - * Unwraps the internal representation of the element into its original form + * Unwraps the internal representation of the element into its original form *

internal object -> model element *

null must be mapped to null. */ @@ -269,13 +271,17 @@ public interface IQueryRuntimeContext { * @since 1.4 */ public void ensureWildcardIndexing(IndexingService service); - + /** * Execute the given runnable after traversal. It is guaranteed that the runnable is executed as soon as * the indexing is finished. The callback is executed only once, then is removed from the callback queue. * @param traversalCallback - * @throws InvocationTargetException + * @throws InvocationTargetException * @since 1.4 */ public void executeAfterTraversal(Runnable runnable) throws InvocationTargetException; + + default CancellationToken getCancellationToken() { + return CancellationToken.NONE; + } } -- cgit v1.2.3-70-g09d2