From 79c31a44bae4baa0c720aabd9a5f702cabb1f179 Mon Sep 17 00:00:00 2001 From: Grigorii Kirgizov Date: Tue, 21 Jan 2020 15:57:20 +0300 Subject: [PATCH] Handle journal reset for occurrences: upd their status (eg !alive). Add test for one error caused by it. Occurrences which weren't yet activated (according to current journal position) can't be reactivated. Test program is an example of such case. --- .../reactor/core/internal/MatchJournalImpl.kt | 21 +++++- reactor/Test/test/TestIncrementalProgram.kt | 67 +++++++++++++++++++ 2 files changed, 87 insertions(+), 1 deletion(-) diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournalImpl.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournalImpl.kt index 28575954..515facf0 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournalImpl.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournalImpl.kt @@ -91,17 +91,25 @@ internal open class MatchJournalImpl( override fun isFront(): Boolean = current == hist.last override fun resetPos() { - posPtr = hist.listIterator() + // walk backwards and reset all occurrences + posPtr = hist.listIterator(hist.size) + while (posPtr.hasPrevious()) { + current = posPtr.previous() + resetOccurrences(current.entries) + } } override fun reset(pastPos: MatchJournal.Pos) { + // walk backwards, reset occurrences and remove history from posPtr down to pastPos while (posPtr.hasPrevious()) { current = posPtr.previous() if (current === pastPos.chunk) { + resetOccurrences(current.entries.drop(pastPos.entriesCount)) current.entries = current.entries.subList(0, pastPos.entriesCount) posPtr.next() // make 'posPtr' to always point right after 'current' return } + resetOccurrences(current.entries) posPtr.remove() } if (currentPos() != pastPos) throw IllegalStateException() @@ -119,6 +127,17 @@ internal open class MatchJournalImpl( if (currentPos() != futurePos) throw IllegalStateException() } + private fun resetOccurrences(occSpecs: Iterable) = + occSpecs.forEach { + if (it.discarded) { + it.occ.alive = true + it.occ.stored = true + } else { + it.occ.alive = false + it.occ.stored = false + } + } + private fun replayOccurrences(observable: LogicalStateObservable, occSpecs: Iterable) = occSpecs.forEach { if (it.discarded) { diff --git a/reactor/Test/test/TestIncrementalProgram.kt b/reactor/Test/test/TestIncrementalProgram.kt index e381d66b..ebdf4117 100644 --- a/reactor/Test/test/TestIncrementalProgram.kt +++ b/reactor/Test/test/TestIncrementalProgram.kt @@ -1158,4 +1158,71 @@ class TestIncrementalProgram { } } } + + + @Test + fun observerReactivatesFutureOccurrence() { + val progSpec = MockIncrProgSpec( + setOf("main", "bindVar", "produceBound", "eliminateBound"), + setOf(sym1("foo")) + ) + val X = metaLogical("X") + programWithRules( + rule("main", + headReplaced( + constraint("main") + ), + body( + princConstraint("foo", X) + )), + rule("produceBound", + headKept( + princConstraint("foo", X) + ), + body( + princConstraint("hasBound", X) + )), + rule("eliminateBound", + headReplaced( + princConstraint("hasBound", X) + ), + guard( + expression({ x -> x.isBound }, X) + ), + body( + constraint("unexpected") + ) + ) + ).launch("initial run", progSpec) { result -> + + result.storeView().constraintSymbols() shouldBe setOf(sym1("foo"), sym1("hasBound")) + + }.also { (builder, evalRes) -> + // Insert rule before produceBound: test whether + // occurrence reactivation due to var bind can occur + // before this occurrence is actually activated, + // which leads, of course, to inconsistent state. + builder.insertRulesAt(1, + rule("bindVar", + headKept( + princConstraint("foo", X) + ), + body( + statement({ x -> x.set(42) }, X) + )) + ).relaunch("test reactivation", progSpec, evalRes.token()) { result -> + + // Currently incremental processing can't handle such rule dependencies + // stemming from incremental changes to logicals, + // so "eliminateBound" rule won't be reexecuted. + // But reactivation of hasBound shouldn't pass either, + // because 'hasBound' isn't in store at the point of tried reactivation. + result.storeView().constraintSymbols() shouldBe setOf(sym1("foo"), sym1("hasBound")) + + // NB: this is correct if incremental processing + // takes into account rule dependencies due to logicals. + // result.storeView().constraintSymbols() shouldBe setOf(sym1("foo"), sym0("unexpected")) + } + } + } }