Skip to content

Commit f79afd2

Browse files
committed
Merge branch 'debugging-cleaner'
2 parents db60f54 + f28b7d8 commit f79afd2

6 files changed

Lines changed: 101 additions & 20 deletions

File tree

.idea/misc.xml

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
Copyright 2026 JKU/Dynatrace Co-Innovation Lab
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
package com.dynatrace.vectorscan4j;

src/main/java/com/dynatrace/vectorscan4j/Database.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@
5151
*/
5252
public class Database implements AutoCloseable {
5353
private static final Cleaner CLEANER = Cleaner.create();
54-
5554
private final Arena arena;
5655
protected final MemorySegment dbNative;
5756
protected final int modeNative;
@@ -91,7 +90,7 @@ public void run() {
9190
* offending pattern id and vectorscan's error description)
9291
*/
9392
public Database(List<Expression> expressions, ExecutionMode executionMode) {
94-
this.arena = Arena.ofConfined();
93+
this.arena = Arena.ofShared();
9594
this.expressions = List.copyOf(expressions);
9695
this.mode = executionMode;
9796
this.modeNative = executionMode.equals(ExecutionMode.BLOCK_MODE) ? 1 : 2 + (1 << 24);
@@ -226,7 +225,7 @@ public static Database deserialize(InputStream is) {
226225
MemorySegment dbBytesPtr = temp.allocate(dbBytes.length);
227226
dbBytesPtr.asByteBuffer().put(dbBytes);
228227

229-
Arena arena = Arena.ofConfined();
228+
Arena arena = Arena.ofShared();
230229
try {
231230
MemorySegment dbPtr = arena.allocate(C_POINTER);
232231
int ans = hs_deserialize_database(dbBytesPtr, dbBytes.length, dbPtr);

src/main/java/com/dynatrace/vectorscan4j/Scanner.java

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,29 @@
3030

3131
abstract class Scanner implements AutoCloseable {
3232
private static final Cleaner CLEANER = Cleaner.create();
33-
3433
private long bufferCapacity = 1024L; // 1KB default buffer size
3534
private int bufferLength;
36-
private Arena dataArena = Arena.ofConfined();
35+
private Arena dataArena = Arena.ofShared();
3736
private MemorySegment dataSegment = dataArena.allocate(bufferCapacity);
38-
protected final Arena arena = Arena.ofConfined();
37+
protected final Arena arena = Arena.ofShared();
3938
protected final Database database;
40-
private OnMatchEventHandler handler;
4139
protected final MemorySegment scratch;
42-
protected final MemorySegment funcPtr = VectorscanMatchEventHandler.allocate(new CallHandlerOnMatch(), arena);
43-
private final CleanupState cleanupState;
40+
private final CallHandlerOnMatch callHandler = new CallHandlerOnMatch();
41+
protected MemorySegment funcPtr = VectorscanMatchEventHandler.allocate(callHandler, arena);
42+
;
43+
protected final CleanupState cleanupState;
4444
private final Cleaner.Cleanable cleanable;
4545

46-
private static final class CleanupState implements Runnable {
46+
static class CallHandlerOnMatch implements VectorscanMatchEventHandler.Function {
47+
OnMatchEventHandler handler;
48+
49+
@Override
50+
public int apply(int id, long from, long to, int flags, MemorySegment context) {
51+
return handler.onMatch(id, from, to, flags) ? 0 : 1;
52+
}
53+
}
54+
55+
protected static final class CleanupState implements Runnable {
4756
private final MemorySegment scratch;
4857
private final Arena arena; // manages lifetime of internal scratch space.
4958
private Arena dataArena; // manages lifetime of internal data segment.
@@ -76,17 +85,12 @@ public void run() {
7685
}
7786

7887
protected void setHandler(OnMatchEventHandler handler) {
79-
this.handler = handler;
80-
}
81-
82-
protected class CallHandlerOnMatch implements VectorscanMatchEventHandler.Function {
83-
@Override
84-
public int apply(int id, long from, long to, int flags, MemorySegment context) {
85-
return handler.onMatch(id, from, to, flags) ? 0 : 1;
86-
}
88+
this.callHandler.handler = handler;
8789
}
8890

8991
protected Scanner(Database database) {
92+
CallHandlerOnMatch callHandler = new CallHandlerOnMatch();
93+
9094
this.database = database;
9195

9296
// allocate scratch space
@@ -117,7 +121,7 @@ protected void setBuffer(ByteBuffer input) {
117121

118122
private void resizeBuffer(long newSize) {
119123
dataArena.close();
120-
dataArena = Arena.ofConfined();
124+
dataArena = Arena.ofShared();
121125
cleanupState.setDataArena(dataArena);
122126
dataSegment = dataArena.allocate(newSize);
123127
bufferCapacity = newSize;

src/test/java/com/dynatrace/vectorscan4j/BlockScannerTests.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import com.dynatrace.vectorscan4j.utils.LoadGenerator;
2727
import java.lang.foreign.Arena;
2828
import java.lang.foreign.MemorySegment;
29+
import java.lang.ref.WeakReference;
2930
import java.nio.ByteBuffer;
3031
import java.nio.charset.StandardCharsets;
3132
import java.util.*;
@@ -393,4 +394,53 @@ void scannerOperationsWithInvalidDb() {
393394
db.dbNative.set(JAVA_BYTE, 0, b0);
394395
}
395396
}
397+
398+
@Test
399+
void notClosingStillFreesMemory() throws InterruptedException {
400+
Database db = new Database(expressions, BLOCK_MODE);
401+
BlockScanner scanner = new BlockScanner(db);
402+
WeakReference<Database> dbRef = new WeakReference<>(db);
403+
WeakReference<BlockScanner> scannerRef = new WeakReference<>(scanner);
404+
assertNotNull(dbRef.get(), "Database was freed pre-emptively.");
405+
assertNotNull(scannerRef.get(), "BlockScanner was freed pre-emptively.");
406+
407+
// Making the BlockScanner instance unreachable will garbage collect that instance, but not the Database
408+
scanner = null;
409+
System.gc();
410+
Thread.sleep(100);
411+
assertNotNull(dbRef.get(), "Database was freed pre-emptively.");
412+
assertNull(scannerRef.get(), "Scanner did not get freed.");
413+
414+
// Making the Database instance unreachable will then also free the Database.
415+
db = null;
416+
System.gc();
417+
Thread.sleep(100);
418+
assertNull(dbRef.get(), "Database did not get freed.");
419+
assertNull(scannerRef.get(), "Scanner did not get freed.");
420+
}
421+
422+
@Test
423+
void scannerKeepsDatabaseAlive() throws InterruptedException {
424+
Database db = new Database(expressions, BLOCK_MODE);
425+
BlockScanner scanner = new BlockScanner(db);
426+
WeakReference<Database> dbRef = new WeakReference<>(db);
427+
WeakReference<BlockScanner> scannerRef = new WeakReference<>(scanner);
428+
assertNotNull(dbRef.get(), "Database was freed pre-emptively.");
429+
assertNotNull(scannerRef.get(), "BlockScanner was freed pre-emptively.");
430+
431+
// The BlockScanner keeps the Database alive, so it won't get garbage collected.
432+
db = null;
433+
System.gc();
434+
Thread.sleep(100);
435+
assertNotNull(dbRef.get(), "Database was freed pre-emptively.");
436+
assertNotNull(scannerRef.get(), "Scanner was freed pre-emptively.");
437+
438+
// Also losing the strong reference to the BlockScanner will cascade the freeing operation to the Database
439+
scanner = null;
440+
System.gc();
441+
Thread.sleep(100);
442+
443+
assertNull(dbRef.get(), "Database did not get freed.");
444+
assertNull(scannerRef.get(), "Scanner did not get freed.");
445+
}
396446
}

src/test/java/com/dynatrace/vectorscan4j/DatabaseTests.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import com.dynatrace.vectorscan4j.constants.ExecutionMode;
2626
import com.dynatrace.vectorscan4j.constants.Flags;
2727
import java.io.*;
28+
import java.lang.ref.WeakReference;
2829
import java.util.ArrayList;
2930
import java.util.Arrays;
3031
import java.util.EnumSet;
@@ -297,4 +298,16 @@ void operationsOnInvalidDb() {
297298
assertDoesNotThrow(() -> db.serialize());
298299
}
299300
}
301+
302+
@Test
303+
void notClosingStillFreesMemory() throws InterruptedException {
304+
Database db = new Database(expressions, BLOCK_MODE);
305+
WeakReference<Database> dbRef = new WeakReference<>(db);
306+
assertNotNull(dbRef.get(), "Database was freed pre-emptively.");
307+
db = null;
308+
309+
System.gc();
310+
Thread.sleep(100);
311+
assertNull(dbRef.get(), "Database did not get freed.");
312+
}
300313
}

0 commit comments

Comments
 (0)