From c7a86623b1589a3bd68a84a8d54a1eadc1aacefb Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Sun, 10 Sep 2023 23:07:11 +0200 Subject: fix: VIATRA projection indexer error When a projection indexer is constructed for a production node, the projection memory is only populated if changes are being propagated. The cache doesn't get populated even if changes are flushed afterwards. This not only returns invalid query results, but also a duplicate deletion exception will be thrown when the production node tries to delete a tuple from the index memory. To counteract this issue, we enable update propagation while a matcher (and its associated indexers) are being created. --- .../semantics/model/CountPropagationTest.java | 11 +-- .../semantics/model/ModelGenerationTest.java | 91 +++++++++++++++++++--- .../viatra/internal/pquery/TermEvaluator.java | 4 +- .../update/TupleChangingViewUpdateListener.java | 9 ++- .../viatra/runtime/api/ViatraQueryEngine.java | 3 + .../internal/apiimpl/ViatraQueryEngineImpl.java | 51 ++++++++---- 6 files changed, 129 insertions(+), 40 deletions(-) (limited to 'subprojects') diff --git a/subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/model/CountPropagationTest.java b/subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/model/CountPropagationTest.java index a383d043..eee2c4ae 100644 --- a/subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/model/CountPropagationTest.java +++ b/subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/model/CountPropagationTest.java @@ -67,15 +67,12 @@ class CountPropagationTest { .put(Tuple.of(3), TruthValue.TRUE)) .build(); - var model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(modelSeed); + var initialModel = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(modelSeed); + var initialState = initialModel.commit(); + + var model = store.createModelForState(initialState); var reasoningAdapter = model.getAdapter(ReasoningAdapter.class); var propagationAdapter = model.getAdapter(PropagationAdapter.class); - model.commit(); - - reasoningAdapter.split(0); - assertThat(propagationAdapter.propagate(), is(PropagationResult.UNCHANGED)); - model.commit(); - reasoningAdapter.split(0); assertThat(propagationAdapter.propagate(), is(PropagationResult.UNCHANGED)); } diff --git a/subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/model/ModelGenerationTest.java b/subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/model/ModelGenerationTest.java index 779e18ab..ecd5d39c 100644 --- a/subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/model/ModelGenerationTest.java +++ b/subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/model/ModelGenerationTest.java @@ -122,30 +122,30 @@ class ModelGenerationTest { abstract class Vertex { container Region[0..1] region opposite vertices - Transition[] outgoingTransition opposite source + contains Transition[] outgoingTransition opposite source Transition[] incomingTransition opposite target } class Transition { - Vertex source opposite outgoingTransition + container Vertex[0..1] source opposite outgoingTransition Vertex target opposite incomingTransition } - abstract class Pseudostate extends Vertex {} + abstract class Pseudostate extends Vertex. - abstract class RegularState extends Vertex {} + abstract class RegularState extends Vertex. - class Entry extends Pseudostate {} + class Entry extends Pseudostate. - class Exit extends Pseudostate {} + class Exit extends Pseudostate. - class Choice extends Pseudostate {} + class Choice extends Pseudostate. - class FinalState extends RegularState {} + class FinalState extends RegularState. - class State extends RegularState, CompositeElement {} + class State extends RegularState, CompositeElement. - class Statechart extends CompositeElement {} + class Statechart extends CompositeElement. // Constraints @@ -209,7 +209,74 @@ class ModelGenerationTest { error choiceHasNoIncoming(Choice c) <-> !target(_, c). - scope node = 50..60, Statechart = 1. + scope node = 50..60, Region = 5..10, Statechart = 1. + """); + assertThat(parsedProblem.errors(), empty()); + var problem = parsedProblem.problem(); + + var storeBuilder = ModelStore.builder() + .with(ViatraModelQueryAdapter.builder()) +// .with(ModelVisualizerAdapter.builder() +// .withOutputPath("test_output") +// .withFormat(FileFormat.DOT) +// .withFormat(FileFormat.SVG) +// .saveStates() +// .saveDesignSpace()) + .with(PropagationAdapter.builder()) + .with(StateCoderAdapter.builder()) + .with(DesignSpaceExplorationAdapter.builder()) + .with(ReasoningAdapter.builder()); + + var modelSeed = modelInitializer.createModel(problem, storeBuilder); + + var store = storeBuilder.build(); + + var initialModel = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(modelSeed); + + var initialVersion = initialModel.commit(); + + var bestFirst = new BestFirstStoreManager(store, 1); + bestFirst.startExploration(initialVersion); + var resultStore = bestFirst.getSolutionStore(); + System.out.println("states size: " + resultStore.getSolutions().size()); + + var model = store.createModelForState(resultStore.getSolutions().get(0).version()); + var interpretation = model.getAdapter(ReasoningAdapter.class) + .getPartialInterpretation(Concreteness.CANDIDATE, ReasoningAdapter.EXISTS_SYMBOL); + var cursor = interpretation.getAll(); + int max = -1; + var types = new LinkedHashMap(); + var typeInterpretation = model.getInterpretation(TypeHierarchyTranslator.TYPE_SYMBOL); + while (cursor.move()) { + max = Math.max(max, cursor.getKey().get(0)); + var type = typeInterpretation.get(cursor.getKey()); + if (type != null) { + types.compute(type.candidateType(), (ignoredKey, oldValue) -> oldValue == null ? 1 : oldValue + 1); + } + } + System.out.println("Model size: " + (max + 1)); + System.out.println(types); +// initialModel.getAdapter(ModelVisualizerAdapter.class).visualize(bestFirst.getVisualizationStore()); + } + + @Test + void filesystemTest() { + var parsedProblem = parseHelper.parse(""" + class Filesystem { + contains Entry root + } + + abstract class Entry. + + class Directory extends Entry { + contains Entry[] entries + } + + class File extends Entry. + + Filesystem(fs). + + scope Filesystem += 0, Entry = 100. """); assertThat(parsedProblem.errors(), empty()); var problem = parsedProblem.problem(); @@ -265,7 +332,7 @@ class ModelGenerationTest { var test = injector.getInstance(ModelGenerationTest.class); try { test.statechartTest(); - } catch (AssertionError e) { + } catch (Throwable e) { e.printStackTrace(); } } diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/TermEvaluator.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/TermEvaluator.java index 5df861a6..d064ff2c 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/TermEvaluator.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/TermEvaluator.java @@ -5,10 +5,10 @@ */ package tools.refinery.store.query.viatra.internal.pquery; -import tools.refinery.viatra.runtime.matchers.psystem.IExpressionEvaluator; -import tools.refinery.viatra.runtime.matchers.psystem.IValueProvider; import tools.refinery.store.query.term.Term; import tools.refinery.store.query.term.Variable; +import tools.refinery.viatra.runtime.matchers.psystem.IExpressionEvaluator; +import tools.refinery.viatra.runtime.matchers.psystem.IValueProvider; import java.util.stream.Collectors; diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TupleChangingViewUpdateListener.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TupleChangingViewUpdateListener.java index 9dc739f1..5577faa3 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TupleChangingViewUpdateListener.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TupleChangingViewUpdateListener.java @@ -5,11 +5,11 @@ */ package tools.refinery.store.query.viatra.internal.update; -import tools.refinery.viatra.runtime.matchers.tuple.Tuples; import tools.refinery.store.model.Interpretation; import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; import tools.refinery.store.query.view.SymbolView; import tools.refinery.store.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; import java.util.Arrays; @@ -27,18 +27,19 @@ public class TupleChangingViewUpdateListener extends SymbolViewUpdateListener boolean fromPresent = view.filter(key, fromValue); boolean toPresent = view.filter(key, toValue); if (fromPresent) { + var fromArray = view.forwardMap(key, fromValue); if (toPresent) { // value change - var fromArray = view.forwardMap(key, fromValue); var toArray = view.forwardMap(key, toValue); if (!Arrays.equals(fromArray, toArray)) { processUpdate(Tuples.flatTupleOf(fromArray), false); processUpdate(Tuples.flatTupleOf(toArray), true); } } else { // fromValue disappears - processUpdate(Tuples.flatTupleOf(view.forwardMap(key, fromValue)), false); + processUpdate(Tuples.flatTupleOf(fromArray), false); } } else if (toPresent) { // toValue appears - processUpdate(Tuples.flatTupleOf(view.forwardMap(key, toValue)), true); + var toArray = view.forwardMap(key, toValue); + processUpdate(Tuples.flatTupleOf(toArray), true); } } } diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngine.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngine.java index 4c603a47..0f402b49 100644 --- a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngine.java +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngine.java @@ -15,6 +15,7 @@ import tools.refinery.viatra.runtime.api.scope.QueryScope; import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; import java.util.Set; +import java.util.function.Supplier; import java.util.stream.Collectors; /** @@ -147,4 +148,6 @@ public abstract class ViatraQueryEngine { public abstract QueryScope getScope(); public abstract void flushChanges(); + + public abstract T withFlushingChanges(Supplier supplier); } diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/apiimpl/ViatraQueryEngineImpl.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/apiimpl/ViatraQueryEngineImpl.java index 47a51629..5317a79e 100644 --- a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/apiimpl/ViatraQueryEngineImpl.java +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/apiimpl/ViatraQueryEngineImpl.java @@ -41,6 +41,7 @@ import java.lang.ref.WeakReference; import java.lang.reflect.InvocationTargetException; import java.util.*; import java.util.concurrent.Callable; +import java.util.function.Supplier; import java.util.stream.Collectors; import static tools.refinery.viatra.runtime.matchers.util.Preconditions.checkArgument; @@ -164,9 +165,27 @@ public final class ViatraQueryEngineImpl extends AdvancedViatraQueryEngine } delayMessageDelivery = false; try { - for (IQueryBackend backend : this.queryBackends.values()) { - backend.flushUpdates(); - } + flushAllBackends(); + } finally { + delayMessageDelivery = true; + } + } + + private void flushAllBackends() { + for (IQueryBackend backend : this.queryBackends.values()) { + backend.flushUpdates(); + } + } + + @Override + public T withFlushingChanges(Supplier callback) { + if (!delayMessageDelivery) { + return callback.get(); + } + delayMessageDelivery = false; + try { + flushAllBackends(); + return callback.get(); } finally { delayMessageDelivery = true; } @@ -186,18 +205,20 @@ public final class ViatraQueryEngineImpl extends AdvancedViatraQueryEngine @Override public > Matcher getMatcher( IQuerySpecification querySpecification, QueryEvaluationHint optionalEvaluationHints) { - IMatcherCapability capability = getRequestedCapability(querySpecification, optionalEvaluationHints); - Matcher matcher = doGetExistingMatcher(querySpecification, capability); - if (matcher != null) { - return matcher; - } - matcher = querySpecification.instantiate(); - - BaseMatcher baseMatcher = (BaseMatcher) matcher; - ((QueryResultWrapper) baseMatcher).setBackend(this, - getResultProvider(querySpecification, optionalEvaluationHints), capability); - internalRegisterMatcher(querySpecification, baseMatcher); - return matcher; + return withFlushingChanges(() -> { + IMatcherCapability capability = getRequestedCapability(querySpecification, optionalEvaluationHints); + Matcher matcher = doGetExistingMatcher(querySpecification, capability); + if (matcher != null) { + return matcher; + } + matcher = querySpecification.instantiate(); + + BaseMatcher baseMatcher = (BaseMatcher) matcher; + ((QueryResultWrapper) baseMatcher).setBackend(this, + getResultProvider(querySpecification, optionalEvaluationHints), capability); + internalRegisterMatcher(querySpecification, baseMatcher); + return matcher; + }); } @Override -- cgit v1.2.3-70-g09d2