This is an automated email from the ASF dual-hosted git repository.
lollipopjin pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/rocketmq.git
The following commit(s) were added to refs/heads/develop by this push:
new 7e5d22dff8 [ISSUE #10334] Make native CqCompactionFilter shim
self-contained without rocksdbjni dependency (#10371)
7e5d22dff8 is described below
commit 7e5d22dff8542dfe7176147970d72a177904675d
Author: lizhimins <[email protected]>
AuthorDate: Mon May 25 10:07:16 2026 +0800
[ISSUE #10334] Make native CqCompactionFilter shim self-contained without
rocksdbjni dependency (#10371)
---
docs/en/Native_RocksDB.md | 116 +++++------------
.../store/rocksdb/ConsumeQueueRocksDBStorage.java | 1 +
.../store/rocksdb/CqCompactionFilterJni.java | 140 ++++++---------------
.../main/resources/native/cq_compaction_filter.cpp | 27 ++--
.../main/resources/native/cq_compaction_filter.dll | Bin 136192 -> 137216 bytes
.../resources/native/libcq_compaction_filter.dylib | Bin 41776 -> 74512 bytes
.../resources/native/libcq_compaction_filter.so | Bin 23848 -> 30688 bytes
.../native/libcq_compaction_filter_aarch64.so | Bin 74824 -> 77488 bytes
8 files changed, 84 insertions(+), 200 deletions(-)
diff --git a/docs/en/Native_RocksDB.md b/docs/en/Native_RocksDB.md
index f0d43a8137..86a2332eef 100644
--- a/docs/en/Native_RocksDB.md
+++ b/docs/en/Native_RocksDB.md
@@ -42,8 +42,7 @@ To implement a custom compaction filter outside the
`rocksdbjni` build, we creat
▼
┌──────────────────────────────────────────────────────┐
│ CqCompactionFilterJni.java │
-│ - Extracts libcq_compaction_filter.so to the same │
-│ temp dir as the already-loaded rocksdbjni .so │
+│ - Extracts libcq_compaction_filter.so to temp dir │
│ - Uses NativeCqCompactionFilter wrapper with │
│ disOwnNativeHandle() + public setCompactionFilter │
│ - Calls native createNativeFilter0() → raw pointer │
@@ -59,33 +58,24 @@ To implement a custom compaction filter outside the
`rocksdbjni` build, we creat
│ JNI: createNativeFilter0() → new CqCompactionFilter │
│ JNI: setMinPhyOffset0(ptr, offset) │
│ │
-│ NEEDED: librocksdbjni-linux64.so ($ORIGIN RPATH) │
-└──────────────────┬───────────────────────────────────┘
- │
- ▼
-┌──────────────────────────────────────────────────────┐
-│ librocksdbjni-linux64.so (official rocksdbjni) │
-│ - All RocksDB C++ classes (CompactionFilter, etc.) │
-│ - JNI glue for all Java↔C++ bindings │
-│ - Compiled with -fno-rtti -D_GLIBCXX_USE_CXX11_ABI=0│
+│ Self-contained: stub vtable methods inline, │
+│ NO DT_NEEDED on librocksdbjni │
└──────────────────────────────────────────────────────┘
```
### Key design decisions
-**1. Direct C++ subclassing with explicit linking**
-
-The shim directly subclasses `rocksdb::CompactionFilter` in C++ and is
compiled with matching ABI flags (`-fno-rtti -D_GLIBCXX_USE_CXX11_ABI=0`) to
match how `librocksdbjni` was built. It is explicitly linked against
`librocksdbjni-linux64.so` (extracted from the `rocksdbjni` jar) with `$ORIGIN`
RPATH so the dynamic linker resolves symbols from the same directory.
+**1. Self-contained C++ shim with stub vtable methods**
-This replaced an earlier dlopen/RTLD_GLOBAL approach that caused C++ `double
free` crashes — loading the same `.so` twice (once via JVM's `RTLD_LOCAL` and
once via `RTLD_GLOBAL`) creates conflicting C++ global state (memory
allocators, static singletons, vtables).
+The shim directly subclasses `rocksdb::CompactionFilter` in C++ and is
compiled with matching ABI flags (`-fno-rtti -D_GLIBCXX_USE_CXX11_ABI=0`). All
inherited virtual methods from `Configurable` and `Customizable` are provided
as inline stub implementations, making the shim fully self-contained with **no
runtime dependency on librocksdbjni**. This avoids the JVM crash caused by
`dlopen` loading a second copy of rocksdbjni (which has no SONAME — different
temp paths produce different ino [...]
**2. Raw pointer as jlong, wrapped with disOwnNativeHandle()**
The native shim creates `new CqCompactionFilter()` and returns the raw C++
pointer as a `jlong`. A thin Java wrapper `NativeCqCompactionFilter extends
AbstractCompactionFilter<Slice>` passes this pointer to the protected
`AbstractCompactionFilter(long)` constructor, then calls `disOwnNativeHandle()`
so that `close()` does not free the native memory. This is critical because
`AbstractRocksDBStorage.shutdown()` closes `ColumnFamilyOptions` (step 2)
before closing the DB (step 4) — without [...]
-**3. Shared temp directory for .so resolution**
+**3. Simple temp file extraction**
-At runtime, `CqCompactionFilterJni` loads `librocksdbjni-linux64.so` from the
rocksdbjni JAR first (via `System.loadLibrary` or extraction to a temp dir),
then extracts `libcq_compaction_filter.so` to the same temp directory. This
ensures the `$ORIGIN` RPATH in the shim correctly resolves its `NEEDED`
dependency on `librocksdbjni-linux64.so`. The rocksdbjni native library is NOT
bundled in the RocketMQ repository — it is sourced from the
`org.rocksdb:rocksdbjni:8.4.4` JAR at runtime.
+At runtime, `CqCompactionFilterJni` extracts the shim library to a temp file
and calls `System.load()`. Since the shim is self-contained (no `DT_NEEDED` on
rocksdbjni), it can be loaded from any directory without worrying about library
path resolution. The rocksdbjni native library is loaded separately by
`AbstractRocksDBStorage` via `RocksDB.loadLibrary()` — the two libraries are
independent at the dynamic linker level.
**4. Thread-safe minPhyOffset with std::atomic**
@@ -116,98 +106,60 @@ Prerequisites: `g++` / `clang++`, RocksDB C++ headers
matching `rocksdbjni` vers
### Linux (x86_64)
```bash
-# 1. Extract librocksdbjni from the rocksdbjni jar
-ROCKSDB_JAR=~/.m2/repository/org/rocksdb/rocksdbjni/8.4.4/rocksdbjni-8.4.4.jar
-unzip -j "$ROCKSDB_JAR" librocksdbjni-linux64.so -d /tmp/rocksdb-native/
-
-# 2. Download matching RocksDB headers
+# 1. Download matching RocksDB headers (only headers needed — no linking
against rocksdbjni)
wget https://github.com/facebook/rocksdb/archive/refs/tags/v8.4.4.tar.gz
tar xzf v8.4.4.tar.gz rocksdb-8.4.4/include --strip-components=1
-# 3. Compile the shim with explicit linking
+# 2. Compile the self-contained shim
export JAVA_HOME=/usr/lib/jvm/java-8 # or your JDK path
g++ -shared -fPIC -O2 -std=c++17 -fno-rtti -D_GLIBCXX_USE_CXX11_ABI=0 \
-I./include \
-I${JAVA_HOME}/include \
-I${JAVA_HOME}/include/linux \
- -Wl,--no-undefined \
- -Wl,-rpath,\$ORIGIN \
- -L/tmp/rocksdb-native \
- -l:librocksdbjni-linux64.so \
-o libcq_compaction_filter.so \
store/src/main/resources/native/cq_compaction_filter.cpp
-# 4. Verify NEEDED and RPATH
-readelf -d libcq_compaction_filter.so | grep -E "NEEDED|RPATH"
-# Should show: NEEDED librocksdbjni-linux64.so, RPATH $ORIGIN
+# 3. Verify no dependency on rocksdbjni
+objdump -p libcq_compaction_filter.so | grep NEEDED
+# Should show ONLY: libstdc++, libm, libgcc_s, libc — NOT librocksdbjni
+nm -D libcq_compaction_filter.so | grep " U " | grep rocksdb
+# Should be empty (no undefined rocksdb symbols)
-# 5. Replace the pre-built .so
+# 4. Replace the pre-built .so
cp libcq_compaction_filter.so store/src/main/resources/native/
```
### macOS (arm64 / x86_64)
-On macOS, the rocksdbjni jar uses `.jnilib` extension (not `.so` or bare
names) and the native library names differ from Linux. Key gotchas:
-- Apple Silicon uses `librocksdbjni-osx-arm64.jnilib` (not
`librocksdbjni-osx-aarch64` as the filename pattern might suggest)
-- macOS `ld` does NOT support the `-l:` syntax used on Linux — pass the
`.jnilib` file directly
-- After linking, use `install_name_tool` to fix the absolute install name to
`@loader_path`, otherwise the shim fails to resolve the rocksdbjni dependency
at runtime
-- GitHub downloads may be blocked by corporate firewalls; use a mirror (e.g.
`ghproxy.net`) or a local RocksDB checkout for headers
-
```bash
-# 1. Extract the macOS native library from rocksdbjni jar
-# The jar contains librocksdbjni-osx-arm64.jnilib (arm64) or
librocksdbjni-osx-x86_64.jnilib
-ROCKSDB_JAR=~/.m2/repository/org/rocksdb/rocksdbjni/8.4.4/rocksdbjni-8.4.4.jar
-mkdir -p /tmp/rocksdb-native
-
-# For Apple Silicon (arm64):
-unzip -j "$ROCKSDB_JAR" librocksdbjni-osx-arm64.jnilib -d /tmp/rocksdb-native/
-
-# For Intel Mac (x86_64):
-unzip -j "$ROCKSDB_JAR" librocksdbjni-osx-x86_64.jnilib -d /tmp/rocksdb-native/
-
-# 2. Download matching RocksDB headers
+# 1. Download matching RocksDB headers
# Use ghproxy.net mirror if github.com is blocked:
curl -sL
"https://ghproxy.net/https://github.com/facebook/rocksdb/archive/refs/tags/v8.4.4.tar.gz"
-o /tmp/rocksdb-8.4.4.tar.gz
tar xzf /tmp/rocksdb-8.4.4.tar.gz -C /tmp rocksdb-8.4.4/include
--strip-components=1
# Or use a local RocksDB checkout if available:
# ROCKSDB_INCLUDE=/path/to/rocksdb/include
-# 3. Compile the shim — pass .jnilib directly (macOS ld does NOT support -l:
syntax)
+# 2. Compile the self-contained shim (no linking against rocksdbjni needed)
export JAVA_HOME=$(/usr/libexec/java_home)
ROCKSDB_INCLUDE=${ROCKSDB_INCLUDE:-./include} # adjust to your headers
location
-ROCKSDB_JNILIB=/tmp/rocksdb-native/librocksdbjni-osx-arm64.jnilib # or
-x86_64.jnilib
clang++ -shared -fPIC -O2 -std=c++17 -fno-rtti \
-I"$ROCKSDB_INCLUDE" \
-I${JAVA_HOME}/include \
-I${JAVA_HOME}/include/darwin \
- -Wl,-undefined,error \
- "$ROCKSDB_JNILIB" \
- -o /tmp/rocksdb-native/libcq_compaction_filter.dylib \
+ -o libcq_compaction_filter.dylib \
store/src/main/resources/native/cq_compaction_filter.cpp
-# 4. Fix the install_name to use @loader_path for runtime resolution
-# Without this, otool -L shows an absolute path to the build directory
-install_name_tool -change "$ROCKSDB_JNILIB" "@loader_path/$(basename
$ROCKSDB_JNILIB)" \
- /tmp/rocksdb-native/libcq_compaction_filter.dylib
-
-# 5. Verify dependencies
-otool -L /tmp/rocksdb-native/libcq_compaction_filter.dylib
-# Should show @loader_path/librocksdbjni-osx-arm64.jnilib (or -x86_64.jnilib)
+# 3. Verify no dependency on rocksdbjni
+otool -L libcq_compaction_filter.dylib
+# Should show ONLY system libs (libc++, libSystem) — NOT librocksdbjni
-# 6. Place the output
-cp /tmp/rocksdb-native/libcq_compaction_filter.dylib
store/src/main/resources/native/
+# 4. Place the output
+cp libcq_compaction_filter.dylib store/src/main/resources/native/
```
### Windows (x86_64)
-**⚠ Must use MSVC — MinGW is NOT compatible**
-
-The official `librocksdbjni-win64.dll` is compiled with MSVC. MinGW-w64
produces incompatible C++ binaries (different vtable layout, name mangling,
exception handling). Attempting to link a MinGW-compiled shim against the
MSVC-compiled rocksdbjni DLL will cause undefined symbol errors at link time
and crashes at runtime.
-
-**Option A: Native MSVC build (required for Windows)**
-
-1. Install Visual Studio Build Tools 2019 (v14.29, matching the rocksdbjni
linker version 14.29.30159).
-2. Use the x64 Native Tools Command Prompt or set up the environment manually.
+**⚠ Must use MSVC — MinGW is NOT compatible** (different vtable layout, name
mangling, exception handling).
```powershell
# 1. Set up environment (run vcvarsall.bat first, or use the VS Dev Command
Prompt)
@@ -219,7 +171,7 @@ set "JAVA_HOME=C:\path\to\jdk8"
curl -LO https://github.com/facebook/rocksdb/archive/refs/tags/v8.4.4.tar.gz
tar xzf v8.4.4.tar.gz rocksdb-8.4.4/include --strip-components=1
-# 3. Compile with MSVC cl.exe (must use /GR- to disable RTTI, matching
rocksdbjni)
+# 3. Compile with MSVC cl.exe (no linking against rocksdbjni needed)
cl.exe /LD /O2 /std:c++17 /GR- /EHsc /utf-8 ^
/I"%JAVA_HOME%\include" ^
/I"%JAVA_HOME%\include\win32" ^
@@ -244,24 +196,12 @@ dumpbin /exports cq_compaction_filter.dll
copy cq_compaction_filter.dll store\src\main\resources\native\
```
-> **Note on Git Bash / MSYS2**: When running `cl.exe` from Git Bash, MSYS2's
automatic path conversion will corrupt `/LD`, `/O2` etc. into `C:/Program/LD`
etc. Use `MSYS2_ARG_CONV_EXCL='*'` to disable this, or run from `cmd.exe` /
PowerShell directly.
-
-**Windows build troubleshooting**
-
-| Problem | Cause | Solution |
-|---------|-------|----------|
-| `cl: warning D9024: cannot recognize source file type` | MSYS2/Git Bash
converts `/LD` to `C:/Program/LD` | Prefix command with
`MSYS2_ARG_CONV_EXCL='*'` or use `cmd.exe` |
-| `fatal error C1083: cannot open include file 'atomic'` | MSVC C++ headers
directory not in include path | Add `/I"%VCTools%\include"` (from VS Build
Tools) |
-| `LNK2019: unresolved external symbol ... Configurable::GetOption` |
`CompactionFilter` inherits from `Configurable`/`Customizable`, whose virtual
methods are not exported by `librocksdbjni-win64.dll` | Provide inline stub
implementations in your `.cpp` for all unexported pure virtual methods |
-| `LNK2019: unresolved external symbol ...
Status::Status(Code,SubCode,Slice,Slice,Severity)` |
`Status::NotSupported("msg")` calls the non-inline 5-parameter constructor
(defined in `status.cc`, not exported by DLL) | Use `return Status();` instead
of `return Status::NotSupported("...");` — `Status()` default constructor is
fully inline |
+> **Note on Git Bash / MSYS2**: Use `MSYS2_ARG_CONV_EXCL='*'` to prevent path
corruption of `/LD`, `/O2` etc.
**Option B: Run on WSL (recommended for development)**
-Run the entire RocketMQ build and test under WSL (Windows Subsystem for
Linux). This uses the native Linux toolchain and pre-built `.so` with zero code
changes:
-
```bash
# In WSL (Ubuntu)
-java -version # should show WSL JDK
mvn test -pl store -Dtest=CqCompactionFilterJniTest -Djacoco.skip=true
```
@@ -285,6 +225,6 @@ mvn test -pl store -Dtest=CqCompactionFilterJniTest
-Djacoco.skip=true
3. **C++17 required** — The C++ source uses `std::atomic<int64_t>` which
requires a C++17-capable compiler. All modern compilers (GCC 7+, Clang 5+, MSVC
2017+) support this.
-4. **Shim depends on rocksdbjni native library at runtime** — The
`libcq_compaction_filter.so` has a `DT_NEEDED` entry for
`librocksdbjni-linux64.so` (~13 MB). The `CqCompactionFilterJni` class handles
this by extracting the shim to the same temp directory as the rocksdbjni native
library, so the `$ORIGIN` RPATH resolves correctly without requiring
`LD_LIBRARY_PATH`.
+4. **Windows requires MSVC** — MinGW produces incompatible C++ binaries
(different vtable layout). Must use MSVC for the Windows build.
-5. **Windows requires MSVC** — `librocksdbjni-win64.dll` is compiled with MSVC
and does not export C++ base class symbols (`Configurable`/`Customizable`
vtable methods, `Status` constructors). A MinGW-compiled shim cannot link
against it. Must use the same MSVC version (v14.29 for rocksdbjni 8.4.4) and
provide inline stubs for unexported virtual methods.
+5. **Stub vtable methods must match RocksDB version** — The shim provides stub
implementations of `Configurable`/`Customizable`/`Status` methods to fill the
vtable. If the upstream RocksDB header adds new virtual methods to these
classes in a future version, the stubs must be updated accordingly.
diff --git
a/store/src/main/java/org/apache/rocketmq/store/rocksdb/ConsumeQueueRocksDBStorage.java
b/store/src/main/java/org/apache/rocketmq/store/rocksdb/ConsumeQueueRocksDBStorage.java
index 925d8f91c7..70406c308e 100644
---
a/store/src/main/java/org/apache/rocketmq/store/rocksdb/ConsumeQueueRocksDBStorage.java
+++
b/store/src/main/java/org/apache/rocketmq/store/rocksdb/ConsumeQueueRocksDBStorage.java
@@ -99,6 +99,7 @@ public class ConsumeQueueRocksDBStorage extends
AbstractRocksDBStorage {
@Override
protected void preShutdown() {
+ CqCompactionFilterJni.destroyNativeFilter();
if (this.offsetCFHandle != null) {
this.offsetCFHandle.close();
}
diff --git
a/store/src/main/java/org/apache/rocketmq/store/rocksdb/CqCompactionFilterJni.java
b/store/src/main/java/org/apache/rocketmq/store/rocksdb/CqCompactionFilterJni.java
index 69d74e3364..a13896bb62 100644
---
a/store/src/main/java/org/apache/rocketmq/store/rocksdb/CqCompactionFilterJni.java
+++
b/store/src/main/java/org/apache/rocketmq/store/rocksdb/CqCompactionFilterJni.java
@@ -32,12 +32,12 @@ public class CqCompactionFilterJni {
private static final Logger log =
LoggerFactory.getLogger(LoggerName.ROCKSDB_LOGGER_NAME);
private static final AtomicLong NATIVE_FILTER_PTR = new AtomicLong(0);
+ private static final Object FILTER_LOCK = new Object();
private static volatile boolean loaded = false;
/** Platform-specific shim library name and extension. */
private static final String SHIM_LIB_NAME;
private static final String SHIM_LIB_EXTENSION;
- private static final String ROCKSDB_JNI_LIB_NAME;
static {
String os = System.getProperty("os.name").toLowerCase();
@@ -45,21 +45,14 @@ public class CqCompactionFilterJni {
if (os.contains("mac") || os.contains("darwin") || os.contains("osx"))
{
SHIM_LIB_NAME = "libcq_compaction_filter.dylib";
SHIM_LIB_EXTENSION = ".dylib";
- ROCKSDB_JNI_LIB_NAME = arch.contains("aarch") ||
arch.contains("arm")
- ? "librocksdbjni-osx-aarch64"
- : "librocksdbjni-osx-x86_64";
} else if (os.contains("win")) {
SHIM_LIB_NAME = "cq_compaction_filter.dll";
SHIM_LIB_EXTENSION = ".dll";
- ROCKSDB_JNI_LIB_NAME = "librocksdbjni-win64.dll";
} else {
SHIM_LIB_NAME = arch.contains("aarch") || arch.contains("arm")
? "libcq_compaction_filter_aarch64.so"
: "libcq_compaction_filter.so";
SHIM_LIB_EXTENSION = ".so";
- ROCKSDB_JNI_LIB_NAME = arch.contains("aarch") ||
arch.contains("arm")
- ? "librocksdbjni-linux-aarch64.so"
- : "librocksdbjni-linux64.so";
}
}
@@ -72,10 +65,6 @@ public class CqCompactionFilterJni {
return;
}
- // Preload RocksDB's native library so that linked symbols are
available
- // when our compaction filter shim is loaded.
- String rocksdbDir = ensureRocksDBNativeLoaded();
-
String libName = SHIM_LIB_NAME;
try (InputStream is = CqCompactionFilterJni.class
.getClassLoader().getResourceAsStream("native/" + libName)) {
@@ -83,15 +72,7 @@ public class CqCompactionFilterJni {
log.error("[CqCompactionFilterJni] Native library '{}' not
found on classpath", libName);
return;
}
- File tempLib;
- if (rocksdbDir != null) {
- // Extract our shim to the same temp directory as the RocksDB
JNI library,
- // so that the DT_NEEDED / LC_LOAD_DYLIB dependency can be
resolved.
- tempLib = new File(rocksdbDir, libName);
- } else {
- // RocksDB was loaded from java.library.path; our shim can go
anywhere.
- tempLib = File.createTempFile("cq_compaction_filter_",
SHIM_LIB_EXTENSION);
- }
+ File tempLib = File.createTempFile("cq_compaction_filter_",
SHIM_LIB_EXTENSION);
Files.copy(is, tempLib.toPath(),
StandardCopyOption.REPLACE_EXISTING);
tempLib.deleteOnExit();
System.load(tempLib.getAbsolutePath());
@@ -99,6 +80,8 @@ public class CqCompactionFilterJni {
log.info("[CqCompactionFilterJni] Native library loaded from
classpath: {}", tempLib.getAbsolutePath());
} catch (IOException e) {
log.error("[CqCompactionFilterJni] Failed to load native shim", e);
+ } catch (UnsatisfiedLinkError e) {
+ log.error("[CqCompactionFilterJni] Failed to link native shim", e);
}
}
@@ -109,76 +92,6 @@ public class CqCompactionFilterJni {
return loaded;
}
- /**
- * Locates and loads the RocksDB native JNI library, returning the
temporary
- * directory in which it was extracted (or null if loaded from
java.library.path).
- * <p>
- * This method deliberately uses {@code System.loadLibrary("rocksdbjni")}
- * rather than {@code RocksDB.loadLibrary()} for the following reasons:
- * <ol>
- * <li><b>Avoid unnecessary side effects</b> — {@code
RocksDB.loadLibrary()}
- * iterates over all compression types (snappy, lz4, zstd, bzip2, etc.)
- * and attempts to load each one. Those libraries are not needed by
this
- * compaction filter, and the resulting {@code UnsatisfiedLinkError}s
slow
- * down startup and pollute logs.</li>
- * <li><b>Control the temp directory location</b> — The caller needs to
know
- * the directory where the native JNI library was extracted so that
- * {@code libcq_compaction_filter.so} can be placed alongside it. This
is
- * required for the dynamic linker to resolve the {@code DT_NEEDED}
- * dependency of the custom shim. {@code RocksDB.loadLibrary()}
extracts
- * to an internal temp directory that is not exposed to callers.</li>
- * <li><b>Avoid class-loading coupling</b> — {@code RocksDB.loadLibrary()}
- * triggers the full initialization chain of the rocksdbjni Java
bindings
- * (including {@code CompressionType.values()} iteration and a
singleton
- * {@code NativeLibraryLoader} state machine). Loading the custom shim
- * must complete before any RocksDB Java classes are exercised, to
avoid
- * native symbol resolution race conditions.</li>
- * </ol>
- *
- * @return the absolute path of the temporary directory containing the
- * extracted RocksDB JNI library, or null if the library was loaded
- * from {@code java.library.path} (in which case no temp directory
- * is needed for the shim).
- */
- private static String ensureRocksDBNativeLoaded() {
- // Try System.loadLibrary first (works if on java.library.path)
- try {
- System.loadLibrary("rocksdbjni");
- // No temp dir needed since it's on java.library.path
- return null;
- } catch (UnsatisfiedLinkError ignored) {
- // Not on java.library.path, try from JAR
- }
-
- // Determine the platform-specific JNI library name from RocksDB's
Environment
- String jniLibName;
- try {
- jniLibName =
org.rocksdb.util.Environment.getJniLibraryFileName("rocksdb");
- } catch (Exception e) {
- jniLibName = ROCKSDB_JNI_LIB_NAME;
- }
-
- try (InputStream is =
CqCompactionFilterJni.class.getClassLoader().getResourceAsStream(jniLibName)) {
- if (is == null) {
- log.error("[CqCompactionFilterJni] RocksDB native library '{}'
not found on classpath", jniLibName);
- return null;
- }
- // Create a temp directory and extract the library there.
- // Our shim will be placed in the same directory so the DT_NEEDED
- // dependency resolves correctly.
- File tempDir =
Files.createTempDirectory("rocksdb-native").toFile();
- tempDir.deleteOnExit();
- File tempLib = new File(tempDir, jniLibName);
- Files.copy(is, tempLib.toPath(),
StandardCopyOption.REPLACE_EXISTING);
- tempLib.deleteOnExit();
- System.load(tempLib.getAbsolutePath());
- return tempDir.getAbsolutePath();
- } catch (IOException e) {
- log.error("[CqCompactionFilterJni] Failed to extract RocksDB
native library", e);
- return null;
- }
- }
-
/**
* Create a native CqCompactionFilter instance.
* Returns the raw C++ pointer as a jlong.
@@ -190,6 +103,12 @@ public class CqCompactionFilterJni {
*/
public static native void setMinPhyOffset0(long filterPtr, long
minPhyOffset);
+ /**
+ * Delete the native CqCompactionFilter instance.
+ * Must only be called after all compaction threads have stopped.
+ */
+ public static native void destroyNativeFilter0(long filterPtr);
+
/**
* Set the native compaction filter on the ColumnFamilyOptions via the
* public {@code setCompactionFilter} API.
@@ -209,20 +128,43 @@ public class CqCompactionFilterJni {
*/
@SuppressWarnings("UnusedReturnValue")
public static long createAndSetFilter(ColumnFamilyOptions options) {
- long ptr = createNativeFilter0();
- NATIVE_FILTER_PTR.set(ptr);
- setNativeFilter(options, ptr);
- return ptr;
+ synchronized (FILTER_LOCK) {
+ long oldPtr = NATIVE_FILTER_PTR.getAndSet(0);
+ if (oldPtr != 0) {
+ destroyNativeFilter0(oldPtr);
+ log.warn("CqCompactionFilter replaced without explicit
destroy");
+ }
+ long ptr = createNativeFilter0();
+ NATIVE_FILTER_PTR.set(ptr);
+ setNativeFilter(options, ptr);
+ return ptr;
+ }
}
/**
* Update the minPhyOffset on the current native filter.
*/
public static void setMinPhyOffset(long minPhyOffset) {
- long ptr = NATIVE_FILTER_PTR.get();
- if (ptr != 0) {
- setMinPhyOffset0(ptr, minPhyOffset);
- log.info("CqCompactionFilter setMinPhyOffset={}", minPhyOffset);
+ synchronized (FILTER_LOCK) {
+ long ptr = NATIVE_FILTER_PTR.get();
+ if (ptr != 0) {
+ setMinPhyOffset0(ptr, minPhyOffset);
+ log.info("CqCompactionFilter setMinPhyOffset={}",
minPhyOffset);
+ }
+ }
+ }
+
+ /**
+ * Destroy the native filter. Must be called only after
+ * cancelAllBackgroundWork(true) ensures no compaction thread is running.
+ */
+ public static void destroyNativeFilter() {
+ synchronized (FILTER_LOCK) {
+ long ptr = NATIVE_FILTER_PTR.getAndSet(0);
+ if (ptr != 0) {
+ destroyNativeFilter0(ptr);
+ log.info("CqCompactionFilter destroyed");
+ }
}
}
}
\ No newline at end of file
diff --git a/store/src/main/resources/native/cq_compaction_filter.cpp
b/store/src/main/resources/native/cq_compaction_filter.cpp
index 1d0bd84cd9..344655e071 100644
--- a/store/src/main/resources/native/cq_compaction_filter.cpp
+++ b/store/src/main/resources/native/cq_compaction_filter.cpp
@@ -35,21 +35,16 @@
#include "rocksdb/slice.h"
/* ------------------------------------------------------------------ */
-/* Windows stub implementations */
+/* Stub implementations for inherited virtual methods */
/* */
-/* On Linux/macOS, ELF/Mach-O shared libraries export all symbols by */
-/* default, so the shim resolves inherited virtual methods from */
-/* librocksdbjni at link time. On Windows, DLLs only export symbols */
-/* marked __declspec(dllexport) — rocksdbjni only exports JNI entry */
-/* points, not internal C++ class methods. We must provide stub */
-/* implementations for the Configurable/Customizable virtual methods */
-/* that appear in CompactionFilter's vtable. These stubs are never */
-/* called at runtime (RocksDB only invokes Filter() and Name() on */
-/* compaction filters), but the linker needs addresses for them. */
+/* CompactionFilter inherits from Customizable → Configurable, which */
+/* declare many virtual methods. These stubs fill the vtable so the */
+/* shim is fully self-contained — no DT_NEEDED on librocksdbjni. */
+/* None of these are called at runtime: RocksDB only invokes Filter() */
+/* and Name() on compaction filters, and disOwnNativeHandle() in Java */
+/* prevents the destructor path that could touch Configurable methods. */
/* ------------------------------------------------------------------ */
-#ifdef _WIN32
-
#include "rocksdb/configurable.h"
#include "rocksdb/customizable.h"
#include <unordered_map>
@@ -226,7 +221,7 @@ std::string Status::ToString() const {
} // namespace rocksdb
-#endif // _WIN32
+/* End of stub implementations */
/* ------------------------------------------------------------------ */
/* Our concrete compaction filter */
@@ -291,4 +286,10 @@
Java_org_apache_rocketmq_store_rocksdb_CqCompactionFilterJni_setMinPhyOffset0(
filter->SetMinPhyOffset(minPhyOffset);
}
+JNIEXPORT void JNICALL
+Java_org_apache_rocketmq_store_rocksdb_CqCompactionFilterJni_destroyNativeFilter0(
+ JNIEnv* env, jclass clazz, jlong filterPtr) {
+ delete reinterpret_cast<CqCompactionFilter*>(filterPtr);
+}
+
} // extern "C"
diff --git a/store/src/main/resources/native/cq_compaction_filter.dll
b/store/src/main/resources/native/cq_compaction_filter.dll
index 2dc74834f4..80a1bc5fae 100755
Binary files a/store/src/main/resources/native/cq_compaction_filter.dll and
b/store/src/main/resources/native/cq_compaction_filter.dll differ
diff --git a/store/src/main/resources/native/libcq_compaction_filter.dylib
b/store/src/main/resources/native/libcq_compaction_filter.dylib
index 58d6e6796a..b0b4f17997 100755
Binary files a/store/src/main/resources/native/libcq_compaction_filter.dylib
and b/store/src/main/resources/native/libcq_compaction_filter.dylib differ
diff --git a/store/src/main/resources/native/libcq_compaction_filter.so
b/store/src/main/resources/native/libcq_compaction_filter.so
index 46dabe1880..7b0140f662 100755
Binary files a/store/src/main/resources/native/libcq_compaction_filter.so and
b/store/src/main/resources/native/libcq_compaction_filter.so differ
diff --git a/store/src/main/resources/native/libcq_compaction_filter_aarch64.so
b/store/src/main/resources/native/libcq_compaction_filter_aarch64.so
index b77869f063..2bdbb559ed 100755
Binary files
a/store/src/main/resources/native/libcq_compaction_filter_aarch64.so and
b/store/src/main/resources/native/libcq_compaction_filter_aarch64.so differ