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

Reply via email to