Skip to content

Commit 357cd1d

Browse files
Merge pull request #978 from sirixdb/fix/hot-strict-binna-conformance
Fix/hot strict binna conformance
2 parents 4873ae8 + 19a9eb1 commit 357cd1d

98 files changed

Lines changed: 43275 additions & 1571 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

bundles/sirix-core/build.gradle

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,21 @@ test {
108108
}
109109

110110
jvmArgs = baseArgs
111+
// The HOT soak test (hot.soak.run) sustains thousands of mutating revisions + concurrent
112+
// readers; give its fork more heap. Normal test runs are unaffected.
113+
if (System.getProperty('hot.soak.run') == 'true') {
114+
maxHeapSize = System.getProperty('hot.soak.heap', '8g')
115+
systemProperty 'hot.soak.run', 'true'
116+
['hot.soak.perRev', 'hot.soak.revs', 'hot.soak.readers', 'hot.soak.validateEvery',
117+
'hot.soak.heap', 'hot.soak.seed', 'hot.soak.index',
118+
'sirix.allocator.maxSize'].each { k -> if (System.getProperty(k) != null) systemProperty k, System.getProperty(k) }
119+
// An OOM in a forked soak worker otherwise leaves the JVM hung indefinitely (the test
120+
// never returns, gradle waits forever). Exit immediately and capture a heap dump so the
121+
// run fails fast and the leak is diagnosable.
122+
jvmArgs += ["-XX:+ExitOnOutOfMemoryError",
123+
"-XX:+HeapDumpOnOutOfMemoryError",
124+
"-XX:HeapDumpPath=${System.getenv('TMPDIR') ?: '/tmp'}/soak-oom.hprof"]
125+
}
111126
// Pass through diagnostic system properties
112127
systemProperty "sirix.debug.path.summary", System.getProperty("sirix.debug.path.summary", "false")
113128
systemProperty "sirix.debug.leak.diagnostics", System.getProperty("sirix.debug.leak.diagnostics", "false")
@@ -121,7 +136,43 @@ test {
121136
systemProperty "sirix.lz4.fast.decompress", useFastLz4.toString()
122137
systemProperty "sirix.lz77Codec.native.disable", System.getProperty("sirix.lz77Codec.native.disable", "false")
123138
systemProperty "sirix.lz77Codec.diag.counters", System.getProperty("sirix.lz77Codec.diag.counters", "false")
124-
139+
// HOT trie flags still referenced in source — forward from Gradle CLI to test JVM.
140+
systemProperty "hot.strict.validate", System.getProperty("hot.strict.validate", "false")
141+
systemProperty "hot.strict.phase7u.lexprimary", System.getProperty("hot.strict.phase7u.lexprimary", "false")
142+
systemProperty "hot.strict.phase7u.lexfallback.disable", System.getProperty("hot.strict.phase7u.lexfallback.disable", "false")
143+
systemProperty "hot.bisect.i8", System.getProperty("hot.bisect.i8", "false")
144+
systemProperty "hot.bisect.from", System.getProperty("hot.bisect.from", "0")
145+
systemProperty "hot.bisect.to", System.getProperty("hot.bisect.to", "600")
146+
systemProperty "hot.dump.at", System.getProperty("hot.dump.at", "-1")
147+
systemProperty "hot.debug.phase4", System.getProperty("hot.debug.phase4", "false")
148+
systemProperty "hot.formal.verify", System.getProperty("hot.formal.verify", "false")
149+
systemProperty "hot.strict.phase5e", System.getProperty("hot.strict.phase5e", "false")
150+
systemProperty "hot.trace.persistent", System.getProperty("hot.trace.persistent", "false")
151+
systemProperty "hot.strict.phase7f", System.getProperty("hot.strict.phase7f", "false")
152+
systemProperty "hot.strict.phase7h", System.getProperty("hot.strict.phase7h", "false")
153+
systemProperty "hot.strict.phase7j", System.getProperty("hot.strict.phase7j", "false")
154+
systemProperty "hot.strict.phase7k", System.getProperty("hot.strict.phase7k", "false")
155+
systemProperty "hot.strict.phase7q.commitroot", System.getProperty("hot.strict.phase7q.commitroot", "false")
156+
systemProperty "hot.strict.phase7q.postcommit", System.getProperty("hot.strict.phase7q.postcommit", "false")
157+
systemProperty "hot.strict.phase7r.routeverify", System.getProperty("hot.strict.phase7r.routeverify", "false")
158+
systemProperty "hot.strict.phase7s.split", System.getProperty("hot.strict.phase7s.split", "true")
159+
systemProperty "hot.strict.phase7t.monotone.probe", System.getProperty("hot.strict.phase7t.monotone.probe", "false")
160+
systemProperty "hot.strict.phase7t.betamixed.probe", System.getProperty("hot.strict.phase7t.betamixed.probe", "false")
161+
systemProperty "hot.strict.phase7t.crossroute.probe", System.getProperty("hot.strict.phase7t.crossroute.probe", "false")
162+
systemProperty "hot.strict.phase7t.subsetroute.probe", System.getProperty("hot.strict.phase7t.subsetroute.probe", "false")
163+
systemProperty "hot.strict.phase7t.perkey.probe", System.getProperty("hot.strict.phase7t.perkey.probe", "false")
164+
systemProperty "hot.strict.phase7t10.perkey.probe", System.getProperty("hot.strict.phase7t10.perkey.probe", "false")
165+
systemProperty "hot.strict.phase7t11.perkey.probe", System.getProperty("hot.strict.phase7t11.perkey.probe", "false")
166+
systemProperty "hot.debug.i6trace", System.getProperty("hot.debug.i6trace", "")
167+
systemProperty "hot.diag.selfHealDetail", System.getProperty("hot.diag.selfHealDetail", "false")
168+
systemProperty "hot.diag.postHandlerValidate", System.getProperty("hot.diag.postHandlerValidate", "false")
169+
systemProperty "hot.diag.directionOneFallback", System.getProperty("hot.diag.directionOneFallback", "false")
170+
// Forward ALL hot.* system properties from the gradle JVM to the forked test JVM so that
171+
// ad-hoc -Dhot.diag.* debug flags reach the code under test without needing to whitelist each
172+
// one here (the individual entries above are kept for clarity / defaults but are redundant
173+
// with this pattern). Mirrors the root build.gradle's sirix.* forwarding.
174+
System.properties.findAll { it.key.startsWith("hot.") }.each { k, v -> systemProperty k, v }
175+
125176
// Show test output for diagnostics
126177
testLogging {
127178
showStandardStreams = true

bundles/sirix-core/src/main/java/io/sirix/access/EmptyBufferManager.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import io.sirix.cache.RevisionRootPageCacheKey;
1111
import io.sirix.index.name.Names;
1212
import io.sirix.node.interfaces.Node;
13+
import io.sirix.page.HOTLeafPage;
1314
import io.sirix.page.KeyValueLeafPage;
1415
import io.sirix.page.PageReference;
1516
import io.sirix.page.RevisionRootPage;
@@ -23,6 +24,8 @@ public final class EmptyBufferManager implements BufferManager {
2324

2425
private static final EmptyCache<PageReference, Page> PAGE_CACHE = new EmptyCache<>();
2526

27+
private static final EmptyCache<PageReference, HOTLeafPage> HOT_LEAF_PAGE_CACHE = new EmptyCache<>();
28+
2629
private static final EmptyCache<RevisionRootPageCacheKey, RevisionRootPage> REVISION_ROOT_PAGE_CACHE =
2730
new EmptyCache<>();
2831

@@ -49,6 +52,11 @@ public Cache<PageReference, Page> getPageCache() {
4952
return PAGE_CACHE;
5053
}
5154

55+
@Override
56+
public Cache<PageReference, HOTLeafPage> getHOTLeafPageCache() {
57+
return HOT_LEAF_PAGE_CACHE;
58+
}
59+
5260
@Override
5361
public Cache<RevisionRootPageCacheKey, RevisionRootPage> getRevisionRootPageCache() {
5462
return REVISION_ROOT_PAGE_CACHE;

bundles/sirix-core/src/main/java/io/sirix/access/trx/node/AbstractIndexController.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,12 @@ public IndexController<R, W> createIndexListeners(final Set<IndexDef> indexDefs,
236236
return this;
237237
}
238238

239+
@Override
240+
public void clearChangeListeners() {
241+
listeners.clear();
242+
primitiveListeners.clear();
243+
}
244+
239245
private void updateIndexCapability(final IndexType type) {
240246
switch (type) {
241247
case PATH -> hasPathIndex = true;

bundles/sirix-core/src/main/java/io/sirix/access/trx/node/IndexController.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,15 @@ void notifyChange(ChangeType type, long nodeKey, NodeKind nodeKind, long pathNod
221221
*/
222222
IndexController<R, W> createIndexListeners(Set<IndexDef> indexDefs, W nodeWriteTrx);
223223

224+
/**
225+
* Remove all registered change listeners. A write-side controller may be cached and reused across
226+
* write transactions; its listeners capture a specific transaction's storage engine and path
227+
* summary, so a new transaction must clear the previous (now-closed) transaction's listeners
228+
* before rebinding its own — otherwise {@link #notifyChange} could fire a listener against a
229+
* closed transaction ("Transaction is already closed!").
230+
*/
231+
void clearChangeListeners();
232+
224233
NameFilter createNameFilter(Set<String> names);
225234

226235
PathFilter createPathFilter(Set<String> paths, R rtx) throws PathException;

bundles/sirix-core/src/main/java/io/sirix/access/trx/node/json/JsonNodeTrxImpl.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,10 @@ private boolean isFusedSyntheticChildCursor() {
248248

249249
// Register index listeners for any existing indexes.
250250
// This is critical for subsequent write transactions to update indexes on node modifications.
251+
// The write-side index controller is cached and reused across transactions, so first drop any
252+
// listeners bound to a previous (now-closed) transaction before rebinding this one's — else a
253+
// stale listener fires against a closed storage engine ("Transaction is already closed!").
254+
indexController.clearChangeListeners();
251255
final var existingIndexDefs = indexController.getIndexes().getIndexDefs();
252256
if (!existingIndexDefs.isEmpty()) {
253257
indexController.createIndexListeners(existingIndexDefs, this);

bundles/sirix-core/src/main/java/io/sirix/access/trx/node/xml/XmlNodeTrxImpl.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,10 @@ final class XmlNodeTrxImpl extends
151151

152152
// Register index listeners for any existing indexes.
153153
// This is critical for subsequent write transactions to update indexes on node modifications.
154+
// The write-side index controller is cached and reused across transactions, so first drop any
155+
// listeners bound to a previous (now-closed) transaction before rebinding this one's — else a
156+
// stale listener fires against a closed storage engine ("Transaction is already closed!").
157+
indexController.clearChangeListeners();
154158
final var existingIndexDefs = indexController.getIndexes().getIndexDefs();
155159
if (!existingIndexDefs.isEmpty()) {
156160
indexController.createIndexListeners(existingIndexDefs, this);

bundles/sirix-core/src/main/java/io/sirix/access/trx/page/HOTRangeCursor.java

Lines changed: 5 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,6 @@ public byte[] valueBytes() {
109109
private HOTLeafPage currentLeaf;
110110
private int currentIndex;
111111
private boolean exhausted = false;
112-
private boolean guardAcquired = false;
113112

114113
// Pre-computed next entry (for hasNext/next pattern)
115114
private Entry nextEntry = null;
@@ -168,7 +167,6 @@ private void descendToFirstEntry() {
168167
}
169168

170169
if (currentLeaf != null) {
171-
acquireLeafGuard();
172170
advanceToValid();
173171
} else {
174172
exhausted = true;
@@ -229,42 +227,17 @@ private void advanceToValid() {
229227
* @return true if advanced to a new leaf, false if no more leaves
230228
*/
231229
private boolean advanceToNextLeaf() {
232-
// Release current leaf guard
233-
releaseLeafGuard();
234-
235-
// Use reader's parent-based traversal
230+
// reader.advanceToNextLeaf resolves the next leaf through HOTTrieReader.loadPage, which
231+
// guards it as the reader's single guarded leaf (releasing the previous one). The cursor
232+
// holds no guard of its own — currentLeaf stays live for as long as it is current.
236233
currentLeaf = reader.advanceToNextLeaf();
237-
238234
if (currentLeaf == null) {
239235
return false;
240236
}
241-
242-
// Acquire guard on new leaf
243-
acquireLeafGuard();
244237
currentIndex = 0;
245238
return true;
246239
}
247240

248-
/**
249-
* Acquire guard on current leaf.
250-
*/
251-
private void acquireLeafGuard() {
252-
if (currentLeaf != null && !guardAcquired) {
253-
currentLeaf.acquireGuard();
254-
guardAcquired = true;
255-
}
256-
}
257-
258-
/**
259-
* Release guard on current leaf.
260-
*/
261-
private void releaseLeafGuard() {
262-
if (currentLeaf != null && guardAcquired) {
263-
currentLeaf.releaseGuard();
264-
guardAcquired = false;
265-
}
266-
}
267-
268241
@Override
269242
public boolean hasNext() {
270243
return positionedValid;
@@ -388,7 +361,8 @@ int getCurrentIndex() {
388361

389362
@Override
390363
public void close() {
391-
releaseLeafGuard();
364+
// No guard to release here: HOTTrieReader.loadPage owns the single leaf guard and frees
365+
// it on the next navigation or when the reader itself is closed.
392366
currentLeaf = null;
393367
nextEntry = null;
394368
positionedValid = false;

0 commit comments

Comments
 (0)