From 79a9006c9450bbf66fc77058c69906f1d9859b3e Mon Sep 17 00:00:00 2001 From: lizhimins <707364882@qq.com> Date: Tue, 19 May 2026 14:09:48 +0800 Subject: [PATCH 1/4] [ISSUE #10334] Support native CqCompactionFilter with cross-platform JNI shim --- common/pom.xml | 4 +- .../common/config/AbstractRocksDBStorage.java | 23 +- docs/en/Native_RocksDB.md | 290 +++++++++++++++++ pom.xml | 8 +- store/pom.xml | 4 + .../ConsumeQueueCompactionFilterFactory.java | 47 --- .../rocksdb/ConsumeQueueRocksDBStorage.java | 72 ++++- .../store/rocksdb/CqCompactionFilterJni.java | 224 +++++++++++++ .../rocksdb/NativeCqCompactionFilter.java | 38 +++ .../store/rocksdb/RocksDBOptionsFactory.java | 4 +- .../resources/native/cq_compaction_filter.cpp | 294 ++++++++++++++++++ .../resources/native/cq_compaction_filter.dll | Bin 0 -> 136192 bytes .../native/libcq_compaction_filter.dylib | Bin 0 -> 41776 bytes .../native/libcq_compaction_filter.so | Bin 0 -> 23848 bytes .../rocksdb/CqCompactionFilterJniTest.java | 184 +++++++++++ 15 files changed, 1118 insertions(+), 74 deletions(-) create mode 100644 docs/en/Native_RocksDB.md delete mode 100644 store/src/main/java/org/apache/rocketmq/store/rocksdb/ConsumeQueueCompactionFilterFactory.java create mode 100644 store/src/main/java/org/apache/rocketmq/store/rocksdb/CqCompactionFilterJni.java create mode 100644 store/src/main/java/org/apache/rocketmq/store/rocksdb/NativeCqCompactionFilter.java create mode 100644 store/src/main/resources/native/cq_compaction_filter.cpp create mode 100755 store/src/main/resources/native/cq_compaction_filter.dll create mode 100755 store/src/main/resources/native/libcq_compaction_filter.dylib create mode 100755 store/src/main/resources/native/libcq_compaction_filter.so create mode 100644 store/src/test/java/org/apache/rocketmq/store/rocksdb/CqCompactionFilterJniTest.java diff --git a/common/pom.xml b/common/pom.xml index eec71184143..b931afcb89c 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -113,8 +113,8 @@ rocketmq-logback-classic - org.apache.rocketmq - rocketmq-rocksdb + org.rocksdb + rocksdbjni diff --git a/common/src/main/java/org/apache/rocketmq/common/config/AbstractRocksDBStorage.java b/common/src/main/java/org/apache/rocketmq/common/config/AbstractRocksDBStorage.java index bc4a18006f8..4875ce43e22 100644 --- a/common/src/main/java/org/apache/rocketmq/common/config/AbstractRocksDBStorage.java +++ b/common/src/main/java/org/apache/rocketmq/common/config/AbstractRocksDBStorage.java @@ -427,22 +427,35 @@ protected void manualCompactionDefaultCfRange(CompactRangeOptions compactRangeOp if (!hold()) { return; } - long s1 = System.currentTimeMillis(); + long before = getEstimateNumKeys(); + long startMs = System.currentTimeMillis(); boolean result = true; try { - LOGGER.info("manualCompaction Start. {}", this.dbPath); + LOGGER.info("ManualCompaction started, dbPath={}, estimateNumKeys={}", this.dbPath, before); this.db.compactRange(this.defaultCFHandle, null, null, compactRangeOptions); } catch (RocksDBException e) { result = false; scheduleReloadRocksdb(e); - LOGGER.error("manualCompaction Failed. {}, {}", this.dbPath, getStatusError(e)); + LOGGER.error("ManualCompaction failed, dbPath={}, error={}", this.dbPath, getStatusError(e)); } finally { release(); - LOGGER.info("manualCompaction End. {}, rt: {}(ms), result: {}", this.dbPath, System.currentTimeMillis() - s1, result); + long after = getEstimateNumKeys(); + long elapsed = System.currentTimeMillis() - startMs; + String ratio = before > 0 ? String.format("%.1f", (1.0 - (double) after / before) * 100) : "0.0"; + LOGGER.info("ManualCompaction finished, dbPath={}, elapsed={}ms, success={}, before={}, after={}, reduced={}%", + this.dbPath, elapsed, result, before, after, ratio); } } - protected void manualCompaction(long minPhyOffset, final CompactRangeOptions compactRangeOptions) { + private long getEstimateNumKeys() { + try { + return this.db.getLongProperty(this.defaultCFHandle, "rocksdb.estimate-num-keys"); + } catch (RocksDBException e) { + return -1L; + } + } + + protected void manualCompaction(final CompactRangeOptions compactRangeOptions) { this.manualCompactionThread.submit(new Runnable() { @Override public void run() { diff --git a/docs/en/Native_RocksDB.md b/docs/en/Native_RocksDB.md new file mode 100644 index 00000000000..2eec489a038 --- /dev/null +++ b/docs/en/Native_RocksDB.md @@ -0,0 +1,290 @@ +# Native RocksDB ConsumeQueue Compaction Filter + +## Background + +RocketMQ previously depended on a custom-forked RocksDB Java binding published as `org.apache.rocketmq:rocketmq-rocksdb:1.0.6`. This fork was maintained in the `apache/rocketmq-externals` repository and was essentially a republished copy of `org.rocksdb:rocksdbjni` with exactly **one** additional class: + +- `org.rocksdb.RemoveConsumeQueueCompactionFilter` — a RocksDB compaction filter that removes stale consume queue entries during compaction. Its C++ implementation and JNI glue lived in the forked C++ source tree under `utilities/compaction_filters/remove_consumequeue_compactionfilter.*` and `java/rocksjni/remove_consumequeue_compactionfilterjni.cc`. + +All other RocketMQ subsystems using RocksDB (Pop consumption state, config storage, index storage, timer storage, transaction half-message storage) used only standard RocksDB Java APIs and had no dependency on the fork's custom code. + +## Problem + +Maintaining a fork of RocksDB's Java bindings has several costs: + +1. **Upgrade friction** — every RocksDB upstream release requires rebuilding the entire fork to pick up the new native library and Java API +2. **Native build complexity** — the fork bundles a full C++ build pipeline for multiple platforms (Linux glibc/musl, macOS, Windows) +3. **Dependency duplication** — the `rocksdb/` module in the RocketMQ source tree duplicates ~190 classes that are identical to upstream `rocksdbjni` +4. **License ambiguity** — the fork republishes Facebook's RocksDB code under the Apache group + +## Solution + +Replace `rocketmq-rocksdb` with the official `org.rocksdb:rocksdbjni:8.4.4` and move the single custom compaction filter into a standalone native shim. + +### Why a native shim is needed + +The official `rocksdbjni` provides a `ColumnFamilyOptions.setCompactionFilter(AbstractCompactionFilter)` method, but its `AbstractCompactionFilter` Java class requires a native handle (raw `rocksdb::CompactionFilter*` pointer) passed to its constructor. The Java `filter()` method callback goes through a C++ trampoline that RocksDB's JNI layer manages internally — you can only subclass it from within the same JNI compilation unit. + +To implement a custom compaction filter outside the `rocksdbjni` build, we create a standalone C++ shared library that: +- Directly subclasses `rocksdb::CompactionFilter` in C++ +- Exposes JNI methods to create filter instances and update the `minPhyOffset` threshold +- Returns the raw `CompactionFilter*` pointer as a `jlong` to Java + +### Architecture + +``` +┌──────────────────────────────────────────────────────┐ +│ ConsumeQueueRocksDBStorage (Java) │ +│ - CqCompactionFilterJni.createAndSetFilter(cqCfOpts)│ +│ - CqCompactionFilterJni.setMinPhyOffset(offset) │ +└──────────────────┬───────────────────────────────────┘ + │ + ▼ +┌──────────────────────────────────────────────────────┐ +│ CqCompactionFilterJni.java │ +│ - Extracts libcq_compaction_filter.so to the same │ +│ temp dir as the already-loaded rocksdbjni .so │ +│ - Uses NativeCqCompactionFilter wrapper with │ +│ disOwnNativeHandle() + public setCompactionFilter │ +│ - Calls native createNativeFilter0() → raw pointer │ +└──────────────────┬───────────────────────────────────┘ + │ + ▼ +┌──────────────────────────────────────────────────────┐ +│ libcq_compaction_filter.so (native shim) │ +│ │ +│ class CqCompactionFilter │ +│ : public rocksdb::CompactionFilter { ... } │ +│ │ +│ 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│ +└──────────────────────────────────────────────────────┘ +``` + +### 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. + +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). + +**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` 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 `disOwnNativeHandle()`, background compaction threads would access a freed filter. The filter is then set via the public `ColumnFamilyOptions.setCompactionFilter()` API, avoiding reflection and ensuring JDK 17+ compatibility. + +**3. Shared temp directory for .so resolution** + +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. + +**4. Thread-safe minPhyOffset with std::atomic** + +The `CqCompactionFilter` uses `std::atomic` with `memory_order_relaxed` for `min_phy_offset_`. This is sufficient because there is a single writer (Java main thread via JNI) and one reader (compaction background thread), and eventual consistency is acceptable — a slightly stale threshold only means a few extra entries survive one compaction cycle. This replaces the earlier `pthread_mutex` approach, eliminating per-entry lock/unlock overhead during full compaction over hundreds of millions of entries. + +## Changed files + +| File | Change | +|------|--------| +| `pom.xml` | `rocksdb.version` → `rocksdbjni.version=8.4.4`; dependency changed to `org.rocksdb:rocksdbjni` | +| `common/pom.xml` | `rocketmq-rocksdb` → `org.rocksdb:rocksdbjni` | +| `common/.../config/AbstractRocksDBStorage.java` | `manualCompactionDefaultCfRange` enhanced with `estimateNumKeys` logging (before/after key count, elapsed time, reduction ratio); `manualCompaction` removed unused `minPhyOffset` parameter | +| `store/.../rocksdb/ConsumeQueueCompactionFilterFactory.java` | **Deleted** — replaced by native shim | +| `store/.../rocksdb/ConsumeQueueRocksDBStorage.java` | Use `CqCompactionFilterJni.createAndSetFilter()` instead of `CompactionFilterFactory`; added `triggerCompactionSync()` and `countEntries()` helpers | +| `store/.../rocksdb/RocksDBOptionsFactory.java` | Remove `setCompactionFilterFactory()` call from `createCQCFOptions()` | +| `store/.../rocksdb/CqCompactionFilterJni.java` | **Rewritten** — uses raw JNI pointer + `NativeCqCompactionFilter` wrapper via public API; added platform-aware library name detection (macOS `.dylib` / Linux `.so` / Windows `.dll`) | +| `store/.../rocksdb/NativeCqCompactionFilter.java` | **New** — thin `AbstractCompactionFilter` wrapper with `disOwnNativeHandle()` | +| `store/.../resources/native/cq_compaction_filter.cpp` | **Rewritten** — direct C++ subclassing, explicit linking, `std::atomic` for thread safety | +| `store/.../resources/native/libcq_compaction_filter.so` | **New** — pre-compiled native library (Linux x86_64) | +| `store/.../resources/native/libcq_compaction_filter.dylib` | **New** — pre-compiled native library (macOS arm64) | +| `store/.../resources/native/cq_compaction_filter.dll` | **New** — pre-compiled native library (Windows x86_64, MSVC v14.29) | +| `store/.../rocksdb/CqCompactionFilterJniTest.java` | **New** — integration test for compaction filter | + +## Building the native shim + +Prerequisites: `g++` / `clang++`, RocksDB C++ headers matching `rocksdbjni` version (8.4.4), JNI headers from your JDK. + +### 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 +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 +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 + +# 5. 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 +# 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) +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 \ + 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) + +# 6. Place the output +cp /tmp/rocksdb-native/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. + +```powershell +# 1. Set up environment (run vcvarsall.bat first, or use the VS Dev Command Prompt) +set "VCTools=C:\Program Files\Microsoft Visual Studio\2022\BuildTools\VC\Tools\MSVC\14.29.30133" +set "SDK=C:\Program Files (x86)\Windows Kits\10" +set "JAVA_HOME=C:\path\to\jdk8" + +# 2. Extract RocksDB headers +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) +cl.exe /LD /O2 /std:c++17 /GR- /EHsc /utf-8 ^ + /I"%JAVA_HOME%\include" ^ + /I"%JAVA_HOME%\include\win32" ^ + /I"rocksdb-8.4.4\include" ^ + /I"%VCTools%\include" ^ + /I"%SDK%\Include\10.0.19041.0\ucrt" ^ + /I"%SDK%\Include\10.0.19041.0\shared" ^ + /I"%SDK%\Include\10.0.19041.0\um" ^ + /Fecq_compaction_filter.dll ^ + store\src\main\resources\native\cq_compaction_filter.cpp ^ + /link /MACHINE:X64 ^ + /LIBPATH:"%VCTools%\lib\x64" ^ + /LIBPATH:"%SDK%\Lib\10.0.19041.0\ucrt\x64" ^ + /LIBPATH:"%SDK%\Lib\10.0.19041.0\um\x64" + +# 4. Verify exports +dumpbin /exports cq_compaction_filter.dll +# Should show: Java_org_apache_rocketmq_store_rocksdb_CqCompactionFilterJni_createNativeFilter0 +# Java_org_apache_rocketmq_store_rocksdb_CqCompactionFilterJni_setMinPhyOffset0 + +# 5. Place the output +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 | + +**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 +``` + +## Platform support + +`CqCompactionFilterJni.java` automatically detects the OS and architecture at runtime, selecting the correct library name and extension. + +| Platform | Library name | Architecture | Status | +|----------|-------------|--------------|--------| +| Linux (glibc) | `libcq_compaction_filter.so` | x86_64 | Pre-built | +| Linux (glibc) | `libcq_compaction_filter.so` | aarch64 | Requires rebuild | +| macOS | `libcq_compaction_filter.dylib` | arm64 | Pre-built | +| macOS | `libcq_compaction_filter.dylib` | x86_64 | Requires rebuild | +| Windows | `cq_compaction_filter.dll` | x86_64 | Requires MSVC rebuild | + +## Limitations + +1. **Jacoco incompatibility** — The jacoco Java agent can cause native crashes when combined with dynamically loaded native libraries. Unit tests should be run with `-Djacoco.skip=true` when testing RocksDB functionality. + +2. **Global singleton filter** — `CqCompactionFilterJni` stores the native filter pointer in a static `AtomicLong NATIVE_FILTER_PTR`. Only one filter instance is tracked globally per JVM. If multiple `ConsumeQueueRocksDBStorage` instances exist (e.g., in tests or multi-Broker processes), `setMinPhyOffset()` always updates the last-created filter. Earlier instances lose their threshold updates silently. + +3. **C++17 required** — The C++ source uses `std::atomic` 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`. + +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. diff --git a/pom.xml b/pom.xml index 893e58b4960..868faa57d10 100644 --- a/pom.xml +++ b/pom.xml @@ -139,7 +139,7 @@ 1.47.0-alpha 2.0.6 2.20.29 - 1.0.6 + 8.4.4 2.13.4.2 1.3.14 @@ -761,9 +761,9 @@ ${slf4j-api.version} - org.apache.rocketmq - rocketmq-rocksdb - ${rocksdb.version} + org.rocksdb + rocksdbjni + ${rocksdbjni.version} io.github.aliyunmq diff --git a/store/pom.xml b/store/pom.xml index 1600a007e09..70ecafe4282 100644 --- a/store/pom.xml +++ b/store/pom.xml @@ -71,5 +71,9 @@ io.github.aliyunmq rocketmq-shaded-slf4j-api-bridge + + org.rocksdb + rocksdbjni + diff --git a/store/src/main/java/org/apache/rocketmq/store/rocksdb/ConsumeQueueCompactionFilterFactory.java b/store/src/main/java/org/apache/rocketmq/store/rocksdb/ConsumeQueueCompactionFilterFactory.java deleted file mode 100644 index f19fb9e2036..00000000000 --- a/store/src/main/java/org/apache/rocketmq/store/rocksdb/ConsumeQueueCompactionFilterFactory.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.rocketmq.store.rocksdb; - -import java.util.function.LongSupplier; -import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.org.slf4j.Logger; -import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; -import org.rocksdb.AbstractCompactionFilter; -import org.rocksdb.AbstractCompactionFilterFactory; -import org.rocksdb.RemoveConsumeQueueCompactionFilter; - -public class ConsumeQueueCompactionFilterFactory extends AbstractCompactionFilterFactory { - private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.ROCKSDB_LOGGER_NAME); - private final LongSupplier minPhyOffsetSupplier; - - public ConsumeQueueCompactionFilterFactory(final LongSupplier minPhyOffsetSupplier) { - this.minPhyOffsetSupplier = minPhyOffsetSupplier; - } - - @Override - public String name() { - return "ConsumeQueueCompactionFilterFactory"; - } - - @Override - public RemoveConsumeQueueCompactionFilter createCompactionFilter(final AbstractCompactionFilter.Context context) { - long minPhyOffset = this.minPhyOffsetSupplier.getAsLong(); - LOGGER.info("manualCompaction minPhyOffset: {}, isFull: {}, isManual: {}", - minPhyOffset, context.isFullCompaction(), context.isManualCompaction()); - return new RemoveConsumeQueueCompactionFilter(minPhyOffset); - } -} 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 4392283c67c..925d8f91c7f 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 @@ -21,10 +21,14 @@ import java.util.List; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.config.AbstractRocksDBStorage; +import org.apache.rocketmq.common.constant.LoggerName; +import org.apache.rocketmq.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.store.MessageStore; import org.rocksdb.ColumnFamilyDescriptor; import org.rocksdb.ColumnFamilyHandle; import org.rocksdb.ColumnFamilyOptions; +import org.rocksdb.FlushOptions; import org.rocksdb.ReadOptions; import org.rocksdb.RocksDB; import org.rocksdb.RocksDBException; @@ -33,13 +37,13 @@ public class ConsumeQueueRocksDBStorage extends AbstractRocksDBStorage { + private static final Logger log = LoggerFactory.getLogger(LoggerName.ROCKSDB_LOGGER_NAME); + public static final byte[] OFFSET_COLUMN_FAMILY = "offset".getBytes(StandardCharsets.UTF_8); private final MessageStore messageStore; private volatile ColumnFamilyHandle offsetCFHandle; - private ConsumeQueueCompactionFilterFactory compactionFilterFactory; - public ConsumeQueueRocksDBStorage(final MessageStore messageStore, final String dbPath) { super(dbPath); this.messageStore = messageStore; @@ -67,20 +71,27 @@ protected boolean postLoad() { final List cfDescriptors = new ArrayList<>(); - this.compactionFilterFactory = new ConsumeQueueCompactionFilterFactory(messageStore::getMinPhyOffset); - - ColumnFamilyOptions cqCfOptions = RocksDBOptionsFactory.createCQCFOptions(this.messageStore, this.compactionFilterFactory); + ColumnFamilyOptions cqCfOptions = RocksDBOptionsFactory.createCQCFOptions(this.messageStore); this.cfOptions.add(cqCfOptions); cfDescriptors.add(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cqCfOptions)); ColumnFamilyOptions offsetCfOptions = RocksDBOptionsFactory.createOffsetCFOptions(); this.cfOptions.add(offsetCfOptions); cfDescriptors.add(new ColumnFamilyDescriptor(OFFSET_COLUMN_FAMILY, offsetCfOptions)); + + if (CqCompactionFilterJni.isLoaded()) { + CqCompactionFilterJni.createAndSetFilter(cqCfOptions); + CqCompactionFilterJni.setMinPhyOffset(messageStore.getMinPhyOffset()); + log.info("CqCompactionFilter created and set, minPhyOffset: {}", messageStore.getMinPhyOffset()); + } else { + log.warn("CqCompactionFilterJni native library not loaded, compaction filter will not be installed"); + } + open(cfDescriptors); this.defaultCFHandle = cfHandles.get(0); this.offsetCFHandle = cfHandles.get(1); } catch (final Exception e) { - LOGGER.error("postLoad Failed. {}", this.dbPath, e); + log.error("postLoad Failed. {}", this.dbPath, e); return false; } return true; @@ -91,11 +102,6 @@ protected void preShutdown() { if (this.offsetCFHandle != null) { this.offsetCFHandle.close(); } - - if (this.compactionFilterFactory != null) { - this.compactionFilterFactory.close(); - } - } public byte[] getCQ(final byte[] keyBytes) throws RocksDBException { @@ -116,10 +122,13 @@ public void batchPut(final WriteBatch batch) throws RocksDBException { } public void manualCompaction(final long minPhyOffset) { + if (CqCompactionFilterJni.isLoaded()) { + CqCompactionFilterJni.setMinPhyOffset(minPhyOffset); + } try { - manualCompaction(minPhyOffset, this.compactRangeOptions); + super.manualCompaction(this.compactRangeOptions); } catch (Exception e) { - LOGGER.error("manualCompaction Failed. minPhyOffset: {}", minPhyOffset, e); + log.error("manualCompaction Failed. minPhyOffset: {}", minPhyOffset, e); } } @@ -130,4 +139,41 @@ public RocksIterator seekOffsetCF() { public ColumnFamilyHandle getOffsetCFHandle() { return this.offsetCFHandle; } + + /** + * Synchronously trigger compaction with an updated compaction filter threshold. + * This method updates the native compaction filter's minPhyOffset and then + * performs a full compaction on the default column family. + */ + public void triggerCompactionSync(long minPhyOffset) throws RocksDBException { + if (CqCompactionFilterJni.isLoaded()) { + CqCompactionFilterJni.setMinPhyOffset(minPhyOffset); + } + db.compactRange(this.defaultCFHandle); + } + + /** + * Flush all memtables to SST files. + */ + public void flushAll() throws RocksDBException { + try (FlushOptions flushOpts = new FlushOptions()) { + flushOpts.setWaitForFlush(true); + flush(flushOpts); + } + } + + /** + * Count all entries in the default column family by iterating. O(N), use only in tests. + */ + public long countEntries() { + long count = 0; + try (RocksIterator iter = db.newIterator(this.defaultCFHandle)) { + iter.seekToFirst(); + while (iter.isValid()) { + count++; + iter.next(); + } + } + return count; + } } 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 new file mode 100644 index 00000000000..d78579f37c1 --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/rocksdb/CqCompactionFilterJni.java @@ -0,0 +1,224 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.store.rocksdb; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.concurrent.atomic.AtomicLong; +import org.apache.rocketmq.common.constant.LoggerName; +import org.rocksdb.ColumnFamilyOptions; +import org.apache.rocketmq.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; + +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 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(); + if (os.contains("mac") || os.contains("darwin") || os.contains("osx")) { + String arch = System.getProperty("os.arch"); + 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 = "libcq_compaction_filter.so"; + SHIM_LIB_EXTENSION = ".so"; + ROCKSDB_JNI_LIB_NAME = "librocksdbjni-linux64.so"; + } + } + + static { + loadNativeShim(); + } + + private static synchronized void loadNativeShim() { + if (loaded) { + 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)) { + if (is == null) { + 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); + } + Files.copy(is, tempLib.toPath(), StandardCopyOption.REPLACE_EXISTING); + tempLib.deleteOnExit(); + System.load(tempLib.getAbsolutePath()); + loaded = true; + log.info("[CqCompactionFilterJni] Native library loaded from classpath: {}", tempLib.getAbsolutePath()); + } catch (IOException e) { + log.error("[CqCompactionFilterJni] Failed to load native shim", e); + } + } + + /** + * Returns whether the native compaction filter shim was successfully loaded. + */ + public static boolean isLoaded() { + 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). + *

+ * This method deliberately uses {@code System.loadLibrary("rocksdbjni")} + * rather than {@code RocksDB.loadLibrary()} for the following reasons: + *

    + *
  1. Avoid unnecessary side effects — {@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.
  2. + *
  3. Control the temp directory location — 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.
  4. + *
  5. Avoid class-loading coupling — {@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.
  6. + *
+ * + * @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. + */ + public static native long createNativeFilter0(); + + /** + * Update the minPhyOffset threshold on an existing native filter. + */ + public static native void setMinPhyOffset0(long filterPtr, long minPhyOffset); + + /** + * Set the native compaction filter on the ColumnFamilyOptions via the + * public {@code setCompactionFilter} API. + *

+ * The wrapper uses {@code disOwnNativeHandle()} so that closing the + * ColumnFamilyOptions does not free the native filter — this prevents + * use-after-free when AbstractRocksDBStorage closes options before the DB. + */ + public static void setNativeFilter(ColumnFamilyOptions options, long filterPtr) { + NativeCqCompactionFilter filter = new NativeCqCompactionFilter(filterPtr); + options.setCompactionFilter(filter); + } + + /** + * Create the native filter and set it on the ColumnFamilyOptions. + * Returns the native pointer for later threshold updates. + */ + @SuppressWarnings("UnusedReturnValue") + public static long createAndSetFilter(ColumnFamilyOptions options) { + 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); + } + } +} \ No newline at end of file diff --git a/store/src/main/java/org/apache/rocketmq/store/rocksdb/NativeCqCompactionFilter.java b/store/src/main/java/org/apache/rocketmq/store/rocksdb/NativeCqCompactionFilter.java new file mode 100644 index 00000000000..6a3101c261a --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/rocksdb/NativeCqCompactionFilter.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.store.rocksdb; + +import org.rocksdb.AbstractCompactionFilter; +import org.rocksdb.Slice; + +/** + * Thin Java wrapper around a native CqCompactionFilter C++ pointer. + *

+ * The native filter is allocated by {@link CqCompactionFilterJni#createNativeFilter0()} + * and its lifetime is managed externally (it lives for the entire JVM session). + * {@link #disOwnNativeHandle()} is called so that {@code close()} does not + * free the native memory — this is critical because {@code AbstractRocksDBStorage} + * closes {@code ColumnFamilyOptions} (which closes this filter) before closing + * the DB, while background compaction threads may still reference the filter. + */ +class NativeCqCompactionFilter extends AbstractCompactionFilter { + + NativeCqCompactionFilter(long nativeHandle) { + super(nativeHandle); + disOwnNativeHandle(); + } +} diff --git a/store/src/main/java/org/apache/rocketmq/store/rocksdb/RocksDBOptionsFactory.java b/store/src/main/java/org/apache/rocketmq/store/rocksdb/RocksDBOptionsFactory.java index b74cf8c85d5..37eec67d357 100644 --- a/store/src/main/java/org/apache/rocketmq/store/rocksdb/RocksDBOptionsFactory.java +++ b/store/src/main/java/org/apache/rocketmq/store/rocksdb/RocksDBOptionsFactory.java @@ -41,8 +41,7 @@ public class RocksDBOptionsFactory { - public static ColumnFamilyOptions createCQCFOptions(final MessageStore messageStore, - ConsumeQueueCompactionFilterFactory consumeQueueCompactionFilterFactory) { + public static ColumnFamilyOptions createCQCFOptions(final MessageStore messageStore) { BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(). setFormatVersion(5). setIndexType(IndexType.kBinarySearch). @@ -93,7 +92,6 @@ public static ColumnFamilyOptions createCQCFOptions(final MessageStore messageSt setTargetFileSizeBase(256 * SizeUnit.MB). setTargetFileSizeMultiplier(2). setMergeOperator(new StringAppendOperator()). - setCompactionFilterFactory(consumeQueueCompactionFilterFactory). setReportBgIoStats(true). setOptimizeFiltersForHits(true); } diff --git a/store/src/main/resources/native/cq_compaction_filter.cpp b/store/src/main/resources/native/cq_compaction_filter.cpp new file mode 100644 index 00000000000..1d0bd84cd90 --- /dev/null +++ b/store/src/main/resources/native/cq_compaction_filter.cpp @@ -0,0 +1,294 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Native compaction filter for ConsumeQueue entries. + * + * Subclass rocksdb::CompactionFilter directly, create instances in C++, + * and pass the raw C++ pointer as a jlong to Java. Java's + * AbstractCompactionFilter(nativeHandle) wraps it seamlessly. + * + * All rocksdb symbols are declared weak so they resolve at runtime to the + * symbols already loaded by the JVM's ClassLoader. + */ + +#include +#include +#include +#include + +#include "rocksdb/compaction_filter.h" +#include "rocksdb/slice.h" + +/* ------------------------------------------------------------------ */ +/* Windows stub implementations */ +/* */ +/* 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. */ +/* ------------------------------------------------------------------ */ + +#ifdef _WIN32 + +#include "rocksdb/configurable.h" +#include "rocksdb/customizable.h" +#include +#include + +namespace rocksdb { + +struct ConfigOptions; +struct DBOptions; +struct ColumnFamilyOptions; +class OptionTypeInfo; + +// --- Configurable virtual methods (defined in options/configurable.cc) --- + +Status Configurable::GetOption(const ConfigOptions&, const std::string&, + std::string*) const { + return Status(); +} + +bool Configurable::AreEquivalent(const ConfigOptions&, const Configurable*, + std::string*) const { + return true; +} + +Status Configurable::PrepareOptions(const ConfigOptions&) { + return Status(); +} + +Status Configurable::ValidateOptions(const DBOptions&, + const ColumnFamilyOptions&) const { + return Status(); +} + +const void* Configurable::GetOptionsPtr(const std::string&) const { + return nullptr; +} + +Status Configurable::ParseStringOptions(const ConfigOptions&, + const std::string&) { + return Status(); +} + +Status Configurable::ConfigureOptions( + const ConfigOptions&, + const std::unordered_map&, + std::unordered_map*) { + return Status(); +} + +Status Configurable::ParseOption(const ConfigOptions&, const OptionTypeInfo&, + const std::string&, const std::string&, + void*) { + return Status(); +} + +bool Configurable::OptionsAreEqual(const ConfigOptions&, const OptionTypeInfo&, + const std::string&, const void*, + const void*, std::string*) const { + return true; +} + +std::string Configurable::SerializeOptions(const ConfigOptions&, + const std::string&) const { + return ""; +} + +std::string Configurable::GetOptionName(const std::string& name) const { + return name; +} + +// Non-virtual, but referenced by inline code paths +void Configurable::RegisterOptions(const std::string&, void*, + const std::unordered_map*) {} + +Status Configurable::ConfigureFromMap( + const ConfigOptions&, + const std::unordered_map&) { + return Status(); +} + +Status Configurable::ConfigureFromMap( + const ConfigOptions&, + const std::unordered_map&, + std::unordered_map*) { + return Status(); +} + +Status Configurable::ConfigureOption(const ConfigOptions&, const std::string&, + const std::string&) { + return Status(); +} + +Status Configurable::ConfigureFromString(const ConfigOptions&, + const std::string&) { + return Status(); +} + +Status Configurable::GetOptionString(const ConfigOptions&, + std::string*) const { + return Status(); +} + +std::string Configurable::ToString(const ConfigOptions&, + const std::string&) const { + return ""; +} + +Status Configurable::GetOptionNames(const ConfigOptions&, + std::unordered_set*) const { + return Status(); +} + +Status Configurable::GetOptionsMap( + const std::string&, const std::string&, std::string*, + std::unordered_map*) { + return Status(); +} + +// --- Customizable virtual/override methods (defined in options/customizable.cc) --- + +Status Customizable::GetOption(const ConfigOptions&, const std::string&, + std::string*) const { + return Status(); +} + +bool Customizable::AreEquivalent(const ConfigOptions&, const Configurable*, + std::string*) const { + return true; +} + +std::string Customizable::GetOptionName(const std::string& name) const { + return name; +} + +std::string Customizable::SerializeOptions(const ConfigOptions&, + const std::string&) const { + return ""; +} + +std::string Customizable::GenerateIndividualId() const { + return "stub"; +} + +Status Customizable::GetOptionsMap( + const ConfigOptions&, const Customizable*, const std::string&, + std::string*, std::unordered_map*) { + return Status(); +} + +Status Customizable::ConfigureNewObject( + const ConfigOptions&, Customizable*, + const std::unordered_map&) { + return Status(); +} + +// --- Status methods (defined in util/status.cc) --- + +Status::Status(Code _code, SubCode _subcode, const Slice& msg, + const Slice& msg2, Severity sev) + : code_(_code), subcode_(_subcode), sev_(sev), + retryable_(false), data_loss_(false), scope_(0) {} + +std::unique_ptr Status::CopyState(const char* s) { + if (s == nullptr) return nullptr; + const size_t n = std::strlen(s) + 1; + char* result = new char[n]; + std::memcpy(result, s, n); + return std::unique_ptr(result); +} + +std::string Status::ToString() const { + return "OK"; +} + +} // namespace rocksdb + +#endif // _WIN32 + +/* ------------------------------------------------------------------ */ +/* Our concrete compaction filter */ +/* ------------------------------------------------------------------ */ + +class CqCompactionFilter : public rocksdb::CompactionFilter { +public: + const char* Name() const override { + return "ConsumeQueueCompactionFilter"; + } + + bool Filter(int /*level*/, const rocksdb::Slice& /*key*/, + const rocksdb::Slice& existing_value, std::string* /*new_value*/, + bool* /*value_changed*/) const override { + static const int CQ_MIN_SIZE = 28; + if (existing_value.size() < static_cast(CQ_MIN_SIZE)) { + return false; + } + const unsigned char* data = + reinterpret_cast(existing_value.data()); + int64_t phy_offset = + (static_cast(data[0]) << 56) | + (static_cast(data[1]) << 48) | + (static_cast(data[2]) << 40) | + (static_cast(data[3]) << 32) | + (static_cast(data[4]) << 24) | + (static_cast(data[5]) << 16) | + (static_cast(data[6]) << 8) | + (static_cast(data[7])); + + int64_t min_offset = min_phy_offset_.load(std::memory_order_relaxed); + return phy_offset < min_offset; + } + + void SetMinPhyOffset(int64_t offset) { + min_phy_offset_.store(offset, std::memory_order_relaxed); + } + +private: + std::atomic min_phy_offset_{0}; +}; + +/* ------------------------------------------------------------------ */ +/* JNI bindings */ +/* ------------------------------------------------------------------ */ + +#include + +extern "C" { + +JNIEXPORT jlong JNICALL +Java_org_apache_rocketmq_store_rocksdb_CqCompactionFilterJni_createNativeFilter0( + JNIEnv* env, jclass clazz) { + CqCompactionFilter* filter = new CqCompactionFilter(); + return reinterpret_cast(filter); +} + +JNIEXPORT void JNICALL +Java_org_apache_rocketmq_store_rocksdb_CqCompactionFilterJni_setMinPhyOffset0( + JNIEnv* env, jclass clazz, jlong filterPtr, jlong minPhyOffset) { + CqCompactionFilter* filter = reinterpret_cast(filterPtr); + filter->SetMinPhyOffset(minPhyOffset); +} + +} // extern "C" diff --git a/store/src/main/resources/native/cq_compaction_filter.dll b/store/src/main/resources/native/cq_compaction_filter.dll new file mode 100755 index 0000000000000000000000000000000000000000..2dc74834f41e6de650a06b5c48e527ded91ecc70 GIT binary patch literal 136192 zcmeFadw5jU)%ZV?WXKQ_&Y%Q?iVPAp8ZVJ}Neq-ZWFlu^qCr6M3W~%V6(lnRD=1+S zWjKz-wzhBI+7|82`o69DwuOMLCPb2Ok(*kB+KRQ+Gme+w1p+Gb`>cItGU3v1`+J|? z^Zfq#=6Q0?KKruv-fOSD_F8MNy{GoN)sAe3!;!;(GU;%%@|HiJdj6jueIySZy1 z_k&*^)9MVqJZ8r2cQkkxE?RKgqMN_zoqO}$cP|KeZ<*&^6u#Sg$K784mD9c7TyX2W z^9u^{ic_G2C8thu2Nn%W|DSsLVox^Tr=EU~r$D_MJx%J};2EOwH4M9f_jThJdj_fZ zJszieH+o8Wca3lK3|8+&srN$lzUq#-w@V%A9@W)29Jf9=$T7y5e`h+b*KxXcNY>C% zj*FZQ$H#W;!>94~sPJ-P?QoXEF-S#a-j0Pt3KIFta?ByCT`2uJ_S=qGeZcAXoHEUQ zPDhCu}6Df6=Wshi;~C&q`+U{15LVDpEA#JKxUc@QotfL&f>LKOp~rKcB_ z%ZzBKxXg%r?P(~`Jh?AufY63H6So`dllI>OhWUjN*{&H?`wVUJJ|lL8(`Y&<(_qnA zMod4;sA^w4$1uCC`}3&X>^553LQ{-L+gV0r?_MKn^d<6)mdzoTbrb2qSdG(&Qt#_h zVquZA_bwXKVMH5>3{#q7-cVd&nDdK$M&u>$><;U;a~+OtR$(qr`SVSeZb^5{jYg~> zT;p_T*R&b2>24!B#E2PgZCIA|t3m+BTnkB5wRH%r>pPB9{^H}7zs)Yc<~Zd6+oS`w zOIQ2=gu01Zta5_oc2$XLsr@*ry_@{UYwrOjiy)!Dl^Fmsp7u~0=19XlXtcbkHNUC` zdVXDc4l$A&jiy6ttWFJ@dY=*5cvhgan_(l^ zH(Gn>`v3|s5{H>xY9zX{xIqB*c%xYDgQQ89}f?tkqEH%s?!`y1Emf9HX3L|=f5naH@ zEr15-jtqW!+!vgqMjYfmS}nuqG$QTp_>EvHgB(JFAGacPa-QTPCpd~a%{L^w^}^v~ za({<2RItG-C^L6h!&TikNL^hvg&hIzNO)ZnkAio1KfIC@yq6$MY4*PdZ_-il9$`hW z+s9N;?F;=2c+WbIq_8f<5*dj4u1T~U=!0dugnR(EP^ zhcQ-eW5`avvNFN!OfV-CbY+5rGQr$bFkt$;M%7O3@ix24R;j+f_HEm!^#}wrdn;<- z2ZkPrJ40vck%P|ANWJw7>3*#6g>QhMSiwZ_Zk<`?aKOGIFI99HrZZ>;i{~U){|EWX zH#fIwkF}faN2GQ7i)T54=IYf75+6p?UtD6C{^Hvux3Xf!bF!A7X=A@4k)#YdKaImmBr51N~F^YwrPRA;*Iq1)-M zPoJ#@Ce5GVU2i1oZZF?#q*ws#G|<&1Yk(1*>RCBN8!h!a0?D>X75R?v0mIxWbNP=_ z?*P`7hWAFmPM?1e00Yv?kD`ZkepUZ!sX`WzLc??$<}2_ldfrWQz1Ihld)50$a@q!k zbWKt`%ZO$f(Mg__SxkmZCdD7rc-l-N@+}YTJ!F{swC0%9W;*G%(bb`PYQ6%?Tt40- z{fj&x%jAxrIa#fgTCi$kIG*4&g(Px;040C&O^~zMEb#1iIVI(`2k1tA>PZypT ztm+7lQ7GGC-K)xnPk5F-E5;$5kS@-K&iNkKX7ffBvkk6x2ZPz)A->P98u%Ug&@f`b zVy|_JU^-SffQo!i@UIWe`LwyyA$-$VWzAxcVAJ=U&6SqbOkZ1_eob zf>oOuY4<^q5JX<6G@|ziR;=ICiO!r$9q~`3_b&?e_;= z8ZR@<*M#Nn`oJjNXjEB^XB)Aca|Ci#x1mkyGh&mS#ssxgUCc6$SkbLL_zZv$AJ(76 zo4@7O$@BpF(3*e5XE3(#tU%nj?k0Qif&{ZODf5x+`oFc%I4 zQ-=6;R2$Z(K^)PB#1Penw^ZjdZ2(V(VeYai`z}Zg1XaA1CiT0iaX=pvny{vWNDvUk zi4vYx3RFJcm*Kjeetnq?4YGcOro7Es{}tmFe@4RnFk&uK8X!`$bN@zXC~t!-(AJ-& z;xt;+*tP5qRYpE=hDziyB65hV=_RJ5!y4*>fL+$xtjyZlHZ@|x7a6%Ybm*vslpm92 ztR${h50Pd1*B-BaHJxU}KV_{c-)!9vEEc}zZfS@Mh^u;5`ip(ObXEkngvf@ z>~!eKT}jsu4?7(SM~N)xJi`K6p6uClQ^$IwAcu7WuvZIhr)9h{->QSA>srdUv^`K@ z7ly;UR5rUK*&TKHNU#!Asxae?Ot^`Ntnj5)Qo8=1Bv_9EIC942#5?H5k7#T8Zr`dM z)*lEV3%nV5*J(LHo7s_g(r(1-q^w@O%`jgwqO-igsIMqmhiqWD;g>Q%K6s@P2U>QA zwaUUje(iKTkXzms7kfi&QZmx#Y^*U%mtQe}ws2k-tGSF$VtBIf+bHK|kF4*)(?kOY zYf1*N)_k2zW|Dp|Fljx-?W9Q38NQ^YEj*55<-3Kw{g^bPWj|D9b;5|Kcxwei4do9_ z=Sdb8Q7y8IE0W9(`}%7@YoUnYA}y_92#=`1K|FC$PS3#VKtsGdym`p9%QWzL+tsJL527|8_;u9aNN-e1L*M_xx!nT7loA-ooLlJ7c(|V0TU?fXg z-V7Jh%C)3gAAxU7=A)2T4d3b-CrIIKfF)I5V-x3|__=Wx_ly4L?CnG7#6<`#TNfW_d~^2*-QR4F#r z@a)zhFw*);y(l~*Sh`VLrw=z`(~7VG5FBL0@|Oo=`CE+W@Zvz#u27LVB|lizz9`R} zyt5RU7IoM-TD8Y|5!9CN1}{UJ z-wfv(4;4Prr$&!{r11!!g{AKanS!O~x=GK?bUQYuY)=Nwc+8wLy)mwKmP}T#$ zm(OJ3Z_cGb=8rA18L`KDNJQ$WDQ`;_{zy`QGv0{YT3kVDY_CdHnSv-c(O_{|5bha^ zS(3mf0()(*1ia;&lZCe^&@J7Ph+JQ1L|dd9X^4u{TSY0x;>Pr8St;$bhD!Bz2^N<* zEID;pw0yj02+bx7M*tIy88K2srSuuGnqKXDV8mZ+NTn{Rl-hjo%TmEZI3LuzV@9^J zsTk(t#XCWk(e#qw>_+%ddX4rPu~DD2OX&#~ufz$g{?h+J$-*^Y6k`yks<#)#?U^d` z(;f#~J&TR%u*?4OsIs^Ild>Bj;FJ!NVjG_Fw(`x|lYXH9mpp^8Yix69SWCnHS43xrx7j_jTOu|58( zo-igpAQkT&zc*YEDflU=zUZjOc*)3?h;XJX%P)D9p`pC3(zP0tv1Ev7(Eq3CreEeQ zvSr@o_qvgUK1Egt1yRt2eEddaN76`kEgxSY%605Yr?qUfka>@!V&Q`IT!U`XZRKJ1 zKt|U)FLyfwQKS=*|Gy?Lnk2BIS)G{3QtO1Woh}tE6(vr|EGd3M0}%d07$F8*?NU!< ztfRAJg%=9&5R-)Dcu!_^tl(EzP~*;G`xQGD(7W6 z!@}np$@W09o4D}DwPtsqJ)RvNjdl{!?DTv)J-pAreuKrVC)u%@Oj)5zrE^t4xZW_a zgje~8xx)+@I}HRq;RytbBv=$aAA_tnklY-8IS|=9I#o&NN9ppXh`m;lKPEXh5s}aS ze=|SN&@dBhX0ySrJ<~N6%JlD=tHq{Zeu~ci+0SoE;~|rdHXMu<{uxCMG-%pG`5?KT zzchX(n-8y$eW}{)Eqd~uj@3ak%beo&YwM=u`?a-K4Gl&e$o}>IsFCkqSvRzUbkfMM zc1j*;^(6XN8hIVsy1e?RyJNMPN7+d^l0GOZ<$O7$50Y|exC1~+F|tm|qO53Gn~C0M%Au{p;yH6vjSC{wG}!m((1^BC;kEJ&{n*Q zR1j$Db9fz&#Umu(C~`Cw?01br7|+sLZbhWuba+A5OHbUtV1CwK$pGfH2Va6FgXZQy z~&1^l3_U%qdoWt zPn)teDBClTvL(afd$iGh2U0pSOA>fmIb&_w6V zt+v19B@||V$&0UN?Js!=z1d&#;%lenO-LfiWcWWO1 zuwGV7PatA>_P1-Hlf>LfgEbFnIm5JJZq$_8(hhs|$PF#f&UPk-A2B}K*}6wNd$0nf zd4j|C8clIQ0`UvAvjcM^p-vKfEH*^e5$Ti2GKusOQ$~zeVmz8Be^~gp0+BW80j;Fn zt~V!ia)ut*ESDBb^JMkvC8rj0>zXG|*MUw})UQtOEX8X@)$PVY z5;Q-szJjsVnzy-Y%}WF3?26jb&-d5l)az!Wp|!k4UqnqVlkKUBs&Xx|3XWtRHnNkZ z|I3_URj;<L4M0{tZ4a1} zee7nLGrq*~{<7b!CGk~}S4v1hjzQi@7QQ(ephL->?2KQ`T*hR!Nx+y7c>U&(K0m65H7Ak z5kEIEDt5W^Ij0_-Z1|-d{jAM?Nl)&ip_EE?qHECD6<5;@g^OT}ee8gFeObV~t0G`F z5Pnymt>2{IsNaA{V#}Dx!o8;mY$6?J5r;!UHgEmPy%aT*USpS|qM{b2!hMOKD~E=})q4NmGeWMTy_WZ7XMBlEL`i zRH-OqdgR`soN%qFd(RVLH`15Y=tIaWlYU%k%&p5o`WNdg{lu|cemGTbJ|i|xzQ89OQJLI_q!go#7O)DrfM)7ZqrV0-{}pO zrY3uCXhN{6U^_bv+Cy_F3da$KkYcn1*YN+W%}d${?&N=OJ&)e-tHL$XEaSw~{;A>@ z?H=J3pDSK5Xp%O}@9Y&eA^V3~sZ%S>z%K=UsSUqWZzH&q|GoV8hF@cWk{S|^AF)1} zyMnQT{acgCYO}fNS=f6pyBppLeB@dDL!#66MW-FCk52n~9Q!2ap7vQD6tV$#d(KbZxtH&J{KCPU>i*@jx3OcAr7_YKOJ zJ>|Qt!)hYE8N#&XI`p1E>JeRVtMvzrmg%W!&lfy6)FgJPI;@LO7Nu_)y;d1EA``v? z@MKta23AudS(rE(?pd&mShLT%T3rZJCcR- zPZkiJ1N31uT&9`u30VKH0b&&)+DBcx_lV6X89IY;c@LQ%hV?oLG;3&=D*%$DJ}h{- z7V?oJWocG$%!2PrSqjE@1jAEExp-IWDMlAnF{C6h{|7#@Q1^*zH5q?|MC7OF`T#mO6d(DHv*o5c1 zMK#=>tTo>Wm=?=%z-&$yjmmPY4pen&%?TPIEVkwE1Zu0=wdP#}%rFbr;|Cm)O;q_I zfvUf1Ek7hBaaXPR#{)gL3i1Qy4!u>R$7EqGt+Piie08$$?IJ<#HR1F8vFcoMf#E-5 z@3MY%xx()VkfTZ`3kx&lP79cww0DTK_n~H0w-MQt6R7$yew!Nk4pKkr8j#9R4QA|< zg)dSciK#$g6$m_bMc2oEyZfK41JtLdA20_y;UynK=d5@zUn< z&B2&U6JuSh&?9{?Aq#;oTe52Au;uXs=>>qUCwwCHGhggA5n(V^un*hgdJ!pr^cQ7^ zEc`1kO9v7#fR&#ve8m7je>+J44S=&=;6O$K=NC4dUkaRqve9)UoIwNNtWt0ko!B-> z^9>r+Oi2t2vBLWVDw9Z{-X`Fl1{}UBj2bO-Ckr>wp#;{>DZsI(l|7@5;R!lU7O{eA z0Za|9aRkJ65i2Z{Dp-ac))mrJcE}X$A%3$nS$KU4{3|2U;2D;b|A@_0c@E&eGLPZG z$crwpYOBYb;!lTohW{Pa6`gk=|lM2u66<*C0=OclJrhMQ05dF z(z^h)@L_75limXWU}_KGCMjyRxTWpU78|nth1s7@(vFs=7`OgBHOo;CLdnAOC|JJP z>`NAw3kaYa$wDtd*-*kMEngfVa#;^#ThTD>y@~Q|)?-N_@W+b{ii2_He(MM5#5YXy(<| z$PN_52z6e*o18+O&Bf0u^6XI;jv$H>^WViiylYum zV=gZ(Lh)7ZczdiV0aU&6{j#<$pgz(+Je$JeD!$eF4f2kp%(PR6N?+`h@1|0!NO?gH z5=b9?uu6oTw?6|rOw%%PMZ*C9njX6X&g@{Y{^Uwe&jUSvbCYq(W@D`4l01@ye;%f0 z$SE|Ufkv=Y3Q+4;sk(LuEk|C41rQ5ZVFf-s2kZTn7uALL8?i}4@UQkQo`jFv%rNT}bd{#el7N=59lLphvWLterb z*7+L`>o3R(Tp#JXAv87+yYetX)jfP&5Go8-?QcLwif09qo!UC5WC{NxXuell`d%RN z)tQU03`7o}&`_hVzZd|uW>;+~{vB!%chZ{F2*;?EP8`lOb*qwfw3Y@^u8if}7Qcpo z00e|xUW@#VHU)NloC00C-MT;3zbYHtGZ}D8Q{cu3l+yh+gxXk6qjd`@saE9L#g=D^ z)uf7jL`}4B`x*VHEKQ}riPETKw5vfSTqfBrLqAo9e)v@8AyY{N*kEa!zCKUpOsvKp zyvJ;~CuC}TC6#iYoiaaDQauXvK`lciN1-r7fjIP46QT55<;vjyj zrQMhvv#sk>?LNhZ|L~dpJ-Rjp-)X1ZpGldRN_n4Zz(QYff0;8cVP$Lx zy2?)%DPxD)g6wDcbF8Nr%wBDWO?Xr%(7}g)m}6t;bSroiuNjH8Ney`Y$PgPI*!g6n~5g$ok46UQ%lgt~E(H z!EbulQ1yealKA};BR%FiZ_M%K5U&UkC2YwmQC$a}<{_(uF=Vw1l_UJ*S#M0Vzs?AX zjgq}EcvRXG?MVNj8~T#T{v6f_)DUSKt8<-4`~icw;4<(c{m4=lTUf-_v32Piib~Y@ zC@BXq-fewRMlVIGxd~lVq+3cOu*l*m(h2TPMr=NNxU!vm(E4nT5QtjZx7&h?IEqL^ z-PWbJxI{KH(wc$Q>ZJ*1fF=Sw{J&PyP%^zOt26om4SVszUKA`4YM6SUJ$k$s}-5%HqDq zh;-$0hOGUQY@=$6p-ui;4o;k$aSMqK0lK(_Bs*~*55JePMI12(wyc@b#|{b5sx6^Y zQZVw8g*CunR9SYFdr9J5TX8LuhAN{WzAqknjM2E*6F+dJYImzxG_et7v#D)%9~7mow2S+*j@nkoCqO>Gnn0I8s|LX^`sPoSzT{8@mtTe2eKF;#tT zarbQ=#0LgSSGWMWaqBVPSMe{Tw1{DDW@}uSu3_GVVBO%3EXi?eEgK{z(q3l79$if^ z!A5UDHpkgy-xB_;sUpVtOY=TY=vz${Y&x}t?rN&|w!Cj>svv(`sJ5vBN0GMBH<~Kg z0%!|e&{XkVd3&2GexJpASX0Fxc$=?>eH(z~NL;c(Hm?#FY-o{J$%a+(8ndBUUMFv8 zlGlkF9+X%9hKRge8&>d&PWP;L^A4D8$->_aMv}SDlkk{Ej`UPT{)9-*nYrU9!urxf z#7u?rg#*~ zX!Q9w=!FaTn!=Yg3S$&a$;RzDd?Ld2#nLhCSG=r`g!jh^$JtS1CF%`{GOOK^eQ2j^ zEkXqL?`lscFEy2{w-6J=*SWl{>7bCM_PsWn=4D|hg6Vlu4z6|P9=15bQ^ZTF5n__5 zJz>4=y5|m;8*`2EMs|0~S+6+btT!^UUdC_eVozi}oAYe#h}!oA0aVwb&YBVire<$2 zR{JX7UcyM|HG2tTKB$2!&FoQ@0d(D+(VADIH9gi$7-3|Y*AW^bV=#w~)ZN$tOfWC{ z0Q}4g`x(s`3DYf6CfqrsnXCWEv;6}%j`iVT$-;p2!t6F*NfusC5C}sg7j~-K_yi}Z zaaI0vvWiqN{bPk6uzMm%7)K^>Fj_Rgid1~E@b4}gHEw}(R<6__$ykR`0A7?tg?HhX zV^Fp_%6FG<#+$|&e@L-o*0; z<#d%Y8ulpHrGtUUS&l%L2v>~J?oh4~Jvk8Rbo<7Aw0z>}Va?b4k#FtkKJ9x*2@<%hkYK|BYCe=a}< zcUC7I3Bc^|joZH}R}KxlN-M^DLpkFI<3E|~T9upFC((Z*y0&G1C^ry2O&S+S<`i#` z7MHi-KAh}Iwyn~ZAG{&-`SOFOhs&BbhKr=uyoxLF;3hdS81MP!{dA&pmzD4&IsE%( zXi#VN4fKEpL#7`}^`Xnj6n0bpgzWX?#u~awg}}axj4(X+2;>X2~!s>GFp38fUapPX0y**(I#%ZkT|v z2gVyX&>Q6I0;Bz%>{{)aZa6#6+X0R@pBl*CtgvDonj*puOMvxz}%PqHBSetFm(#UUb^!tN2DZ+b}<4h9Xrh^D%RMO1gq_`8MbjxIK)^{#-IG#t5r(-neEyS?+onYsRSWX`eP~bU}n1*g0 zfR*Wy!_M#sv&P4WM@J2&Nm19A%%bwmi96sF_@*s`qi?m67+%a?$d|pp+6rsA67ewn zEUU0Wgw4YVxG&ieW3k*+UdGxP^C1Vc8J0?AS)rds#@@I@5^CM3iuB2XaE6!?#lYWfeTnd1zL~xnf#}3Qq|0p`L?aAlf06hv>5yLy zy!8*_;27p-^mPwm>0zDP7ZRI4E3`}`8^s+W1fhg@)iP-v_|QJ90cWjJ$VdrktZ#de zVdW@RxAmZaj6Hy~1eb#=NX?5<)nsHeRt!Y;!A4a<=gSP4%lZ{`NWE;`sXZb)?MXY$ zJSr_#a4!j#Ol}t9u!Pwefz0jr8|?5r;ss(n$ank|W=C3nJ&wHLK%-RhhB$;{As%8) zBIbzUKSo|mF9&Lad!KCVv;Cyxx*Y%sny$Mrtl-QbM$+;sC4OA*HeT5gDDOeS<`g6y z?)zGMNF*1s)tbHJ37YgHVMG$jbs9k+o4u&eCxP-djwB+mAphc!2JoQ(=U6HQ6=Y8k z2R}UTYs5lJS+jJm_x15E&HpGreJUa?H;&rzN9pob>y)?<&Q|N0Pwn?upFR7x z-fq9UF#q@OC?d0Mj{oAXqW`DvrYLwFDx>|qTDw!k`q2>KOG0?DQ7=m`sr`8NfHVb|u>^^eFk9l!c+>ZO9qTw?hRdMIqaWOk?883%bByo{5 zmdHncpJRpZ$c|C0;Eh6#8J%&P=sJRjUtK_E6qD3Z9u@b>dHn_DyLoeH1P(zZ>Y9} z@@zx%Ha_>e?%N>IpEhx1-e8`^Q4@nLAsvRrIq*oQT$_N%#Vj|ooT z@TYtwhoOaqPgyzA`cE47ycl6w_5Q||Pg<+RXAkd>>Qy;zeWf#yY=`d-GC17{*W$#= z9!k!UlA=~8Ub5%^(Yyn{T5Bq6ykZ`U1_|@vpt0pA>PYT^Y^!WWwiFpV+HN!v-kz}= z)s+9;R)x?2ShaV6g$V*CHGI+g%sezvB##3Xyb zAHB7>5?|z7izoP3-da3Sv2(Eql>~_^(VYbYYx}r79iCgWD&ifDl%6V(3Y2K6kKg-AOgVA5eRuHc?^+IZv zWn+my`iq@JSkIsc8FPE2?NjyrUCz+ya(RLq3T=51l#5M>dr7*&XSck$Rhtk5`hZ^+r)8!|XFR*&=* zghoZ)$a?+U~awMvQy^zX3?Qp71R|Zwwz`*%>gk%&XGsjXJHleV`qaM zRRZP;MZ)b?7P2V2M>%?JbP`f?n{H02XxaRL3zxvq#PZz;^Llhrg>H5n z_^dv%5u0jHedJK(VohJwevz|nuPY}vYoqqU&VV_|&|cVrLw_JL$v7;ZiQmhBxY->D z?CPtDPO6iXth8^+(QOR=FWOj38-4w4#P`dv8yWn6(a7H&w~^Q$v_~FN6Y*GOSk6Ox z;Zc6=g->;nEA}CPn}aYSx5xAMw{tdLq=(27w}S=69KlZ0cI*8xhGV&@uD+@r-8|dO zP7I0+5k@A?MuU=FRbMZC{%8Uoa8#VNhBn>L4!GKkURvy_F^3_0YA+n({FhKri^2M#V{LdSJ!o~(o?GQo-Q-x20^Emtez1>Vnt z5?5G%I8$apuG;%viII*EsBh&;72>ibWaiUYNDWitSd+)P+zy*f!ljuVWIREnZCEJ=_&=114XWwGolF44slWd&y|f5hRMINqx~O8-yg3pfw?bf< z_O0s0_HGRmQOca|6@FjBTE>n^P~1rHCz{ULQO0#T!X_86#H594v61bRI(ghKkA<>t zvBbYJyc|9mUcvurKE4->{^36;z-x_q$v!G82}=sQ13}DQbNtaiJWG-_I!js=lWeHp z7hl$2$X`LPZ8q*rv2je~zE#_;Tj_}6=&Flw^cVk1sTfBW!ND*9+5F*n|JMXY_Yo2;LG$fUST_fHsAt6FY>!r- z%V9jN`R5ds%>nF`cxE`Y<`1Y#1)j76p}WLa<0g5O$YTV0h1Rl6>PJt}axvPRkotIo z84}09>8NLUJV%^ir-Q4l)@2AO@s9vUi@m6E)n3!+W05p=M5k87casopsEF_6Z5QM~ z<@AdZn+E2NPQ8c%(O*iNrk3R#*h578Kcs{og|b}AjSZb8uTsuP@H#J4Ag{9UQu4bK zi=|XzAx~%G&cSz;CFT*#O3Y@fqs+Im!56)+BHA#X(2T^?&gvO3wup?ESvS1>gM7J$ z1t&!nV^l75%NEjiB^h*gTWb~!SVmmQ>XJlu^uCg3W$s=_UyHPLu=S~v>S=#VK&hGx zTQZ_X`FVSkTh1ijtcJZEDJ=~kF>79NKehb!O>%c7b_vh;CrjZmbazgM?oP=-x)n&! z-KUJ%QFMpfc|YBq&PGCt?tUmk-A{KyJleYHleBdk`>Bo@{jaI+aS966iSVO{?OhpI zA-^?tAatkD+KuuUKy_iV3qjp0RL7|h_0dPq;^=d@j2s$3benLxi@!(oKr-qlx~(J# z(QQtbvuUn#VCEFfts#q0k4=9dJsk-nhT{G1({p*8=+ z4&5GFLdupGCvU!&LUItZ`vD;iMcgsO5-C6B9|j#7@1aKUMagq)CY}^$}|pIXDh$ zY;by3;6+D@_Op!*xd37RFKq--c9efHCbe`BSjQWa6TI|Qd4pNkzy5#6sxV;fiPpvs^>iS^-rnq9*5U+Jg_- zK<)_L&G+4*n|Uq>P2jmOG@55a=mehO(2V-3&hTv;JmgN)T312CiJ4t`g?vMeLbNrj zy6of&%|Myi-fp?{=B`k#wjrJyom!@L72w(lj}?P<6Gf3gnP-Dc1&`5ZYkld@dt>)#fg-CnfxmiDmL-)I+o;r_mHZLvu&qRqGK z>&pOyI$uNUa#+$w0V%k&OqPH_Fnm3nt*!f_J{np&4rh}rv*DuBhPu)N``h!wBZ9H1 zC3u4xF~!^URj*l<`@}2scf#tqdDDp9Rbnfs(qv6yL_rO^oXZ@EQ;k?P5-$EhU21Pl z$+U9M)_E{CkRmP4maY|X83N_QcIM9ip_ALKot$2hZMd!0ouE9j%;8viDuwKjuOEJH_8c|NU!%9qi!6qV2Hr*cE6eELyT zzVQeu=j&K1e?3Fx8;_;(sr^*mE~1%D<-k!>GehP2i~1jh!X?8|RDP>a`O8A(2al%m zyDkdG8cIsNSqcvwLWt1q}xJ+%iAO{Ih@Z* z0R7soPDbqBS*{!TH0JIUbI#ptW^wGyCi*c(B}% z(^T=Ct+T5N?1~4oHwSiE;t!?0Fm)21c{2GxojRM8E$<4mxlm zoG8zQ|AXENG3u)hMAe}qz&4eqK|^CWSiF?V`JteTc}$`BVh(q+W8^od zj<;S`r|rIt}3RhO*H70O9-r!+kr zY0pkI1`h$p>w5ag103xzKl1!&fq;XB)=c zKZC~3$upc*=n6POFrDF+R0iBhTK3;RZh4ghw=i=u7D?=X%xC7zR1A>oa_aP^y^iHF z8~s~#rfTe3r@w0Z((St09-W*)Zid6A=ASM5ivh3ppmv`3l< z@zV*0ZXUKL9JlKsSjxdO^Uvv-_RthE&9&HKxH6^cYwO5&tW7E?HYv6>lugR=+<3G- zDkn0vgnJafU`OM=y6_{3%jG-4NhQ557<<%~MzeykKnbd=k=ZgC8Zfg2bl6qnbSBVY zt33ge`xX7%@ck=hnat=}GLZ9?3uamB^3@CuZ23^IAU@f9d=+=3kWhICI9QRwf%$bB z1-^QuO5Xqsv;4riS$;tKM zxk2Ybu%Ekp38izKmnI5048=XVS~8LCcd|Qsr2pXncbMba2#@p~rG2$?_o}^peq&&? znkLp4n)JOV5Y079uZUsj8tX_bMMu%r4b4zsURr4hT^%q-SdZc4mfled`=#x05V!R- zpnwypI44{~feeLZil0RB^%Ft2HEjTlP);&8kxeI!NGCUceXh0K$J|#jwoU#cZ1ViJ z$B~_f#mOM0oiBYU_vJZ;SyP6CKwB_c%h?CA zr0?{`zw}>CZoMlGmlJjK!yp3Q5^U3>1F^|jA|T~z>x#tSWvk**KDSywyk7D5znPuu zzee(lY{z2t<#}37Z>;*YdURrBn{+W)dFy-Vk+OBU-b3aMa}YH3=Vk5DTYCv}rQ~w` zKqoGxNv-8(#IJh2>P>CMI=BQ-FKZa0t?STBJK|4BMAaTG@{kI=p{@8Hf%SI+g6M`k zvXyR(%Yifi^k^#hu;`l z*KwY7B0fqA)nk8^za%($Gqi*8NFi6`ND zpGQGhbVWM$5{cc5hWuRcar-+2oc68&xjinwJ#-{x=(l>H(UgXfo#fa@2Ig4;;WQ=QICbvi0aP33|1yPQ~Z4iszYFkx2oFBuq zs>LQfg(S@Nv(T$jmvpTL`+ECf()?y`JsZ`R)mIJ+U2Ltwh?JUouWS?_-z`a-xDq3^ zbtB=`V(A(OI2%%P+_3?w!+t+4t?m* z2ZVQSCT?3gt|$}NnvToK#Qi)S*DG|O>RYtB)ut^VzR@4coM6n-xi=1oziB}H(*xo! zmoc{K3`na7q@3GdV$o8&PHjbzii6I27TunX%Ml1Ro%JlbDjoYV)Y59xSDV4( zp|jYuGwVw?Dk>WTUQ4$m3e$Af;8uv<1{>d;D<0b$#_3hoqM;~}Rhzkhhsozi4AHe`KF*G-6F^6Bts(p3ALDFE9IRE2 zNi2a2LVvmSc&QZDR~CglRskcKBEGBJl1bnGPIvg+V05DRXsOM8#GS|*<)g(;vbsZx z&123~IJr=#jq1(6>Mwwx2e`mrf06IyfXE4J)G(D$R>-GwF4l)*oAo4S5Bnr1l4=E~ zxclqC-J#!s;2Pz_K^>YA%`-P8X6scSH?}EII}ZFMu@&xr*1s;Ovt@I}zmtudTv$F+ zSBCZ5`fjjoeMcBb)W?APIJhm7>3!#ccC0gw&?xrvFU!db--u4jg|4Bi5XrAF?8NZ9 zf#;W}{sJ44hwZ;uPTm#MTDLxI6H^&7K*;|%|UB(66tZMV-g9qluT z%j9a<1HZ{_#EW==I%(fcBznqm%GcTDH8EEWwtmS1D2K!zIjVF@ z-#Sitk6nH;wR>dXm4l0QQwq-I|2CX%yh{7~5tV+Rh=)!a;1PTRuFD%A#azUh!9mvT zQZFt2JLLxUGc7aKAs>l=gJXr9%BuA$mw%)ls}(`S5v}ox9A&2?^l)kmfk#=$qBU|@ zi^^uADiFIjI*p74uUk@)z{AQ=P$>=JE4o_w?x=X>i4>cH#Y;r1ndprx|0w<7li)^J zCc;&bN{y@$pB#J;;W;#JC+(sUGpV($t zj`bNfi`2*M)_a)`!`hSisI#_ZK9*Q7_J2f|OP8O?e2a^b^;5oOd{QU?)u7zLe^-%}!Vn`b`E0xhJ=^-f=qPU(W>gjkLD#v~f*I1zZ>xx^g4i!O7;zEYH|@*Ho|+~_GxNswg*FRH9>G1Gt8UJ*)^dnA?MrGFc!+1K zDy|~8THjvhCzBc=8N(xO1j)j&+hM*1uf;gH*CRkjTQ{rnm{8S4O?}C5Lpkm@ ziG^mtGvpEjn)M)ANPd4qH)vZAHQBf`3u2N3Z)v!Rho;;_KHOL*(}KhXF~ZcUg5eX) zg1O`*$9PrQjs%{5JFKmks>og>4K@$R$-mgk1P{C|3~OvBKm4Guk#Uo=T6XpO?bCli zN*7MCep1$t7mhIChqx^9P40(#IMw_vl4<_!1Dd~=I?aOZlEXe)B+dUR`Do8dY&VH2 z4pUi=sDje`pOcdu%Tz^MrTJT}e7pJAk{#_&>`AGE*4AHlio$XX?tisSD!mp^Y25$g z0%`d1a9^Hoe4gF-fw&*58b3@mz90900~C$ldO{layNF<1X#D%8YJ8ijEQR|k@$?}3 z<77|aei*H@5uP4^0S=1d#{ln%Q_5O%K1|H@9e{#r^_vidmWRb$s6c)^Lx~r0w14|3 zklJrI3vQA8j8?w&6y+lY)A&xG=-9T+bZx*(ri1ge+_9DWfo%2d^2dDcs=QEXvMrFk z6X&EwpT_i$B7MUe2epd#Ku1%%Ut}z}1b~O?+U0E>y6JDvQM{{vj&c;dL1uzdAkh>@E3E4zAvS6&aYFt%Mr@WBx@-ij{OW0DD%xi+N(aFCQ9m5jO~H>fWI%dj zI^Aoh+w-vh?xnQ3qozEmn{joK>>FIHtDA58D{!$7d35$>5A3OrX2oW`8jM|)RcmhK zPPy2kEN;{_w;cGw?2a57++%+*4(9vBUS$Y9 z)Cp@*XZP$o3)q75%#yiL)e2jxh9Py1D%)_a0^S$`)U}E=L_X^oXS= zf6L}2-pIkM`wJomvpMeQn*RfadW`A1m3Oq@Ciyzi68D~z~k+iRsMuz6C!8W;+&C6SDrffqH1B}YLBSK zl4@R)w%^iU%ZustK&@Tez02CgeXMdYa-$tn0o4&yaXo{0 ztM3tx_bNq0Lj+55h+|lStBDVCvCgmoIO(-hdLnAn+4sc&{@2OMQUGZUO};z#23@=}+TJvDh3dQjY z$d~T_0J9@><12qtIlmvm|6R~9vf{duQ1;5}ut{Bq4eL7WR@Zsse_{X_?r@n=d0kQX z^mHC8n;O;&5l7uCXH#T$k-0}*8HyjfXJt`hqpSiII$E0q%DAG2S4sJ9le^Bi{maTO zhU?DdKIia=WZ{FIED+t+MB%be^Dv7NOgcPqt9C8UtC(F9T1_1Z6SKP~4Y2}<3Uc{w z*?kkRGBN&ptbpx??0${!vJt(~t4@WAd$l?#D#t{TXVhIAQk;&mzo~v)XCcIn zah9s^G>T1h5F~Tl)M3S7croD)&ToY*Rhr}s2$1r2kD{8~i1+~ee6sM~m!v&D?XgaL|xyIuckcetsFIuaQFf{+k{bkh~)M}I_Gf7d5xXZEjeANO^L7^e~n%I158DD zU@zenh+V<1PMdvMtjP#MT0JN-+>CVX_XXx_{6O`fVDuuow<)rBBKMcI1|MgL7RYcm-n@ zDa$~G+@0I3v}WoQ=3|&Ai8M84K=TU+Hh-vUzB`Gnp~Ibc-Y~E6C?gU3H5^y*s!K#u zOhWyB7QYpzX1T<{VevCiKd>X2na}LH8?lFrr2*DYppL|^&7oF+_R58b%ZX(~=BQmZ zVK+zM-oE-W&$^Og^iB*J)@PKkkAKz>3)9MA2)TrxA5#}a_#*STfQ5aF$UHBYs1+4n zE?}{UgOw_c8qHDdaiB%k7Aae>obz#zg{wC*clg)+GbOVH+kY0Cb)DPh6%ohtArR{}9cq4+8+%8K26F_1y z=l&WaTEcRu9MXQ=<2nr_-W08+^yi%Y$&7a=0>MH!87It{9T*5z0q(p)zvo0=(-E zQbpa)Oisp;I}Ah(P+sX=Uf|*00yjP8LWAi=ynWnV;I_U^x1bw!M@6(?o^~p^WOZfL%7xUfa^E^ye$pF)Ht$I~@ zzBeb3T_cEys859m0agMEIBy-9|^^rm*ZcsM=ls$1&w+yb_!T)Wt z47`K?Eb=7_eoZo4EpICW4j^$%LU>XzZ_2>@Z2bV3!Y?BS_co4)mna$B(EL4#LTl2M zs?}%6l5z4gvXIEtLIu~r;PBNb-<>QBQl$tBuO12aMG*U@EV5FU8|Wpws~+vb4C2 zHCI)cE}k9(?r0Gt&ro$714y+e&xZ6%AVEQ8;>2$AOPI%w+W zjL?yF1yS1>9+>=4@W(kb&X#lC;iBq*_DqjGSS@d+#_Kz^YP?Ehyzcys8ZTC~f?q)q zjF-{@l7&BPwTEmML*|lssqRF`4B0lWIc!k(!4OxqMf(9)zZ}<}M!9+&rF<*J8qIBr z$8s_cZIzl;WH#&8Y;Sxh!&fmfJX0y*mx~f^=Y5S8ioEunr?>NAa5j}*7`Wo@KCnCT zw|wn;?U)O`*S2ypYuJ@VSUPgeH{vgVJ<+D?qeY3Ih)T^+a51;isJ-UPdTd&rGCtXY= zd&NdAE`8yV$-=pRPy=x42MoYNJj|kai-ILqlKA3DnK5XmHPR36;JEsD4TI)iK6(gl z888H@Sw~t|RFh;qs#Q%=V{ylkV=>E?O6p`R=7_`b?eHR2Z5a!C!-^;3S6Zq_UpzNQ zE?!`g?hcQzp2N4;zHRQyHT16|z6RuL*C1*k5IVV#KsF4QVOVPmUCjnnU42(g-8mDn z5cpOuyUP0V3ZdvBq;j|g#e|iFeG7Ir59*QKGaa$QH#_e|M@-?n* zR>MFwOMa@%kZD@T><46iUyg0_mubqu^8MT<#t+&A&37<(_$n?sJA6RxB=bWuM)n8t zOZ8ROVy|3Yj~EQC&6gW}}*<&PYy}a0fZVD@eP1 zs*SWBdQe+>cuFk4oD>c`)tWC^XQvXMR~Wt0?MHkJnvL9#_sEJ>NYdj5;#MIcimtYCv#&2uQ z|HIw8$460}kN>;bT?iy$Kna3~tRz@8qG(Wq3%CorFe@9SRV#c1rA1S#wNkPRSV0pv z(QJloX^X9GwY9Yu`>9oXLqNrB0!g?C1eA+Z2zcp)@d8pw0A;`L=gj5;V*B}gzrVkJ zyx7dlnKS3|oO7P@oafq~a+#qcfLRCbyJSZ?#C1(B6_WN2_eNt|7K@M89-=~3elpkO z+Mf&!>LaOgNW}S2wncRB9&Yf+@Zpm(5(>lgT#m_S3%APtQThHDb;j`AJr6ocF+k-Q zS8A|=4(l&DvuTHyHWD;_L~Yh3myI92kf66~u)=t{VLn^WW^)|G533FR)Y#r&ZC0S7 zLLXP!2x*sHV3>UAmi*4c-3a7F3QChD3T@J=9rGj zDCdMwc)7;>5!eh^4?RPMPL}E!UJ1J)dGk}!f%DZ^m%adkfs+jKzPj4zZt_E9f6cv7#EDx_IsHM6V3q@%T7SL2 zHXuysO}|$g5FliyK89!Fc>4W36Vy|m&hvDh=kPoS8#0pSk#tCR)gza5##YoiYMj=W z&|+LEetOO4In0|6ALu&XZv3f0NT_zH_S9B-hVTXLe0z!A&fBD&k^gU+hPKoSYnU|4 zAfN3Or-3;hO-AXfOE3b!~Sme+n0v&b6fLS5rK3pHu{UQh2k;h=9aCkl$JFCfa zkb9muN4fvROv;a+_$)TC$+NvH`Q2xs5O!JT#7}%0V>{JSSMbrK$s|;IoJsHi_(uZ32xEiNego$wV;|$-cO9Y@GA~>ESg!op{ zLGKmJaL{{&Vi9|cyQx5-_j&N*Ai|8WyYiSIO|5EzG#2I~L89LIquL>>E+$Euj1*~& zRA1vv*HOE#asDT@!9wViLdToqJ8g0 zJ9S~-ib&4j{>e#R>nym9nP=#jR&g6tQ;8Op)r|c7(wsd#UfDAHhoS zzJ=JEN=4RD!hHu60Jabwb~#h@{5XIh7%`t?$rtKP%Y@A`Q z(TrjpOZk}LxtSc*%5fTUhxn>xv`g)6Ka~Nn?oi>o%xstt>`A}0QKTS3-}%vrLbQ*t zyX=xK52BwZzd=8RnPPYLmwYH5?h=pMpTdI#r79d^mz*UTD2Fjnyk^?nC2=*6!NXoA z4Cjgozgh^yVi81){}6PN$Xh}J^?gt3OXj`!O-c03lf*>cOfF{0ZqYK#o6UX1sQZf( za}qXuzp_egv;0$!|3-s2osqE%f_`-84e*vs)IdeSrBs!W>- z1&-#0_9B5tH~!8>faj5*MD&DPzEse*p7@2Jp~P*2%M!QYfZ@Shv6ujtIs}7594tilVUmz2zO zM!zIp)3g->d|MX|EUHg_*O?qRsv>grV7Kctz37eDzeHdRn+ToODd(;_$@dl6*}7|; zUi4b*6XSY!W!rC<#?eb*$49;Kw!G+BnCyEPW4Pglr_Va37HmpS2knC!Zg~DFH@&&p z!A)j|!Z(vF1$JUW0yGIZ`slrflx-(FDTz1a#a{{%!gRXUf?BErH zgRVC+c^0$^-Yq#d2AP!NsQp=_=#$uUz>o%3ex&+(QT!u!bZGpeyl9^9vl!hRHbOc@ zH-`<78@f5nAvbh$*hdXCt!Qs%WcB1%bJciGOO7wnUJRbop|rTZk|p5$sBMVMM25K+ zG%`CQt4le=aD87;2>KLsQ?SyhWM(Igkde9h(J2vC+7@DL+BD1b=cH5?q_$$aziM&T zkl4ALT4^O4B*+7$r!N;YiZ_3#IFH(`&OnpMN}euD7FB_at_xMDP@O8EV4GNlfO}YD z>HNOGk+Johz|aLbzx{BOhi*N_%41gop@rULV}IC-OW2{6$NnnUcPfv`nu`9zCNE;H_%siCFL|>aC(bvXK5hFz&uZq1RZYN9hq9byJ zmTk<^Td?zOfGSckw2HUM3oj@4tuo_;9~hk`%)R@u@6PDZUaHFA3snXmV;YODRVNko z6F%fC$mjMj!3?6foAI^dDIb06 zf-ExZ!ImF8u_}%b4&@oF)cD)cL(rKL6M1uIMCB8|lzz(;iQRzSH%}5#c~Wx53uFqi z-6r$z`WuAUjjyROyU0%ZI}1=)h7;`6vlR=41mZ{APx{20OLEiiQbg-=eo||%M(@Jy66>11HZ!S)qeVD zd+#r5sofylp6wgL#apL6x?F+O8cjJDk5A(oqNF5!o`%R-qN>0%p$!h#nx+UWw3Hv zX#7ePg6cqqBG&8H`JlWyDeru{ycg{9{w(Fa07dcz@}7|LhTG*WwaZILd2_zF zyvt87Pw+BLd!t?sIrOuc8~VAPi#a$`4&v^$qv+hfe$jbo5vu9bzP-Q)O4>>pPuOLQ z{0n8gF6RJXh3*u7BY+9zJWDy9_oJS4I5P4&0r_{N7`NS}*>*8U|4cDS;q{yvpSr9^ zDaW4>gxRB<%cUHTUCuDOoHbHT==0^A-=mygt8(mpLNJXe?pz%qc;-g`s>m?&Bg#`7DPO*zG&a9c#*R*DzB+=Z^M_;rH|G>i`}Esw{S|d= z!2ms(=Tv>!O()vx=!9r))33L=S24V0veBKs#?Ey9L}`Y{eCgUwm??0nF+Nq_v^!2S z73`uY8(58Um&|YCvTBHf3Y;u>Dg8@yN1!5|pu0OVgW{K_>w;XSGcq`2FvtEvvHLRg zhs>+uJ?1U&u96Qp@!F39R^FE>oeoU0>zd4kz#!&Ngo{E=7m{PDoukCgF;3+eV#e$o z+KN3|XkYDVx;plH;M?L?X*&mcsbVHA}k>qm&(&zG|1+;Uv+r7ckShv8YzKVyJ{lpjS~vLO}#fv z7{&$HUvmP+hkD$XMpr!UI~ffe?d6P)KiVr>_Wzu8{_|7?oG#Xlb--V2)masK>R2y! z;jn>_wct~KxwAe$hJDI?b>)3mQ?`RHl9&}UE3$`hhpIe)j>%l zN#yH{F}))G=sKW->_+6@{dM2o*t`58)V*(Cl%Z(o(BltuIOMdc@@Grf!XF8gbSug0BH73DP!|;j_o~K9dg>~7v?&-g+9&=bs zEquG87urf22kb|}Vj3IG>U`M?&6DF6r@1NtoG9 zcmhOV4YDh&$fSRlbio&C&wF2^o0A9rp^gEJ%{^2(ZQ#RMGKrPzn{lfgU`q4XyO}vk zlH9>v<{esV)6Q#}+@IX+=F^~Ba+)R!`w66j_s@h#OOOP+$RdUCPA=8~)Z^4)N$s!1 zM;fbC%8W?^8Ksoj2eoEmvurTGyDgnQR5^FxL(I|mTrUGYH=93#5p9yWOQ#u}OkiU< z4S_cL2L6nd7fQ;^^IKNY*m$>I@~Qjy^x_XBW-y_4m9~dgRRX@mfJ&>nj2l0`ez0mH z{NN~_$`Ti~dAY(F5}wEybNrJ62XKAmqFfPB#73~QOa`fll8T(^H=2iKg`kJooEE3u z&G2`|JtI}bE|zmlFKtEdvV=>GmcIl_;8bC-k!pi1c4#XIVsTSpi8+J9ST0{tJe?r= z(3syjAG|E!JG}T}Bb-YF^J?tnnpUIMDSLnrO)~wx=60CK>LV7srq9Lil<&~|iT;xN zoY4}BvEs(MfNF$YK;~PNoESY}T1p{?o@*ME18l0NG1bhUiGQK>oS$K8JK81)3QCL) zfgkL1)11?(GJH)D=u6^(sx^th7&~c~TwqSmVThg05Qqo5_0g;g1|SE3oRBkN)P=7p zy;88>Lxh1GOba><@#ZXnsmV$P+Bf@!&29eT=hEqrNy9S?o=deCZ-!t&XT%02k0V##$bu_xLhRdGl6r0O$P3*K} zQ2b(A3(bB9VIS-P;!ptpM2IG@ITL0$U>Xo#GRhIF%ygr8kM(dme>2QJ0`GAy5O zZ!c-@?zI_PJnR@RSPb~Y#|l)Hs?h6D@oCk{F+3;XM`H~Oh9mN_wsaHy2pp|*MlaHn zZk(;Pz`h6Z0IcHmW#js4Rag+wQAAI^t+5Z=YIut8AHLEn-7iYv?^18yT{?76q+nOL z#VYk}FV!Z!S-gH)(mex1)M!77ke$H({Npgf_m<;f-*&A!L_v7dm>z~5Qp66`+A5Oh zn9Nz5WtX$)H$ zT#wQyHhOJo<0+k@k+emtlCEYtm6GOnb?UG3-F{6u^!b12R4htoRG*3(v|HBGCna5{ zPgZ`Jc3%oZ;+V5c+gZobm4^41-n^%J(@%P%Rb9=fb@u3kwu@v885FzyQ8aJP1Wk6M zF8UMlVxf>1aLLA@Ghry{PPOMAeEZ_{Y?mlGeolXvZ;D}_oc_(#nQz{s4L!b5u?%k| zhT%Q^!-?|2J>?_wIO8<~Noca_x?s%}l!(JQ-B@1ON-72dD{(FuHrljs2MEI4@DmO~ zQ*-pW&ARXK!hCa3J$(G+gCj5t$f zKHg|HewRXI*SM9`+NFhCNv}BUtlRIYNKLOH|2$WlpI0kK%Lmv?XDjJBO|7j`nen3S z9P13coOV&Iw!*YMb_WRizLGEacj%$idmg;bsDGLx-U!lYjtvghKoa&2)5rE*r9vG|{#hl`lvsN9y#Bj%`>7XpM7E#;?vCo~OJ@Nw*LfVTdeYI4>#sU5Q z;AIpVfYDe`FGX)R%K|QOwl!{ntIX9HsOgp7A_|A8gKnae)O~vYYh{FxT6A5|SgPPF zsx_Y?aI!lASvLl2!*uO{@&Z5FC0MEk>WiivmIeA^kJaxT~DB5-SlA1y`11mv&2asV?9#F zofu?AkpC_B_X-=E^rB|vL|th9>TRV)ikQ@zUSAV(1{$4XblCBLYw>0J$}R0TzWv*u zul1Hnl-c$+)O~sj$(Ay&)m>YYlg0#4YWQaTVBt`?hyj|4Vf=jTj>VViD|2=}J#Rwa zolnbGt`@PHJnchbE7$4w)IZVV!@i4)^p)Qzv;W!EWu5t}qkL77qWbJm%{(WILBcgD z)hP6^GMkI|KAHz#)wp#GXQ2|#Ld@hI9->cQfA-0_s<49LW>p!_+QuwG1oc7iC#i|a zTT2tunz7n3lQ%ofE1*2sy@Sb+6LFw_bkNw!8hM7Ca5MsF6br4vduT9k^#|H7Q+iQL zxQJjICt;!ocM$(scWn_>3BNh@N=3wY8hbme39{0jq7>9mf-IY5c7bZ=6J}O2Sm;N2 zgP2EBK6yD0JJec)ET-zywJe4AbB1%SVigd-b?z>vX{Z+Tf}!v+?S_VUT_62%_NaxI znwwtf-V6uEAOIA9WKItvh6B|iw!`TGsbsloicJPyiJ$F?msR6w)87;@qZbu#i%%S_ zEw9&IJFE9RNa%7RWT)#URgBMa#E#00KncTq4u^PTgIJ99ZtOjpSY=K!53QXfL8VZ+ zP}EoVqRp=9tF5rX#JPFG3Si)RQ;#2Z>aN{-(Fw(3`3&?w@G_wOhUs5wlWV^bU?r#F zYCIhUGr-uGl3h_UCVETIm_j5s(|LdIK>M!7$M)|UhAX6m@dz}fn6 z*H*`kSxn%d3x^LKeaRqGS7pfR%HX=E{wNuOj>+!$%Y_wGnMK67abqAj;gAHB5tzB= zc0k3!)Nv}I3xy{}K0@etWuA2^=k6)Db>Ge~rpo?HM>%hiQ0_^6SE+FrMydJC4^$2F z9O1dwyQAk4We|1Gv&B<|j7FXAYA9<8I@8coB*c0(x<*Sh^tp)55wld+Jrs`Rg>CS} zBw{2AFtm4`Bia`ItHeSOW`dmG1c7c!u?ESK)ugby&!ajN|66g=@j#<{vIQ5j8SQ=kV< zO3Z!X%Ax=-Sc0MIL=@uD+;8^2T9$LPFvyz>u4vAza+(URQ0EeeIz|5n()2_4WAV?@ z;pBNGL9$BeGJoa#G~{xHEwaHDZ9gKN-UQRl@s3XyPx09~mrMxy4lT*8CH`Oo8eHu) zlTS*M1vjaZyGq2OU9bpv1(f1-n!o!Dp3S7rytJ(w18wR0h%Zp(%Ag?vgPw+lzIQ@K zup14Df~=qju?9rpV+QBpyU?PUH(19Yw97@RS2u=)U+&z>^@2A;+N+1c1^)mkVbFjF z8hyJ3jtXIyN;swy+1iil+McFAX1T6-HuEFw&c@U;iz|wO(e=550Ti2TDVr-%qty+f&9~0 zO7(%2!b{c*QZaY&dTX@4iYNZTgs9$je_t`{#C?A=;R}FsK?)*FSl03VpQ7-R4SjU2 zgiO+X9p#=zm&3X$c+!e z`pLu}1j(>}`HpKpt_vpJkP15^t`zIz2C|qlAFT=0g`&v-*JjH0=+ zNC|TZ;UM^9y4iGv;E!P8P3!~UkLj{G%BeR)Wq2VgP`D9D0*`PqcL=Crrz*sJ|B?){ za5>t3NmX4fmMYZRh0dTjS^vD;Xg+-m6u<`3)iElPeo0<`XXn1CW50vjD_+U|uRd-= zM_9r zb)uK@#{ioiv3lN>%VrYdz#kWkZ(8!a1oG=tRm-*-`(G-)m#{o zQnG5$Cqi#boSUHO{%6#eMeMcSsgKJm@lxQWHE$wa8Q(fkFxTXpKjhsO@o(0A~?^u#aXMG*xcUfi@v zk@t^!{zM54?Dx_wH8_&(q3GT{+M3SU|lNlu^uzLp}pgdFa$f^VCOaM_1+~(PLI} zXa&~!w+pPtO+O?oy#9)(d`O(5EHV{AH67UjY-W{I=M9T*){VHF^io>e{rs`{@~85d zy)>pn3KuqWUG#oEnRk{{f>Hdvghgo0H0*0}T*u5JN`T$)^+=k=Ibs9MN8odiDkYc` zgHnuP_oXYx9X)c4QnSyHLd{5zbicZfcXoTKR0aWLWD6WB+fGXU^X&ZnNqV({1ifgB znQf;HmTWv8OR2_4G3E#Mz4YDutz=LBRaoV(*noTh+R1a)WC7;XLlSRPS)ZLHeabWc zqCV|s{w9yb76BukRRV=hx~un&wkd6l8TpPA<}rI})XmBqhw}O$bXKZGJ;<6%x4soH=4Ps z4YsgBzB`xl7iG%t$J*~Iy}7e=+F|1g*q}f}jF=Avo#Fn>fL4JJ!|x^`yT|nGeD0i| zozI;kWk2_vp|1ooGZd7jW~gMrO^i%YFq=I^GijMQif~iSQS-{q6|MO2f^od&oquEy zk}H?dKwGEW@Z3!m)y<+@HXPn^>=LDFV_x#A((TRl1+sYY0~Q`e*=ZSoggtY64BxRl39u-(rpyMhhiD2X8c=r_XkYI%+qcHkkxdem;nMBd1u%*M?bwA`tW6o8W4ZCCy!)r?-YC@h?9KFoI_ z3w!mX((4t`TEdl>++Sdfp9y+(iZ7*%eVBi^@(be9=Q5DM44!C5FPI0>>)7uPu#FEy z3D8}4G`O|I87(x2d8HeOt2fb~;yo;d^UWVTD_y~(!=i-%&t?9K=O%{;nc^GMY&0IK zIAy%S;H+8X2y7}Vi(elrxACp*Fnq0qddkim8sDb&r+016&$dUaJ~-Y{3~%|J(764`UFr*1x! z!)~mO)Dkm1`ByPcLPdqn(dT|#DhCmK4wPbx9Qp`c{oY6lYrQsPw3WKdNFuZ^V!Rhg z-1~eu^(nv{TX&sNwSP`Qmg71jcnEHB=tbny087+OAw3hJ-<$m_1&EQt8KL4m!NlyJ z1dKh2**^~(Y2(Ye0b`?VdWoC=SnB&siyvT;QhgXpXn{u}^v_x?@G2P0fy1j{kb%}K zk1K;HWh{gO`&612Ft(Q(OPiz>VdKcOr2BBNWY(C8(HybdL384yXYq&P`{jGB6g17K z`lsZK7@_AQMNO0N1$t=W6m8AR`I0(h}<42 zYQBN8k+8uC-2jhjQf<&V5$33G$6`*bA3T^ZuW*j4yHK3nX>KY-f4X>&e3|t8vM`-S zyR}uSw!fZIGwbj~`4)AFP&$&B{VM%!f~EUvfGL$Ecws#4RznQ2l&&K%LeS_icb-ps zSc_WKPC65iFO7Sj*XJTSX;;ybk*kV|XwlB=v^9S#kv=7_cZ%B-Bmv)l2To znY{A=Qw_VK5fw|m?`Si~fcdq0CE#3Tzr}kIFO1UP12s(;{hY@H1F+vd2$zB#GGuHa zFx)5)4HkJJ3&p$$U(MoWK&#rOsx5s{)>B0K%NJf6tEKbDN97R7!q=*vCRdOHyx-VY zG<#pbwT}slZcs#f_%Nlyn+o=gg^_W-7)VaeQ$#XuxMmn6FO-$Mu!l?vqzwt;=Vdv zf1R=PZF?GR2AGp-f=r{i_4rX)+-l7JdBC;Hiinnc^|l8mF*iOcH5y9cKE>a?HVlLQ z`s8^ZP>rM1^;V$PT|!VSX5~(`bd*~JirYOgtftH2y+FhDCJhXh%>KTpa#`V8{Nj1G zU(h|5(+#{=ZD3*9v!Wt$Xo9=s=I>jZ()s@R;LXra2*7?0Yg6_ zHlQP+gRe`^kt0cXwm{^GLcfe!Ft7ka~z3RNf3I(*T=8*kvlIygN0GZ6absw~k_~ zP4rGo&5roimEWgxfc5LXPs$5xdoc*(`^NgDr)D?0@?gc;)}hI&!fUC6v&#IP_L)}U z^P*jfz*j2dkoQVoyn#+Y8l5F42R&48!S`R zcTw;vRO9F&S`RoJxy@d(Fl>E#WyPu!P^P@PL_`*?x(~UCLU|wSng^Y zJkT(8fCr)ht-IvX!VZoP9F6AVw(Jf(&IXfm>K)%djC7F{me(s}C?qp;W&xgj_HlHwBB(nZ6cal69~Ph8A=0 zH6)NlO^L~F8s0V*syUq>`!?W)-2r*KRDnij;xjXCa0?JKLOABMEr62{Br+FmTS}pf zdWyfj`22}L3(wsxhTwzE3!eu0dpYBAwMi_cc?iu^?i-e3S(e|U>OP{UPC|H$eh!=f zT@pza)`gAWCyq%+gRM|aQy`6}A|gYi(IXk&#e2W*EZ2OATdJ}&;xyu0gf9Y&6V639 znqx)jtjQW`{>08>KHvRW$4ovG4|qh)JCjj_+DdxTS<32nj`D64nY$mt9~qoh^$VdW z@UAo5ET(J}P$H^|K{Ua9%&wwsF?qz-O){?u9v9s#HQo=WKBM2`A-HneCTVM;$Hpb6 zq{llK_Q9I3eJix`^MK%3iC27U1sK)jq%8BB*MKk)1t(^=nt!KjQh$!%(xm6x6nvfG zHYb(L>L}5YpGfup^$VfYY~IGWpO%nOnW|3ik#(F_CEP6v7_CxspwE1go*{>_v8|Mwlbih&xZr#AQpzI`-PN>aYecrZPqnxlwvE{9_}L~dU2_>0yyh$8 z;FP{tdI*K1*i~SZMC^67VumV&V*Y~HHfrJhpmZazkz$b#H#%CpjtZ?mg)oGN!2XTh ze`*0wqStP{a%$Rd;q+R6I5q7%!eM<(K?Vxz_2N2-3VMN%?dZp;=qj8mq@A2J`lRy* z*)29^?~;vhD;uFWNaB|=z2vxqTh`}jl#pUW%^`SWvKNW@;VDYQ-Azuzv+1HP%-Nq5jJlN5AiT`4kDV)L-hA_Gs$MnK|NE;n zvIKfxTmZdSTK!IRzaBrHr9E6IXMn^Lg}RiQc(%~5u74{mQP-CX$8e1#UM?IZ*W`E8 zrP}i+Bd%k`=x4$uMLMeuucGOK2*Zdfi`IqPvF@9JLNel{M-5(Z)L3xmwB*=J!v;of zC&Rw>gQcGy9D7E%7f-^hsv)=e8UDK}aT)=r!`zO`vg#CUm=mxs1lX-25U3LCHC z{%$YzUY8tO1V=NS|ChUERy=qSQxQG8i0{Mb`Lqv72}oh9UbJ0X(YGcGy$Iy6VQPrI zt6XEfrkcLM{`O+K!KJ>BqQh}VRa&%V8SSNr+yQ|a_kbQPhR{J@bF=z*oc;0W&fd01 zM?{Rxe5?)kOMk!M`?omEl`YeKM-~hVyY`~LT%K=pW>ul@z*nX>?^@^c5(qBDAWYb{ z>Sa^rrGo!QVJKbS$pvu3$Y2k}?^K3|4q$pXzl3(NdZOn?Fe#x?U(g`k*DhOyM5{9n zwcROThEhX(@}chgaKT_zxDBkZDm+TlsGs*?-v^n>?e`aD-lNu|vt1V9`D-1!vl`pQ zMG@Zz<@u2WW6C^LV=B%+WVq?27EiOHB8Jr#9fqZtsB9bQy4%NI5V-Cl=s`_bWDe5f|d8m_It zgV8~kwxTZH+MCnkgo~*f?gF(=1m|yA!}4#9WJ(PD3Bt@`1wH2bnye& z_#F=yp>c%1UrM`WPdGU#eMGwt+OVVF!g8bPky#&7w5u`QTBeonc3N}fl4Z@5i_7|o zT-??)xp=IgTzXj(jJstSm(+m*E(A+c~-7m`dJ>iXvif4 zt2AI-rbSb>!lhPK%+9R6ycKD4tXC*G^J8lur$H=6`;#wRgcUA9MSe(-n^aMKz?ae< z-9oyt^pVF^+c&58Q}QviH|apz-&E1%$Fk%xtL?X$N0&Uhl;*NzcFUu??a|Cz)ijUw zfV}D@uXas^6nMjLt3nQAw=_Kn)p4q5EYSi|L# zYYmo5o;5%&{m=*IqPn@QOtV_(+ytv|FJj-T>-ir??~J;(3w{9fkQz;6S;7Jj?JBUsQF$qXW(F{aENP zMbhkq;fMG5Il{gFr_xNZ=!w`}aQEhc zDzEYis$2)f=?$V-BO1nF>{cjZhq>S$Xuel!=o_dQCPOIOSd`oT(e{O`hDl}18ea7~HSfIMU zCWk#rQ(_jJBu_h3IZi5xdCbAoT(M-ZvS0`~GRkAZd@1$n#jlyGu9GDVd=21h0N>=D zUG6PaJigD5$8^TS&v?M3TmskM^Skpq$0J*xJ9|zk1CqEIaGT616d)0TfL7L4g;T?MTPsTJ-9zN-$c zL53=&58b90w8&5$S3`9vIy($i{X&;4UKyjuBbhN0GhK0?zL{;Ish1ij$hZyWqrZ`% zI&{iVHOf$-bWHYNl0BGU24Ms&%v`igy!JkeGQ!m~Qd;<3GRXw<0|q3B!uJp}B5u%C zL|w*MBHqJB(TgqiJT%AEQ|nyiMu_tb-q)CD!Rmw2uNa|uQlcn$_&T)jGxz^p$?$AP zDbg;)Y7sv1eng(iJ5jaBBir`#2Rc+ohgTxZ`B&2?fL%(4x)}PtrZ5dc{MF%dS7mX3qJP+=p_)T2LRxhgGs^iypyz1^1^G5 zLVaF376V80nC=(R7+D5Kqb!5#yO+TL($%cbmRa90RlvY3Hkic*vp9KY|4ugS>;?fM zsKbV!^;nPce{ZgbwP8I4J=B91LOcdu0yPTlD6xw`kkd`8j2dm*14)=cseY(@lH?qU zLOcm2Q0^qinVzRHqJJcu1{u0h+&^WfdrgV80p`3*sVq=VUvx!r6V(=~v&TwxoM_ zVCBu+{1kp`Q9X>rxu7AmPe0LJoEVnxocAmIgH?=@^IP=n`#s4eDEuU6JAL~eTr0{y zIbi*OQPsYPY*;}In(z)~m23LrI_lC!Ra5^*vAk651mTvWxWM$W?fsQjBm~4xUjmOoVt~=Vk>py2)2^L;i$;I%BF~yS5N*~HAp5h#TTCvWI& z{t&+1GGm|N`Kd6k1wZ%>ExwA8P82yE;c?^lMYF6C<}w7u;e>l1lO^U!F3PnA8lJoO zAYSKBcy5<3k<_PF!BTdZ%PA2)Xf1MV1U{1}1~pF+n%iP(CWo6%L`8JCcZTTwsC?)C zyL_c8pU_ZB=gaQ5=)Zd50mJ5O3l3ET@>( zFYXwXj$Ug{UkdjMk9uE%E*Mm_E1H9kfT3qFO_zmI$iPS$dOX6%5Ndmp52?aIrpDe8l9^1GEmh zZ0rYSg!`gyz)l|AI?6iFyew1H)UKk;Ws`fL8JT;Tdh^T?U74&&)OV>b?-@`*`GwSn z>eds&f9p|SE^06CdTm9pxg_4|x6U%(HoAIy33Y&L6s}V?@_q)vCRM-oH4M!rJC%;} zLO3x&LGR+_Z01Nu1>_m)`AlRfWqBa7ai#PL@+! zgp_lYCC-+a+~unkSphnpr^pF(;~GNUbh?Q8SZw6PESZxMy@fZycK<8xgC&>6vTYL{ zYgiEbUU`D_fz4k#HR~uQVs+v6U15UO;Tl=(IU-a(R3N2nrH$!mOt9!TAkwE-!n(yv z+Bj76oeQQ`rqG~D<1A_ybYlwk&h(VoS5LoI7RaeXu(eVQ*UAc-MZmSv@el5EU}S?D zk1LTEUShTC+aY>k8-^1TMZ|?qkfCAYGt7XCtTxij>uP0SE|wm33mCqoyk*$xpd-LQKO zw%1qCdUb!Gz24EmeU5FB8~96|HI5{p6C0d-*fA=do*uSFs(l*TrL|}grPqvZpHlpl*0}2-GT9`xS zny<(gBV^(bnf_?>m9X*6Y+MFAb>lPa=B)hM-W-xWn4{oxVT_XX2GY%x^3Z!|wQDui ze2Jj{E-2T_W7!qgUa37C6 zXS)CzVy`A##ErIq>#OWomG281J7^4uh-Zt#A)G^0eZX2UuBk>n+a$Y0`X6su?vlv%Gq!bV*z*KQuN(f_WsqI7#Y*aS(;J$E#FgQ?5fVJiHpdl z@^@w3fk=z+r451he^qZweAP|h%XNuM+wPXCUuDv=qkk4LJ_-4D!i_XGO)`Q%cEbuy zz2^w|#L~nyx3^PXY-?sj)YL%p1R! z$=n|p&cHZ#PHaGc;HdBfFiRl+k~Zrcm}`NeIwG>7k6}a}aRi>t)~lo< z!26Py0z>jXIrU_~_!jxJS5gR0=I}n66L@Jj?}J6VELYIj6DUFj>xn|~r7UCu9Ioiq zRAanhY}|ejWvuP3L1V-AcY`_2nF8YJtOfbWsh=4eiD&(Yd%KxqX5JKk5K7J?BJaDL zOtqR<89D|+K^8H$-_UvAj?;tW@uk zp_4&l)+2aZjTDYzu3dt;l{A#4I#%&cLP7cB&RO0{p~>fGuQmVplvS& zer7i^n6u0BL?o|pzUv4x6g281n8*5>7M!Ci94FHwES4rX(SN=n;;Jk4H7&fDDg|r| zpY}Y~`pv{M^M-Raa#mi@FUnCTrX0yyZ9Qs?}q1v z$Cl)+ZYe0@+B!MuE@oEHgw#Q=G!mzVShxgy_`96EjbRR;NfZ2@J4lFZ2v8u#iAF#Z z_SKh93L0-v0WKHq3L-?ypbpBsB9N{Z;GxX;NWgHFQXyTzLME7)&Na`FL{b_f?KPI`^vR~2h@QOk^gM9m-}zNu~Wf9Pd;1dWkUsDw6ZzC z0(m+QLPazVtGA-<5HbGlC!Gym27U}Hk@MdiF-l)Vc$S(V6DMj`p^nCI&assNgGNnG|CFgz0yzBwW2a+P58#=oE#&hX6bXHxftB3w83{V>*onA((G`S#dF{ zFCPHMpdI!&BNmk?lH(~njQNx&03|I>PwpSo!-xv$btVum@_+{94aMzS0|ia-=ByAW z0yo+hxSpakb+fSZ=Ie4pyxgi%ZaU4_(huncyHIpwosB_w%tAwYEXpDfUDJuV1rTO1 z7sjq(m}qiyxBw=jve=TdHU9|WIdo6BpoL<@sdk;_XX55%fyz(Ex*_!*K9_on z>zb-X+AEbMs>gDd_`8IB5dKIm@Rnf+6#Yw2 zCS^t%CyTeGKJpm*^wir~aM8>+A&%nj4QKuOYd3FiO;in&Y{BF=oFN#hsn)Du;mnPzK*lNe6ga^&M~yZUur#znX7T zjL2j5d<)9-o1~^+R`L2&+xehwYRzfJ&~Z|NTp{`V3O<+F_Hz3keQ2GUJn>dH>%g=8 z6j%g&>C=F*V3Gjh%Kr?AmW$5>h_;W}Ju&csQkPk> zL}>mI43$}l`dQFW0A`afS@{~ID0^?sn>4$9KibyG{7LO6(+F%qK`8L^*DvHzGr$h4 zypSSTc2H8CRF{7R%ObKh_m5qtw$WF<_|*+39$iR7;qdXCG14m*+Z@^A>XFh zPDKm;j!)wXmz0;EYgEf@3?`}+TFJmJ zZpyRqHBofFaobH{nz zbK++gGAHi6ziUpc7}GT;0<}%zv9AwxQl+0oyyVEqbP-k__L^n*{95%SIQfu$(yEfA zyTVmh)Er%cV+*6|;gu{xS>{awVqa4fKRoY5zs7_D5B+Z@lx)xiol9rQ7cQN$%v=_} z;3D1FD~Fz{22c<+s_KHtf0RKr&j5Y$RA&_tXzwiac6{< zLEJ*DIF~GITcnEWL=M*D?YMdN+zox?p8MH@VuapJr+Tjm?yjeeK$# zKbNM=UfzYnJ`O^JvL{Ec$sn-O<@h!0#$K1ul}_w+sDPQ;u%K(E=3Xk@j4czq@TE@F z^*)nTb>?lkkpym;jnY6`J5ZIS+Z_58x$!xSRa#*qELOwK%e%4FbgN{X7mA9>x^#6dZ8qb8SpqX7Zw(7)DHpUX%<=QH5dtxh9 z*D2U)`YG7z8#Gs8D^=I$u$7IS6t<%DZfvEBJr!H|ZEPjjS~p57)FG!6Sy>8MHQJ!^e*GtYUS?x1yo z8HZNAjGhNv>Be5@aNOopG|KnXY_d^al_F41!FqO57vkF#+bu1h)rnuvISs!$70F_a zKdq69*rPqF{H~^&w9y(M!mB`0+RAn#0ldvw(+k;i4FfPEvB+JWUNk^>_vwcg_JPA1 zb;Pnqz3N6-&eFz*Ka69;7w33XZ*EZQDW z7PE@j4YaqSzgWAWZhuzNJs_(tcyV1IsUz&Kr+Olo!8pzd)V32#-bmHt4u;hmbTtP# zYNDq~tj>n8uQ9CM0D%f0a-UX>5+c=^|9=$m~AyJV&w-_%h&rpWF|! z?>EYQU*+RKaXonb0!hOB*ab_S5h=lQk1cUmk;3Pg|3a9}u}L?Q!r~Eq$p^p0wQ{Yn zfI3-4x8e$vaeg1ZG??0&O6uqgUaX zg>4gN5hxbzlRkIF5oi{B#r51K}khi$kqZoKl?9$q!ySrISDSTqm>aPR7kFyO+Z< zy*#wByO)~yu_#M?zMnf`2K}3UW_R{;Wp_Up%j%YX{-#GiL2DooXlCi>0&0$TWSya( zN?z$TzC4f>cC_9Z6~||{VSWCL3dw@EVB%m#jPPJHyswM#Ll6TsW1u=<6;Rf>us{zsEfzR|}ZeRIr z*^mS?scx~Xt^0Xr`?tw62dUR1cx~;IPn`IFKurXW1@5Bziy~*UZYV?fL-Dx-Pf8hC za@Q_z`M`E~jk?)J^Bd@Jp~RUj_vJFdjqU#0E#^SDDHF5y6EIT7N_ ziK^vV)dK)Sd$DKZmFSo`flV-9&xMtPc-l<(Ooz3r4yq*U1{ zpTL?R&lpm^Yp1+zr<9N~&rWHvQ~acq*(tBuDLN^$?36#*DR+@_x1I8HJ7pdzbL^Ca zowA6Oui7aKRf6|8xYc#*@mF)(aJ& zNTgIsUI8aC8+IFSn3YTdl+sGLg}zTGV;+o4lGEmycc~Y~R&ugS{FnWNb*v+K^9ggB zJQ{|dN7JP`X6SO?&YU707a$JPlKZ4FhkFViVi)kZ{gjg_T%L9)9}VOj16JbcBW5~J z%XpHiTIJC&p5oDb+%EnZxnFAE%gM>CuMQlhor46(N_lN#99(Os0gi6kLQX%<;@=l-PX>7fMeJPM?O&G}K-xm3ct!9K>=~sIxy`$6ZO2btD(VNom(+x|LT?Lz8x9hU{cNvxj z)ie~#(l_N}8h@TSKGUL`x<7n!xRm@KY)gBrACz@qGr`y)rskb{v>9E=Y<~Drfw)o#d08YqrJjnpSL#Sn@iLfM2W$UQ##-<*g_GAPWEyPsb7D(Rh)V0v@pz#SQhm_~Tqu?&IMPj*n zQytJqw_VS|FAI&ku~+OaoLXz(Fbs$mog;|N6-@Nw0T+|?*p;^xk~2`7Dr_AuCz}_t ztn=mR(D+t)a#{W5sbl;ht-6i56nFvY{))O)g5HXnjW@O*2-NN>dLvNKVqFxfo$L-6 zX9sF;?FG;Ac&@S z)p0VQ>bL*eRBJDzC1)#hHeA#c#w2}b#P?eHc~X7g;@xEdjKj4R4RY-jNSxg!4BnnP zKoij^cMNcpUnL)4v+k{+V6KIDkd*5vgsKkTO;v;E4-}weB?h&>S&B9oBE^Dr)>+(| z#hC8(REuKj$Vp=CP#KU}$|bR1&Jq$+fH`8*ash4ip|cU+6~G9DDBt~u)=v+88XYN^ zD@VdFnm_1=?9pZJxTE{%aQ=DV6_m1IP(UtbZk3IQSYP8tsmck}X4gTn=2Z*< z#T3vRAkmqj>ph{3U70cr~cHCsQI@j3oL+w4& zE{hLw13(zgI||1`(ZUEd7up5D7KN%*NpX^gLw1|&R8n>y)0Zc$wtAKazd^}`q4REH zGlpbS{~pRX@La|C@XtRPH7Id^iq6CQb2igBWa9O!SWpgeJ$iBTO6I;pytJm9_4cjeufF>$ zS?qa&2`FWT=XSo9LGJO7wL7CsdkBky_=F>FN@9tyKbC2ucJXV)z-jx~GF^KR>!lOJ z?&Et>%`)w#(Z%Zive89cwW@0So2^H*_l}flw|r2hUA9hp0H7s`%x^R=5IGs*2tUXi--5weZB-^tNf=` zSytZ9<(p*Qz`v?0Hhdh=X69mlHwtU4Q7K;R;Y%Pl>}~RK&HJOe&V2J@ZRQ4kSfh<< z1vbO}s?ulVJz&2lP338pcaPfdB>z8n-bgv4k31#i&CL6wef^OBwNZJ0cZ!qb0(4r) zG_amOBUjlu-s8)){NCV4t@i)p0YKJxU>&$a7#m*&&J{#U;L z%g({yGtB;eRs&_VQa(m|qc&yAr(-iW^V`ci=~Rjz@4Gw22py&{asuX>&Oxu%R zD#bIM=u`O{7}^~leGd&_)_l@-__H+j&$08#n%DRXVI_IeyfZhj&slm8_~bR)Z{L-- z&&u12yY1C=pXaB%W)b;36>_s#&++`88<|RVWy^ZBQR(OC#dYiUmv0@rcK@*lxjQk; zAn&^4?;TlpY|rQ$tQUy~>Q0m*Y>)<_pUK344ac^4>?4PChPF->m;*_ZorO)y>qy}ro)^#Ut+d%qAN2Xze)ph3Kbw_5br_PqO2ipEHa=WUq?GK|5 zs;_3YtlKm5z&dN@HhwMZw$D7cZqJN&*X^109zY)Up5!?(tAVsu^5spD8u!c`dA(fU zp6OA~Z_gZdsolc0>rTu7G&34{v{ayayS%n$<=-OLmhx?5*R}1*U;P9R$41q!YdJo8 zue>7j$B?l^ zzr;*~bq2b$Xwn%FuYZ$f_c#8ztcFGCUv*K?J!_R@kW=)1sb#~dh|g8Jx1 z6C7UJG<1T)&n1tntE5UTw884X%EA1%`mBmv!RuFO+k^^}!)%Hb)HYRh$@C$)jJoR*xHK+ab5`!=Ln2SGfIbT}M! zsr`db$e zsn)?#YWEpP1x3P5EbyRUJmOtj7pQEd1sa-0Id!iJz!XGzJ*c2L@RHDAzAe$=0pCUf z&NoMUiMwHIP+*m#0todBtOABaXywW?oNoonmjrwO99R~lq9c3_(Q`yJox_0TuaaS~ z2CNd`TYXo_P+&s+;%3~AbEI31(wBPPL0K*4GX#jF!o578L%*FSlR|E^SL@1hb^?Y> z4#`fAZ7I_&C&QIAMitXo2F%g@lG5eFy~4R1T$aO0hGSt_4xEL>xB_8Vy5O$8SYNsS zS~S&1)}Xc)?$VVQp771*=tsyV0~)8JCJJi z1al;jXC;$O?RTA$;7;w&A|Z9qLndtnbm;m(L5dXOL*&#!uw}jD_IU_2nSPNoft zKhk+_SF_WZA31^}T7evD+An`gOw=T(oEf`E&U+7FLfl)JdIU?7pmj3D!~;CvFm572gfUF1#-Hha1d+`*Z#>gY8IRPxqJE$9#>7 zj9pegbLq^^leO&>Q}KB@QFn6$Hf<7cL$7uE#mqOa=A*dRVcyX7u@&V^j(^NII5%|q z$6SAus)gPC|KJ}}6h?69)HuVga4*-4$z>9m?oaEs0LJm@TxH5B)X+{-Ea zZ-wd<{@>~R5d{K5-o3j)-r>vLv-1-h*aj}gyN}+D!~sP|C7KH(;ZGd1<#eC1tLx;- z;(t3lSZsO{2pIy|3G7u90m2XlMm-uBXve|y(}%0y2leiuTPI_e%A^~ElW`Zt!I;0Nr zx>#d($AGZU+3`8M;O%#m8qLPXtS=p}d*YWh?`cX%=l_YSj9n1@cc=6Fa3AmJ6}z3c z)ji6;UzLA{gvBA=E$*8*6^%m6%SCHXYxyrOkfz*$bSVNXq0ti_A0-hWS~uRn-`Z()nOfs1$3#W}}6;1BLyK1})gaNfckm&O$HQHXLopih4Muww=v3Lw!mZ&?Y@# zpXkQvws$(#j5lZ;Fm?wj-x==8c9~OYifGyy&Fnv}Kzx6HEZYNX1Szwh#W!S!3L2@t z#n?bwMhaG-N>OKBB{*L-3VcKcNqb3Q7jilS1+-K0tq~+|v}mgjo?XzG$@Z#aj_k2b z(Gm6@JEUumZHj4?8)TccdIxfLSXntc0+k2+737W|^hbvUDi78@#BQ7$h#&XI&Z5x4 zRxTen94<(N)dz=!jJ1LGtwA=^f`0^a8qjyts-B_&Dvo`Ba4Q=C=s9Nxh*FwdXKVm) zeGA>HvFcSzVOHm>K+eWOXXefBoW1P9+4LSxS=~r!W3<567#$kmPk$!7e}bgN&JN`4 zv-)bQt$xxXD^K32z0=_eD7WQ=8yQ+gmWd`K8c+kl zL(d_#-(`)IQKkR(Tjxj_P#_?0yM~bY_&*!F%D1H}amybaT4||)%ZZ;jq5+7eyX4eMSc3ezFo{||fb0T$KK#(kefIw-54BDPggv4Gf7-s&oT<>kCc>R?uMAq0d7qedWnXP4(QF{5?|nge^}bz=H6smz9UDQ=DK* zd#Zzc5-yzx3C_Oz;_*oNHQnktFX5Z+4*T!hB-@s1WYg=%ebkjF6)6qS+cw#Oy{WpJ zYPVfPH4AJuDwqM)hxwVIL^NNCpL~(*Md*4 z%U432dxVCPzjK~-O#XJnh`Fo_z^71+?U(qAnP?gW$->iVK_&U-+ObAZ>@Fr4too-^^EO=8Me3=4$Riz!_oKpI1t~?3Y4ZeQj ziQ`RbcmNrO)Ak_;C}su&FGdLU7-!mTOWP4#;A| z(w#qXFmF6Gql4QX*Ai$1SRR0FfdhK5G~f&$OR^RM?cnqccMx(^xMO2ZK1VBIR0}Uz z7EjCJ91Xua4JTEH4fD+5wE;@O86&kSgmyBA#X9z|B%)*2Qw|DvAJiRHAPjASC)^}K zUE$$nC&YKP>|+@K4;GNzIzW)sAoAFr65cb8_b&@9Qnx@6_rVw>To|#Lph|en^Mb3$ zF$fX^yRy{54Std90t`g?;`^@J!-^$EYHZ(m>SBkHFoaQ8QxuQ|!y?IRz9p9Lf%g@_jT8LjJKds=bMUa3 zp9{BUPPml`Ta#_e10CE8;ZJ+{xen5!VNNUw>shd+W6cB|7n>;%_N#nv0JkUXx1j>z zeZRv4<#%Pm&ba2ARo3<5A$asGm^auQ*7hjpX<)z-#U5_=mH3MP0Og@YV4odmt^hAH z^7@i_!EgP*BL@q$n0c9lmpOSYWM0ZemAVb68ZJpNqjESEj$cT^ zy4K^i@`eh2EDYJ~aYM#1w}Y&J1tqwldV`X2AHEJHzz=X{sG19~Mijhkd5AYQWr zRf^d&)B^s{O@MMgERlHMLIXQ7z%z^OGkD7kCKafKZ_frOTWGW>S#{UAZ~@|$+(ff8REVp!mG$$B3mKm*laEPKvS;rz&2utVYOj>;>iRHGlR0S~sGYBrkS)efvWhBH%0q;DU% z%Z4v6!t3&2NJ#AjKh7EkZHOWrLhn3zL;d26=+@Sbcpwbq&n~da|qxT z;Qe#b9hez3BnBIJedH^b;C%!>O^###Jzolwptx42WnhO}Fx`nhv!D~=9EL-d+7*8E z@kJ2~xcv3`DnIlC80+e#NQDz12elvYhdZH~Vf?NY*LeO2HiFI8)%UKy4iwsWE?190EV8 z8d(_9>OxTD(cmm6I2!zwP<4E+uk5R+<&a_rZ+B3@ppibLR$)k2$mZ1OhwwdRe3IbK z=zQ;8yWKBPUO4|8R*rjd7{0ZyfG-}NQav z=@jfYn9{bGft+P=1!bN^e|*}5tA-l32Y}Z}ix%L6*`Bm$3|n|{JZaIIw&bg3;KTxa z9Bhg)Pns)WUkKIyS+%=g!h}Q2RooNKMYWx|Cq%^gh|>3P*91ETe=r008@mgnYZe@I zR82RkW@ksan$}QIZp3NZp(n*jvmfj6YZ$b5Rqk1mI@?}{(MS=INJi>Yez+YB3 z3AR|>f@q5u7whH=elTh0892{L0ay=99Lh@UCa={~u_x@TtMBW7R1fp{5toDGjhzNM zINT$>uOEv3uy4BHMVh@?JWO6ZjT2aS4uJOw(cNDN{-P}&KC1Wio5{(22;Zxn%^r>6 z*?2^CZhzLr+`hxyy5fpv%u)-llYL+RH?%;0@D>f8Ukv7_t-SP} zgK_XwD(FKOtP=F_EhJd@3cjiOkSvG`=P&5L$CR+&@!XSj7{1vo!T6j54o%FyqlYI^ zUl?t{S7cs-nWPjmeElO_hEn0ZB}bIt&HbRJe>fa~HNr=Z&h{xRg4Yi^zzd7uPVW07 zp;<+kVh|*kRe1QeHM}3hApu6O;TBL=2#?B!BKMgW-Q+s=LEa zr{TqvXbP%z`qPUc4gsfvpd1(ue&L-(2lKB*Fx`DA>+p->R)^mgwkm>X)!|_j-q+!v z;C&<%hADj0;RSF#cch?Bt{oD2DzsK1q>PwyEC^m(`S?!t z&Va4z^UM2^8U_L*d+TekZ>z(ohr^Mkf?xZbAF763p!(=xzdP^VpE2@Qm39N4Y}p8N z?>syUycE_=F+WfFSB(|$Yf#ve@TMN7nO0tV2j6TD&}9vWUKgq@y>g4CLy@B$yf&Vb|Y1jhLCDWb)>UMKPO#5 zx{>sI(n8Xkq!pxfs6E|Cy-0&e$B?Fwenz^2bR+3*(j%l7N$->LPOWzp(y_)|)oO*z zS`+2rO*Qay7Qdo`XTCL~TtP(`OjM(a{y6B8&v zM0k`cJSi!BswyFRiYhKTVSGvqnvzqJViU%zQW6tYafu1z4aw>2vHbLoOiM`h4Ql#x zd(J=jbGvE$bP69S^cFe^!-ZraT1bMwodjPYN{AO?g#`E{3n}0m4u2CtJHaPWh!iG5 z*c1pe34AbIq!1^hLab=vLqmvU;0UlsLX3Dp2Wdn?oLHa)VXQC?w&H*>PY(ebVjP5s zlS^S}0(qkagh`{4U|!=P_GCG1Dp*v)boi$d{w@^ejOB`lSjmP&tK*J^{HH?6Fcly8 zrxHdO(j7*Y-q1P|g^`dy*3RE3#Q;bZ>w{_VQv9cq@&f#Q{w@AD<^R_lYiB5}kx?o?gO^-)tVg5lgq~AN=LxG%hYNGMpW}X}A9&{y+Gq-(lg^jafc? zzgLmJX*pmJpoR^Oq2La zi1DcuU5<%8&x8wzdHD*7dH%)3b}Tue)Fi%gD621-g$lCc(6j8uGCNP-npi<*dt!Vb zN2fGlCzJ5bCiW&KtTJIYV!l7Rnb^IEd3pMn*wrT7*Cc$P35S@lh8S;#(G4@Phnet) zCafjq?Gt5Uk1?^wnb>tE_7oF)nh8%g;dGPub4)nXgg-aoY!hB$!YfR8H8F3m924GX z5^}n z#GYwl&o!|Z61SrGdSVx1WjIS;MeIckvw^Y;BW^>SPR!%y5X1Pr?DB|t`|lyfCpL5i zCgF>SdHE`c`T0kPVCCcOuOzO+k`q+KygX`R-hNu*))YRI*p)cX#9mC?f$Y|iEPtN9 z8?lP)8se73X~b^Cxy0>>i-~b+4_y^8KOb91vHV~VLv|`+e*QE)UwWE^4nd*s;;! z5wR>~NOwx?xRkM-DI?BZ46I8@>d_7IPm0ES`N$L=FEt@NE_QrEbd>DDtymF!1fb}G zDk?fIIwczJ{{%r41))zL0q$l6!3R8Az-RPFEM%fCIw>5=i{uXBu>W*`PU-FrAKZoy z9+U23d${Ws1nl3Cl+Xv#fZ&1%+7Z;PogjPyItD(|;nM~pO^1#4VDA7QcldOIPiy#y z5UCF&0B?j9ro(3pe6*9trG!VsMYn~x=(Lj~Oc-0Vt)NYrs*6q>$Abx4yrqg$C8xlB zRitWsYIstVY}2v_pz3gwaeqUrfPX6PAhKxL?qbb_PfR8uv{BLH!c*f?RFQB)NKsXh zaf!*PNpe}W*cZZ+{y9FSZyY@q?jf`D zB|xjgehml|v_pms3zAC#cD7#)y`m=neZtuPdB0{mB$}h3O^i>7Rbd}Wj)m+5ZFM8D zBgJ^9V);z%qd%$fajGG~C}dD&|++u77E{>ejCjj<>%_I~#|F@KI9} z!sFr4h)sx1iG?%AwCE(&xWptC?-$%2o)VoFn_|@cjYB~?hR*XJvonPIuL_6t{!2Ul zOZqjA_mr5V=qx_4#32^W*zqtX z;Vw*p?F3MKPEQh|VGBn_B47*0V%%Z;qBHzuAG!K4Lh+yDC&PFIj>yCqVt0WpPZk;e z;@C|RgouW;y9)d&CYYqlg5xtnzqn*yd>l3;02}1rFDW`&_9pHhoiZXkE|vMAf&7OW z`{R8B`Ue@hxqlLLcsSsPLhHh#dq5gSH8@{r2R1gvM~_tUSP_mNMZ>5}Z$Smux?vEe zBW$s8B^=SI_KTC-nE6Es{5h0o5DL~vXpuxnAra!IK->}Fmkc9EI3lHjQJ)U*PbCZi zA2#L_4>_xZ5P47G=vWf8Id}h7-WC*4Bj-6D{)R(ez*;cZ4^qXNaUaYD_ZH^>7!A`w ztY|1OITn9%ZTr`f8s~$j$TpTD#q#%DbR`^`l7V4Ek%cVj#?L9l>FAbu~8 zzDzgNcaxU|feagCZW<2o~AUTnf&nDCb-yvM9&{Jkb@kA(oopYLoK zLd}z*wb@xM8d@w0S`u%?{S%V{;QFOWN{oz7h8_B3NX_xCZbNIb?gz$k`AqpZHdpIq zWGBs|aA!!VI6?}gn>&YvzeoDZe#Xilna?g)vWuE4On#U{dbs#5|lUx10E5 ze8aOXPmI*mUtwZL{5wCsFHGZe#`CXnJnV1yfxh?`1uxnE-xTx2_&je6j}HC*8;51W zeu|0?(?RC(QF%YMf^Br1aT~*A~s9SMr*kAe^8 zjn@E7Cmud3_~_u%1U^ad!Lp{q2g{m4Itvv2XTt~6nFAk;57#lF9emajuLs3)ZG;cz z^9_73+z;@q(!946pBw; zK&nh7e^UM&l17|OT1@(H&lgeh9TeVm#maX#rq3w<7N6gt^LY4l(#5EW{w;nSv@_nt z;oaOcxh(7+*f_n;|4Y(Kgu6d{CcwLKoT)a>!PAh#zm=EWS4Tp8ykEz=VZ3_{hx?(L z&v(2H{w+N=BhO~Sd5Q4(keBG+<4=X0F+M&W@Dy+_uvYuG`0R-l@BMjPF?`7fcAdfee3PPKvOd~Z7n|%maA=Txy;Fj}0zQAIc2B9{YmNmr2O zlI|fbAU#7`MEVP9G3gD`64HC5rKC?t^`sS~RiwhltUT7FPNYppdHSxz9Z9>9s!27Z zVWcsnDWqwn>7?1Dxun=8=<fNzC%+JgituXhrzhyNSKQo7LzLUONteShNlQ%iwcLKJFv@&`vbxyBS6>$wuQdn zACsCS8(x4ltK{zmR zvd}j=Qcf4*z+Bi=jF%*DfG@<4i~)r-V)1Zyuq$3YBLRzWGG(Zk%rbNd_u*FUg z2GUPNf;^19F+T(Qh6;iPKIlS2J4Hoxnraxpi~`qD6$(y;8&ko_;Q|AD$H(`^5gX=) z%IANG57&eQ@EQh;g&YZg@q{!C?sQT?`^nc>c=fG(l|seq2Hr)obXojrX6fMSK^B6g zha;fXevmAskE7%~B>M9Fs{L5Lc%`rQ!*IcnK9;*5q#OYn3Y-M$3)eYRWGrDY)e{x> zg)iuM(4i0yFD--Q`vd`=0}|mn3t!&_f84VyeH=q(qr)s8ylQeE2n3Ghi{tgovHWoi zm^qfey!&MjRu04NqVlk@OANru#qML+JOWFHl@sr2@ve!N7w>ks4-3c2U(??gN{LQG zirb;^uM@Os6#Sh^`x5=p2mj${Jchx4I4aA0FeD2dC`*M`+aEBsubO%pQPZsU}{-qXBLGP&f&ke@-*}t0pe@}sM7xth2&4CdE z(|{E0Co}&~*Z==?{{PeM@9XE^H=tktz@Xp(A)x~Y zX$B7&It<>k95(6$*8cx&|9?gM3nphjJj?O(pWENq{(2VtUvLIgWElP_{!7QtV5qYc z)&I;C|J^?x-?%>iU44yX;I{SOod4@7Fnv#h#Yu32ZRV?lZN7z}{>uteFw^k=U2UsF zDTV57wxv)QUj#U4ma^v;9J59jMvUVN=(NNzj4ituVw?{^mqyIzozjVM`~qDjG0p{` z%O&v)7hFh;V<_l~h;ghQT`@5q z*C-*zu@iKq#C#pKo|vy2ts=%b1KBNO?TK?H=&XqwF%*=UBF4E2bZ*2j zUoSgP;%3BN#C+Yfnpj2lK;jm}8se73VZ^P7wZyK(F~qHjb;NCm(}>#=rxWuV(@bKV z13{Nf+<~F6g194b4zW9NE-|h(MVCk1gQ1X5+>^L~*o(N3xEFB|FXe?MYt;{L=+;viyY;$UJG@c?2s;(^4T#Dj>vh&9A&;t|Aw z#C%*)L;L~R!-)BlpO$zg*<*+mbOO*3n-QlI*CNg)HYeuuS{B4PWVa;FBeo(gAhsqh zB8C@9$c~S<*b$eI-JV!aT$@-}!S+WT9-r8OSVgQP_9U)L%;)Rs5%c*vM`AUFcOuph z*C*BzHz3v#I}@i9HzLj^b|KCoZcLm<+=RG*xG8ZFaWmo);^xG9VimFQ6>Fas#7g3p z#46%e#Gb^i#A@Q!#2Vr@#9HFE#5&@3#OcKCiL;425a$qgB+estCoUxJL|jbVg}9Ws z8*vpe{w_2+>y@njJ&2u&dlI`5dl7pP_aY7??oAv<>`fd)EE1;?OT?MPYT^~dzQno2 z{>1sjeTj>R1BgqA`w{Dj`x6VRSbc+tmBhisD&hgep2Q)TA{L2Di2aH6#1o0F*Rc94 z=!VLf*qqpn*oxST*oHWe*p@hq*q%6s*o8Qa*qb<;cp`BQG2BSYE|1upxPaJ-xQN(> zxP;i2SWj$EEUabqbs<&~dlRdO6?B8=No-E6CblBh5Ze%IiEW8>#P-DL#4g0y#NNcY z#0t7$%qO-XE+n=kE+)1oE+uv$t|Im(R<5J{O*fP(VjE&lVq0Q0u?w+=*qb`bia$ill3 z+Yo#4@Wg>UyeA7E#={fG@bJWGJbX76K9h$hUcti?=koAgEPOt<6BlxOA7(G+cH&ZQ zS2KGRw-Z~hr~3D0c4y9kjNLegF!tiCVH`+oKAmwGu?ukwF`LB@U{oJxDFm2{2$z+w zR|=$&^++0X$5|0{IIoWmXZM-n>|39kM|hci6r@J$Wq zQYjwJZlFsx)Qja8C(qX4dNjuP{t0xGsh%-3rx6aTjL=0>x#B6^aA*m16Dc09ra>1& z@o_x}x``BiBBhrMt5ndXQo1RWKfWOY-FV6eS9PGnnH+SuejHuApS;F_7C^7=hw>?iJmpTM7ku)nwq0fzY(>?iK<9SHthg8juE zX1Vxt5cV77089)VTOHEhunrFUjXTV;@#iY+KgdH^aJe73gO$2Ha4OgXVFrjf>{nsAx5JyHfc*^lW506;;^DFXA@lsPA7XiU zd9goY`|$fmo&Lus(G3mznby*G7gW& znLG4SUN5$Kt)F4P;cltdOC$9K1?iXymR;ZyJ@i@ixo4lXn@rulk3p{R- zdH#6(;(jr02RxpQ`%i>i@9J{lac*2rJl>5NkNcX8<*ts8=K*7mm-m0Q9nTNO_GFXv z@O**$g|}0rT+jZ{qnN|t}w3_BQS}#_@Q&1wh%+ zv33bC^gnj}4m2E3tet!fnfTrai1V<8cEZtxu z#&#HCs1Md3eR$7c4H=VuYTZ$PV`VZZYB2karvHIqJw6N1k2Ze14KT7}c#Yva!t2AY^DKQ|qxxg(3^yDX z%eSX$t_uaU+_;s7v1C9Ct<~Pvjx{s_e)W7r=E%7m89q|p~bYebFkWGAs>^a1Ti1UaGxt-<}+7TC!eHC#r@kC-R zg>ONuCwnAu7`3m0SooH;55NCY63-yJiuea&Eyd^agkEIl$4wg9%_)2!+4+4<81Y=P zr&D+f;ux~?^E00}@yuOQ~<<6PqJ$evHUjaW)ouqw-Y-PpCWc6{({(xct3F<@p0lX;yuJM#8-&Z zh>M6biTQo<3gQB?=MrBc&Y}9*5a*Nq0&yYnZ^XsKzY>=cpC+y%-b-x#9cz!n#LmRO z5W5j?B=#cSK^#bYi#Uw_zq^#DT<{h{K3?62}np>v|gT_hio`KETtXhII-KWcMQbQsO}3!NkQ&S^i4mFtYRKZ9XsVPWBkGv#Yfr z@OkgfWKSb|8Zn=T?oFIY_F2RwOIiMPi8Zu8CE^tnejafy@lawu&)$VNpX^hK)2TgM z6Bm+wJaHP?U5Sgy&PLB*9S8A3vgc9w4#ZVtk0-YNp4DeIv6|}RL+niU&xk$A?oI4Q z_Bq7Gl%FTD7unYk2NJVUJ6MN9oJ#f>;^D-6T}nOTG_o%v&Lkd2yn=W+F<-~xNSsUd z1;o}=pB}{dWdDS?YAI_UC*ne~k02J7F?)UDVzQ4UE+t+;Tt)mjvGoru{yJi3;#_X0 z^cxVnkv)u9N%n@sUSwyZ34*}aP4ppqAlW}94kP}CIEHvKaT@VzVio1rh&Yq%qj`L? zwiSvkyiSvm|iGL=pB0fp%M)|uCTj#U({g9ZC>)$1ICi@X$ zPvW151Bo{iYl*+&;VHky#A#$-Nt{Xi5wV`acO_my_G!d?or;<`m+bS2otLxvcoFB5 zJ)KxZ_Flw=WX~Y>qWqf>7n6MqaVc>Ev6{j+C9Wd-SYmrQOFkCksaaTvu{5j> zf;f=u&4}H|t|hJ_dpBY)vaco95PwXZP2t-T$B;diIE^@wIFq;*@e1NQ#JR-T#QDVQ zi3^E$6BiS2B`ziIM_fg$BWB~W_|0qP*mG_;Jii-r1dQ$)a}>>X@aODknw{bt3!~`9 z;U~~+3b#+BSrg82Fv@QnJ`P6Uj2Xu|j2YiSZp@QR>^MqlY{yYfW9FmWJpZXi^~KRn zHX0adG!Dpo*xxv?^AvDC${kA2(`WN9e6|kfV{qI7zn{$<8;^>B6!1H?)#zRu7unVrwR@%-?6 zRL142?&mE0G($gR<1yI#d3YRu!EbjPGrk$WCY#!8#>ek;8;57#B|{&UU-kZGJl?SX z7{?g;4dZzDuDNk~_)dRg=Ch7GeS8~!O?%Dq=?wjrrH`ZH#^F;DBWz1*a+5EdZj9ByYHk&^iZ)h(z-!;*2e6V??>hkk>AMS(f z4rdSEer$ehqTxKw<`wyDI?k)(yd$4w$9Zd<_o+S}+58}%O-DP z#&&kRRoju{4gHVJ|KVTe*t{@beZbZu822AGPsCR#pdIr!4v*x&ev#|M%zg`cSe=@ZD--T!X#@A0apT~2@`eJ|OrA5a1UVK|7FAtUhL$E71 zo2RXAZ}vVDW5#)ZzDfnxabWGM`wv?Oz*n!J9s3Jk{leC-xWhSupFh|-0iEIc&E^r= zU*q;M_BXCCo7bNVrC^TDzgL%s%@^SLgq>lkne|xCJU-(XLwOkE8$y_4oNPEhFitUC zKiPbLb^a7h5boIxGm}9X=|1M+F%kpYPjtXyb=R%i2Y<`~0ZA zZ@XC>yEu2;7cc93$Mt1#lvlH(mvr=t_*D_ma%ICdZ3p2qtJ-X-)!}&~T$_CJ>iGEh z!sHwAJ02$~Fv8(;QEfLwo_5~&XX}qIoYnn#YhrWp(VDUBM*CjEyQ60(*dG|zrFk!p zT4n(sw63~1X+-dmqt~q9Js^uba;^P$Z=LSaVxjun{AYIOj?HOn_jvlzC9QV^)x%6( ze!Dho-I+t-qi$^5_TfqUz825NH#l%%$gUq=w_C7CyQDqlFSM|q|6{|A3wMp!vaY{n zkKF@4Dj9nt-FE9cz543H+@f@siLD}GJ&#vNmt`kM&c5syd@sD^ma9I>K0hTr`+adr z{ZR$AH#dC}ceTCp(tSux`DwG!;e!%l+kfobgGlC~7%GRWDS z?uU94!eJ!yUfre(#vdJ_YQCg*Ru7w1*0H*u4n|K_JwDcE)tb`r^%HFBujx?UWs>9I zz=P*D{2nl5CO!=-e@t8Q!mJ>r@#n%v`n=Q=*S9$ByL#tVl0$UJ0JkX(N-E=zEcPFK z{?yX8!?)eJ()NDt>doK!Ks-SmT=b+upV?~W@p#*?MDs)bM?Z3}3bd~Kw8^NPS8n=> zv(hGyMmdwN9GKfTc7`J1rE<*(m#L{67YV}c-TV5aX3qMufpm6a?>+Nw&RuKWFCa9= ze)ILWi@$We<@hAVvu)o!Up@8B`2Of=v#_$kF;_RVo;+j0q=C?bqF&#h{Xx0Ij*(01 z?oAiQ?QN$zfARRQGvcL9O+tS-dsDaOnvZXT9|oQ-nzX|4-mlrc)r0Ox+g>J432k$K zmo4@(W#{4BY%Ye`Z~nPW_=vf)n@l)dF*kK`Y2S|L`{($swXi-VTJFy&Ej<5Vc)_dN zyZb8s%=<*;n%p|+LdMJddpj-aTd5cj)z+>|Mjuk zoX5*9P4NzDw$r_Nf0fscWf#u9nt%Q5&4Hg4#0+2mQS9z{ed@VR3YejUHyku+8{9Bq z^S9RVtG5^rwo7)>%M^*eXaKiiUbta4$@3il_ z`m}fZw3t<0CSE-mA$s5Vfadlh5TU+=mw{Eu8!@T#hj%eUFS+o||?bI0J=rH7ie zYjW)5){B>mC!g$b!G8F)FSFO2T$%gxfY#2F^|fs>b6=z=oA-WGH}Llz3nND(ftpUvU5SVO7icF)QbJeO%mV>ba!JC(_pK z`+e%bT1)Q9rw&0_|L~U&;}U1wTlL_o_sjK(5mN?t%Q{gys>98&n|;oQz3s;yY(7eTU{|}0nJ#V~iLnA5192zj{ z-Hd~s%ufx-`{ViTYXjFWO1^G$Z}rnb<7=<0zvar_y_I#Ac_lAC67=}Y@(yE{N?s0? z-z`=-^-Q~P%kRSBn=QrqBX8}RKlb(SKDzC< zXQxNE@4im_`mB^N&27(%DTfCX6ycl#Wy`nRx}|t`>Dk3}v#B{fq3DWLt2JdiZk+76!^f*<=!xW_@$ru= zb5{+nDz9~H!|fqCy;~`N-IDxi-~CtXov-CQSGTJ7jUlB&cK<%`bwjI8n?n!Y{&kz} zpg$r$|MG-F&yMOTlq z{AUYmJ2=KJ2}#a%Xrom-D*D)FPCC=VeN)drPhFe8r}K|~z9BEK#93s)@Rh}#>3=LA zaxDC4#Ma_#b8kNCnjapcU-Hw+GmaB3A70=tcNnE(X^&|q*0yUBUygg65Y#B~`hqW0XQhnku`=O!=O40r9QSi8TsU>7 z>&-g2lcIbNugtvsRY(7;E)UW-c=%LK^qCqo%YVy*sloT(wp^*ZQu62d(-yyOT<}wz z5Ow#v8v{2bSm&-^8?Y#)aM;2(BTuB9`Q>Q7Uqa;7k!MEl?f&qcV_f*)V=mYCFWtLl zVA*=jgO@iZ&5X3_u&&e1H7zkeK{@ZIF?~k#oO0Le>z{oFt-YAhaqWwZDJNeZUH+iS z1h0Ezx3A86X7%bx=H=~&KM>EI+4ZSTYQE-DynCs=^NfaxOJc{|?zk{2u-E#fbxRz& zh4pE;XGvvX+n+|ZneK5dD>WxO-P`f|>+acs=i@&yzvuduM$VJ}F@s%M3+{u=Z3KmR zZ9!pCPf%Do3uczh1T(AFf|<3uP|LczP|HRXYS{(~=C(rwbGwfObNg7q!ah~7s69)t zsFN*NIII&a9d-(q%7cPs-SdJ~-4em7Ub$f9XrZunbXHi`cUM?9@K@M07^Sdjs8iTD zXDV!+a}>6X_9<*#E-7pqKULT@wlTA7(%Q_fDV|4gjJy=CTuK{0#1IrtF6Ru89sPlW9`XB#%y+--cjJW&KNp3y2hQy5ciF^VWZ;!wp*v+@7XG+_<>aas z703#I-opLuUtM0!@WN9QE;C`ctsqA~b~lsr{b5*wy@v3=oY)WLn`hv4gUhU11w|NG z|Nf)GZs~mtjOk#SJUwhKJoWKz;14Uta4p?%SSjYp12N5B`_pJlNB7IFiKyD8`%+MI zCNKF|*3fPls2b~4b5IK=y_<`go;@ZDRX^d!g{V2RZ5N}K?A`kXs%FsGC8)wst7WJ~ zM|Uho)o&c~6{_mx^OdN&qsv#L=2&{IMa?^YWu455{W++r+jG~W7MKm+AnW)RU!!K% z{X^DtMgB(Q60cdCP?h@zY(`bjcKQajV8snti(Y-5EA!R~TTqL(cy2|7a>y#I+`kPu zd%-8$QI(f{ccA7>GT(`+i#j1|-p;wZkPF;g<^8X3Ge_3!i+5%9?CAe3+ST1Q$f|uT z>_%1&87pf^%uljvJR5(9{w29-vU*;+ENjlLE_=|wNL(bVD&w)No>ThE{X^U0YgyIl z@0czb^*x5yf3!!|yx;2lfGjx0%BocymDO`|lYH5~)?`^dozKgvzTb8q`e(0Tmh&XGn1a{7b(W!3%^Cu_R> zGFb(mL$WFdmCCC3t9uy3=Q#J4HN7Z8*6aycvML{ZC#z?_8;mo}k6`$c?Hy$;+A~a6 z{krM0rYCHaHBWU$*6i<}$qM`ND8^GQ^kZ5SD{FdXwyau3zN{tso3eTyw>T!(JNCR^9rhC*}6=^OIG5W}K`!K3TGQF5e}q z^6Dj7HMOc_RW)c*i1D)Ps%0&B9w}?dk-4%8Q@6Y2W>>e3@+)yI7*YhK@P7+al^wcz_lvU(1-KZEf!zq&IG8YHXw8=b74 zWs4cN{Z7{OpkHKFhCP>67~0@0#@F`l$+S+Gtit&;S#{IDk~OEnep!pwT$5E__qD8^ z$&Jrpd^nC|)w~@otG3N_Sxfq?W~x3QYkHR(vKG~S!~AbHK9BK>zVene$8WT(=~t)8 zsvEOX*6a)WWYu@MDr?THDp@rpc13djlN-zGY2HCr<-Fdqsx5+LElC?CtNw0`th#<_ zvZm+El~q~3Ojb|t&9dr0-YctS&oNoG_bxHJ{aslLI+V+*^jBQK{MAF0Oh+}B)icaP zR$-8jtVKQ{vgWvsmQ`14f~=ZrX|k%f%#~HETPABxr%kfL`9ao#Wk+Pq6EDcBzj;em z-IS-YYOUYOnzPvMB9=$v=pw6bft##Kp{J~#69Z%|IyY2Sp+f}YnL1h3XFriu>$pHx z)sR)P=FHn7YstPJnBF-qYo7TpvTB;&k+qU;YAS)3_|JEnHn>}YN+9=r6k zpy6^0(Jbhf9aDmL_|)<2sh1|#5sy|{_Ww2FwvW?+(B@|pmg1q`D+iZ6d*t)@nM*Uf zX*S|XvjW|`uM2zzpM;e;9!jxJ!r7<7==$QWy|r}hKD*@;|8V}=f?p{%?v(7gb)Bnh7nme|+c<*Yvm!ZL~;u8Co#i|wcM4P+jUk2K> z7vDv1-Zmn%xfnUPN26@}7GjUKqu!-CRQa54QrJ3kh*F%85^kh_R@oOpOTBoe0V$Ytv-~N{GDw@~Z8UD#E zM{%9Q!=R5p?4Z*3$Fw@k7bk>nu`+dl10*CuVmAsfQacNo`B z^lW>qZlS-k*yHC9?0$?=i5nGd=h%0%6|YQPw0HPI7jfm9F{@j+KlGXKc4qDSk&@^X zIrwUwRUV>!_lQQ>t=fqhW751b6T67JHg5}=Fxp#eGI`;R2`%+Lf4KDS{A+L@vEhQ` z>7`NiMdxwz%bqOiDvp~x<@wq#e8l{m1$ytmUSgZDtxxOx<}L2;X*TM&W4*+aryrL* zeC8r)4wRiFQ{r?t>p%(+;8 zF6#o+FV)uG!>xsQ`kPT*pXq9gt<|}XpUmtaHk;StXo6=~QInd5mrpUO>93!6&lAOz zC7-q|oZMTy``OJQC+hf!yTU#zIkT~oxYoAd;)Xfi;)T&i%JVj<#IGtxx71$o5qs)a zed_S7wRrpI?7girMX_>OvuAZ3dx$F?Hheg@xrexEU$3B~r5(jPVdAu<7G7evTI(LQ zDeENKj9578`Oscs;IR=^iHCcL72)L$Eq`_uHw^W;th(J@98o?meCCkeqQmN6-oF{) zC0_g0ZI*|kmISiE|UhFKt@}*+sgEA9mbuxW3(cpMEw?9s~^QE`AzsS><`(TkI8g zRe$TN)?!B9qHjMv(?cxGZ6?I`?erj#y$gX0gB7Eo63*O?L%A+Hc zU)zhXtyf3r+~9?W<1eOr?e8sap3&~-u01-6W{>{-%4<#w@xHZc&8aiqqUO?1eJunp z@%995n@S037Ne_m(O<<#SDGzGpXHz#<8DwhjPTXj9&82XSpC* zeiGJIbY54_?16I+I*xjX!CLD%o2?|V#jqv!4t47z+Ijlc(SPeDMmD}O$Io99d!+Ol zV-wk1yxC~qh|m1GiJPzPR(x@~huGxH_Pu9$wG}DH%XrrxigIHrH)H>;ku7w>6ztoN&}CyU@>sqwW%L!`id>ihCcdbONe;j^7IIw8SO;J ztp{Fy_^nF(ZSwg@)#%P*VB4+F?$2%~E_wLAA}T}@9X=?XzW-o9F@N*Gx3R0e#cdnM z&fYYkgLvT&hp9);_=_cj)W<>!+KFq5Q>P6`R*Ur-lpHhf+gMx@cDu-V=v^u`$WV|AucZn^!8a;_6)+-69V$V7_@)W#a+jh%K*f4S6bxKSkdYY$gWK z`5{!)-s$<-hJ-*-zrnt2S9Cuyc@XBRErNs-sqKhGE^K{ zVgJpS<{_f}=1u3W%nla!k33#mcg0h*{i(EG+^N1|{Z@CsY}C<5^qV!-vBk-j;*6AQ zq2kK6Vux>hW7k!^@#$<`X7)``3-Nkt#`FO@28y;1BHww&4iq~*y?W5{wy*d_ld@SI zV%){)8?R@US@jkxlMcIgd#M)xX?#Xi(j5N1m-JR|6f+CnOZ8qUwl-P+UQ$(MG_T5d zFYUdavt1eaUYg_=yf(Toux+cpm95`P*3GBu4_m#L%oYYzoqYICDtMy!&g#rN>9Jz} zwDg_tr13xKw{>3pPP(UAzR4x!os{ys;#BXE@1$gx8&eneekV1P9cYOH{=bvve^zz( z%I|Nb(@k@3C{DkXu4n9J`7XT`GkgKwn8x5rp~xABeS zH~;kqZL;1-O-@dN|8Jzvf@)t_681*YR~=O>lE6R5qu1UxZ={lfj9ON8-bh&sK6^U* z`D^Kg<&t?FetRu_EM`W(J@#5E+JD&T<@VQ7tM|?wnk{`TWp%yg6F1|vWbyRi-8<2* zr9q8L_hbxwEuFJ04i4=3TDqDZr|zM8Ep2RhwetX**OJ?=^#?vHey5Dit%oE*S zO6~3x1-(|ilrCp0>J6&>QhMdQdePyRRg%=RY}%0fRnjGA&jpH0RZ{Qwa3gi7O4|Bt zqQk!JRnqEtipqkos-)n&ZTjCotCIG8QE;GbN|ogNSvdT!lAJ8ljtGOQq~8wip0>PC zm6Wq_mLj-4*kxBGSvqUe6_!7Wk=x4!d2s<4>hJGSVBG+p8G#dil^ zNJpQPShw8qLdxp({lu-SUPy%-(=tNlzmRI5SXtM4>I>=HE#2_wPgNW_J>rGb zui-~27Xn^L3*Px2*LQm%9k|(Vrkm>v$!7ZM_%z2Ck}kvU!xFO>QbTQd18sSwbh4-a z#22?KrSQUxHk&V0N)6)%LDQo30%KYv8mN(p|yeafp=mC}-( zonyXps+24nglRJ@Dy28>?Yc!(K9?TUFUlYN;JLKv&Xc3#i=Rs)m0xzqJoQ{!{Hp(n z1N)y#1s^CTT5NwVZ9UVfEPTy#>5s4s&x;G6OInLC#fbFhQtyeHK7UMlF5MY2egE>v z=MozaeJ;J5_2d24{hv#Z9V!BCdOep0Smcg)-~PE&zv|8-tESJTAwPCKgGx%dic<@x6)f7Vt=rzfAh zb9HEi^!jQZD;l&SI?w{*5fXlCvGUS=m&#`3t)PB#T-3LaFzSsMi^s4EEKR)dAOgg0O5PY}QGwJ)s z55uN7Ka;XAcq$szekN(nj?TGvoD%#DVNsPyVuTlZMn3`Jb!f3m*tXU zUS_?#%yKF8+mzZ8P#U$>ma8#g*yb#gt2BU!?0=jwzRRPg(rQnxW;=7p_6@ zzg!xvZMgggv0S=!U}f5fZsk(@dO0l>?aQUK*DW%BXkISeA0C)KwSKvDwP3t`f4g$2 zo=O4#%Ozd&!!yiZlu04^Gy7CLDU(w2CGS7)lu17YdyIcxTqZTm*s#O$e3>+=q{*9> z$IGO4fAmua|5zs7-CsU_`nP4$vX5QD_kB|)t*u+qNU^p|TD{@(^r1`2r0X5BXXni? zlj=EV)OF4%ljdA$+-UxkGHGhE-#p8NGD*?pjb=eqnN+)N>eyzZ%cN76-+sMsaGB(N z*0#>rz%t3Tf42p7q%x^)VR!gnCT%bHV_sIrGO4TQM-ReUl}T*^ziH&@0(RMzN$E<( z!Pi#6`gcnGJH51_^>?rSc&?YSSKpub{E1$wtDQC6@}6E=)^Aa+>bhR)y|(*^z8Ce9 z_q79qCY{ntlYFy2`=&rI6|QOhx)YuH-L}v5(j29qb&EN`kMi5C`&ciXc+uasO|o8kU2p2@y%Y4(n=NG*LnHLk zXQ@d!PexUCwddX!>^Sz5b_0q$3Q{F_m z=_PA(vkbLLFZG-@xQDAVgqNLON{1Iwn_KH~90MFzSjU(+zBA5aamVH_B!AMQ1#QjJ zan3>O#uUS~P}omEtqq?-n2gwXS0QZtnN8$){q1}XhW?*(N@aEa#wmFYVc&01lN#IP z?f*^X`Aa_JG5szpkEz{@$`?qgAuVY9_w_Q3|3Avc)z5V?6LL!wLOOi@$)Tmd$X;Z$ zjd3sy?y!&Gwi-T|uCX(wbcmz6tq`hA!Wf6gRJ|Y!cN*9>%^Pi)77t~dKc;W&jHw9X zBf@(>Hd;RE71w1NAovV;mmm^SmGocVV#oPi4R` zm?qy7#^u0#jGZy58pHkvd%8&&)9?jG_9CNg42NlOXPhslYwV0kgY}1VM3qSxDVc0K;IKyd=iuz!NKt0tk=MQG?=L)}Wq65DY$e8)Tw+}Q5!4nyNGASk8RsPx)oRi=k_u%L0 z898n|e5Vo^^Y`!{GR!s1-xoFoZB4%q&@ZNjUyvM52Yy%&7B|Qr!)4YCC&$gM;ir+~ zt^hxrnPzb{VHh{JX1Fj6m{-$JPA|WP-!M77f*R=!lfxB(UmkpU(*u4aDkU6_&k}|3 z2HQhsS9v)-oD*4lFu%!(aVhb*wgRskXR5H;f0-8(E-KX|SN{Mt0@|E*WtKs*7 zoOimh-!K*~vxXlFmkoaDumr**jg?VZ39R7k3Of-dwO%NM3Q&(|7G_SB0^Xw!%=T*+ z46_3KXx}skm_!;g0Zs=F?N+Gm_1uYb}2l@$0GeHB3N<3Kda{ny?Z<(Q)@VHV{ zv%E-IjmH|Sul0za-=%_#RF1$po`{)T=CMQi`m#L)3H*ywSeglzUCcZcw&sFuH%q~` zi=|Fst`N*;!P+xaKhW;hLiZ?3!C{!S;Ly!VaOh&EQ`EB->J76M>UFab>UF83x7XQe zTUeM07Wb?LPiT}Z_!L4rw}AX>GStB&49nYE&btM?K-)gbO0XDaDHOq%xF5pD=>bcp zCY!^899XPj5>DX&st%;Q*4C#5Vt2|?$%;;FZAArqc{CEfkcr1Sx)ZQ{!HB61HFf1SgnA6I)L~pG zpis(!~7@KRu>DEJJs5}cs! zPF<8bMSTlshy6l*MZRE(VdQp&Htl91IMjpvj|~A1RUx+>w4YJivG5KMzK-DVMRj;H zGjpL<1HsZvtB})BG_e<&L@9+P!yJSr5VlE@vmR<%RIfm(v#G5RU@@1W{GY;6w*)?B zz6x0tR<#5xzgikIPw-RJ63if7g^tR^%Z4iF0r?EGgFI}7CS9uYF!XWW*G;sMhmFuQ z>X^{9+fkvZ;-FBc1bRBOhvxrj@BHKAs>=L-CT-KnkG9j_Nt2c-zYI_?#^?q_oi=Tm zVu5Lzwh=3xw3&7SX);VEkg^B?3kJR{WV_>E5wQ6Kp z7zNp^qK;ZLuC?y_x%W&-OSG;3_yP@5)WA`_VXg{{}8xF#GiQ$(8qgCvk(4x$^j7>BW?{ zxUM)`m@#W>Sy`-B28!@kp<0PQR^pG9Ri)YGnWZVqv@$Ivg{q{fSe4KpCG+G7U)cilm92W7vK=bUm~&|{b7_%URA)q(+l;#) zKKvYU_g81cU5a?HtS(fm@$+hYv>G3+u3DKb%ar;`FO%`KkXUC-QJ?AK>2!UT$apUz zW{Ih4;#Oi-`X`XbHGuyJc7CBcpSGV*8_%ch=hOD{tGGQY^E>-z>W7E7;dAtIEl{^o!595(wqH_qbZseeSNQ`(O+wf8~x|0T+mWm%r5mLImwc;4NA&e%q$^Uk#} zXEMeYTGhhpg=(RH9*=hvq~`kajcW@{Q%`+TsXu*Gsj_wE-0>6aIiGst^-OhI^%qK2 zqg)@DwJNuI7x+v7gwkdt>e{tt?XKRo|-4v+Py2EPhNErl!xEcHTskULY~PNc!iD9H+;E z%!Pig|7VTSTz_mDB(Blb+835m<^k$a>Nn#$IOJXE@I!1gfO=j}b6Jezs z(P@4%&RfKMTc{TJxlZQKQu)=?$w!RIcndHdjqy64Ser*%h`B>HUn&2nqY%6BP`*#v z;-KHoz6QMdMV@Cx+j4a;=lT~|&it?pe=arsZCRw2W;mCO#i}`#mZhwHh|8si&1;s~ zUkm9k8~wF_w$E3khjabN>n2`PFT*E?mS)ZVnpXa?H~Pz*JLjvVX3vPp(o1E1QlZK%_8H?jOIf9t7pSuOp1iW^ zhx5uT59XO3GuH`@CG39}W1{|3dFNGsGVeUg!+FNl$e4M}JZ{Nz-W)a0@}SDk%ueT} z%yb*ql+_i5s^Y|b${x6%>+>g+t-f1Xs~=F6ud<#;2Y+EvtEt2E*OsC=s;Fs}Dk5f! zh}qJ`tT~DCTuhp4`$ARd&-ayHKJPu}1(x8e#k7BsTKw+$hYPdTOn!QHDz^@DQNDS- zT*#QPsRhT11D8`2_E3Yo4+m zuT}O_{Lge&AL(D3zMKBqvN&HY<{G=0b;RPT1wOO>T$_1qtik8!srglfm6nox))f!i-IhC^s6scEQ>LhdcBHqIm5r^J6YVI?n$vQp{mRVI9 zYnd|qT!vrEs+PD4j5)V}_+m{~Rdjls7GA~L2Fdw_1=HRW=j!;+q}ybEB9G8`4@G~= zIJd~0fe$U>%iYj8ex4Eg7XFcx)j!)e%PsF#*eCT{Bo_07W+nF_&fercn2YpI6r5qd zODC_z2r>is6Wt)DUk{QmE+y5M8$(iIR(HO5&XdI1l ze^Blb{t)}U+~c7K)t*~-?P=TZYupoPZfo7Ox49+Y@n6x@(xR?yysdFpn}6S~Ms|8V zyZmi?Zt(>6-@2BBb~M_=38ylao&(-`nv-xz4V&10rk{$T~S zdji)rx4OIsZrHmQyVAHvMFWhj;v{Y1p5?hVxpyVoziHZjYrei%)1kRt^JdM1n!TEF z&2i0FHK#O}NIlq2{NChy_3LXjcWU0Od9UUn&9LSdHIHfjNORHe%yL(0uFGr zW|QV^nh$DzRx_!2RP&hTq~?@n{wedki!>`Vt2G^(n>F2oXA<};edG+)&;I*&hfv$u43{0Cef|K2wL{>Iil zp4zsKRtAPzop+5VP}|}6ds+i7MuexmeN&UVXj*E$m(Xt_QTaO3g`Q25iK`MDb@Et&SZ#)6wX{#rk7-qYB! z)nm9$sb^Z#P{ z#in}O8(TU&>WI9ea^|bU@@k{sL%Kz1@>6UGVZ8TloW!Xr)*0!{@d(=V0-q_54;Fgk%^FC^%UC#iJq#f!Iafkn!n`+yd zJg&xl9`z*)tyo*@;=PX4{DuwJiTzbct#wJTs+SDAZ9l^oFSdHts?1p_JJhdcUDweP zXkK?9;Hhuh(cI*z^)~v|qWqj$-*$SxS}W(-(0W_5zpZsY1Iuh=JFWb@(UUXn8k@Db z_PR#jX@C4*DMR;@c!p1|EJjdEGuL(O-Rtqw^97Pi``q-Zv~A9`RJzZ@7j`AZEW4hb zr6xBTKHkc#La!TkoyYY4P8lE5Vq&`-D`%C=jI(OlFRxu~%`$LEQV%GjhjbWyL)9AB zbhAftGvy!o|Dy+TYt65J)P8L5@!$WxD*V#rpXSGZWtAXn&Uf0iS9otKTQ0x(%FL6q zbNl5tg5>k$b8Qy;x%T{F>3NiEu5SLYa?USlHhVt@|(6@ogR^@|5KCHb=Sf;J)|3y{mO% z|4XJ3>N9!I=Q1b7a@^HrFh>5TBhN?d=I%HBy@&gQvN<%L_)q3vtMh+>{Qnq!CA>oY zFgzLlh*{XQzo*N;D`Xt8h0m7q#HzlR!{-A!2_YdXWdMnrEMs>OWRbD}T zMQ``b@)ld(S=%hV<=QR%BwKDQg=n|3w&{f!59wR&#f z)#7R07w|gT15FNxQs>MsZ@_bzD0-%Rp4?54x5&pc6|l_XV&b@j5Fm6 z@D^=XGw)2L0dM!s#$5(^u)A z97i?S!fzlab{1AG<}+)`bijMH-2;zlJIH5%?_0|AY~**qHOshHk6j5rhK%PU;a_We z6#hWlw&h%JkXz0LZG6}edF1nf37szdEt2z|gf%>$C;1)l7Hzk{qe#jW9^-kvUdlNR8+dl^1#IC1 ztN1)~(xM)M|A3@j&%xJ_lz##S&m)~Y$KlVAl$nK>*iCy4bZcAKq3uo>)VA;mZI8i{ z)jZ2anL;O$GKD*}E$l09NXGcp@D^>iz|SLT*AdwH0p_3|5hw6Y z77aDn!mq8Tf3Z)%eH+YJ^}=EF3gyS)DU_8uY~=Y0B<-q!EvSKG18@RKoC}v;W46}; zpK+S$&%%=^N`B!}n|RIu`#IQsE%n0|9z#<8arn*;nf6Xtb{%7xJmv5Jl00|9yc?Jw zNuLdUNc=3kjr)PKe@uJflW2-ON8xvn_+$e9Lfa?cnk{B}HEcsybF2^EU#HZC*xm3M zRF2KRuT&QxDd$31t!?40$fz4Us%_!&dNcnT=+L&%rETGT+U|zE+7^x>H|G`3-)5F$ zgKbF8<%0?I>}!k*c<&B;O8$fJ(gxH2Yv8xFUGib2zJ!jF|0s0dXxhS`qbTVo;LRVQ z9@s5#?oKmqt?LVqK%iXDKNJ*F+>7PjH50Gzwew5{-m&CEsQ zpM;0`8FV*gR_y0~HIgwBg&%LDA4xw5FZJ=cA^F$9$8R;uKLW4voBe(@oYig~I~(Q) zc&40Vt?;MlAa)i$+QD3m-3xnfGvi8l!R^d7Qa=3V$CwW#Kiq#8W8^2)2fmETv0s6! z@5ayA7s6)bz;1zGLrvJv!B2NF4`M$CkD}Sw!pHBSZrDd)*}Z1{%i%|n^r;(Gf81<~ z@Sl*RzW`$g&2-`Sk;LIEF#kT&Zh-wr#zG8^>vZ9ywuMLUH)C6P(I+S$pI1O9lKDi~ zjih|x+-|cDR=8i=KCvG#&*g*nJjnTI*FhNmKOBo4h0ce}I27*tGqdmRgx^Kdt}J|z zXChxA|3k3l5w1x;#h);aDzSwHJ@glOisAi8;-njX3mN`_>;BxV|9W^2+R3p8;l4v= zelMK;7py->7j8n&VsD0bpf2pkVE(6QKeiRRkd)s7Bic^ERex!w+u=u%^sgH}uG5de zthR+ad(Hg9Z)$rI-t<@W4d-&hAALr#0I{go;Ol?Q8UlL?-u*fJfZYWj4s-3q?uB(x z)-l-i@SCUy`#5asGkq@HFu?VKbSKOoWXxh)g{T)>Xdg2BT6nd#g|El)CwZpe{shsTTN6h*kglItsZ#^LcVnsF{%@Fn`5bQ^Rbsi!cF zq@Kd!qh@*&970ll90r~;(}fAKDQ6Tuk7S&_0B`)V*~TWQp2lC~5iUWJ=e1{)TKTtT znRd8E+x77H-!Wg4UzqxPvmD`}e_$+={z>?oubO>&5;~vbT28v~rmvgxryD+&W_?1s zaQQLf|BkmT76$51_a)*t8D%Ws?U^9o$?9dmrG zhp!>YFTC-)_>lZN;nPTb`z-93V9qDK6OJMAr*QrEsHc<*0q_&__IHxD);YU5I@G7QJrzq!@0{wi7;s z44=UEUzu}$0G9sR^ldq8K|3i&xMYgg*iCTNZ)l^uFMy9C@p&)&wYH~V?Z4q$=7;t0 zV`39;UGP`G<-C+L1&2?X$Hw7`Q)bLee@0TCo#e|sYLfiIP3R={X87O|;vM@TxOgdZ zB;_xG6-eqMEMA6Am^^`9jxF53oHaFheDED5)Lrt!SF|nsX(`uy@@L`B6yIxRS7pEW8A^QcM|Wky%^qs#BT@Tx+= zjekfVh4%L_KCp!jB=e8(J80+Y_!+AA;wx+i+=t{nxEFR@Nq*8hVLy_(#o$+v)Mp&7 zzlwU1N4N(`dK2tGl0OYsz7Lx`^6cn-u}SyAuc0*Q8F&g!V5_UCJCbsQ`R^w_NVmc= zB*&J+G4ulI!izq@+=E>KKaHlaN8u@C{T1zU;5RhR*m1+XD2?rdpFqd)iBP^DD7H|( z<0rOIzPr~zeqr8PVjo*5-}BncHA^Vp!I1ny`A(PE!k4uzl<${Fx=_C7A+}Jy=ODIF zzBeGYP@c6HTPV-bi!GGr<;51tGrnRQ)V4fdEYBoMx=^026w%? z3(-zggM7%1Dv<-ZkR5fRKW@`J;9{&ZwN!>{!j7;r>xjBBfdyy#Mjr^*VWhE*VEVA7wn7mjrNW8rTWJE(tQ(s{D5L#wr{FW z^;`RG{U!bO{)&EnJi6cA@9p>XclLMnclQVTWBsH3WBsZA@&0uGME_)ewtuSMHees9 z9B>S{2HXR_fv$m`f#AUCKx!a8kQvAhsF*Egk5$GTF;~nT^ToPiJ+WYHG?t2`W0_bs zrUq?;_QA?Q$DnJ_J?IVdiXDB!{ zI+Pkp4`qh3Lu%MIY#**1b_~0Q-NU}&uHl~H;PB{hYB)Wd8O{!?xGipvSH>N2SKJ-< z#k=A?@nC#3o{FdAnRqs?61IdrQJHWgTnTr=m*`6LB!Y?2L@JR^WD?ngO4^dj&76z- zC!(3?WYnxt1$C;SR!-{GFyy6Xoz$(H+VxVuv7zyy3F`s z!Kt7MSwpr^Nyr|m2vvq^LXMC#lOID2G2Y3 zeFNV2;{Q&1pqoDEr59rK!x%j=PG3yW8o@8$l2X=8y!w)3`6$3Q`&Vhyj??C53_dqZ09HWgBv~Oy_O3Nx} zm6H~EX-zjRiP4I2S}+-#idm_91@(4PXD{{brmivSIZhoXsh^d)RnRw1qhC7dlis12 z?vY7)!#Z3tTrpfT>>O?w_6~OrcMtat$A-s-$A>3|Cx@qot?`n0MZ6~Nj5ox+@y>X6 zyf+?;kHyF16Yryd#|>-6Optv5~Qn@sWv<$&slM{M5sB z8d!rR!HQr_&>3t9dV`(8ZlWg^91D&ICxVm2kd^qUAa}7`NW`>A`$HL>`2|nka3R@#3%oH`u5)I4{ossTHZzL8Oi;PDm zB9oD+h&5Ugt%%k{ozaG?w+ryR2RIad_nW`(?!#p(_PKDFqOgI}>5gRjBC9{=_naUUG ziu6Q+k-BDk(E7}tcMn|KmXqxeujjBFdpS`cL&(Y`V zbNBg};d+?eMw!{t%xYO?G#j&7Wxu1}#cbwdChK7)8)X(t_h*>F)PU`O{S0PjWI7m? zZboDmqcNBqO{S9RWG0zSsuA0WeWY^4G2$9=kN8HqMtVkqBhr~m=++c*>m=Pfk{#g@ zUS%`l(ne(3gYx?$^}JS$WiR76#u%O&FlQ#2jeIlCM7eorin(8AA{X;!jM;o@$UZEE K{c(FM5BxiyK+!J% literal 0 HcmV?d00001 diff --git a/store/src/main/resources/native/libcq_compaction_filter.dylib b/store/src/main/resources/native/libcq_compaction_filter.dylib new file mode 100755 index 0000000000000000000000000000000000000000..58d6e6796a775822e1592732a737790e60adff08 GIT binary patch literal 41776 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 T8vXNY?|Aj{{lEGvF%16$v+Khh literal 0 HcmV?d00001 diff --git a/store/src/main/resources/native/libcq_compaction_filter.so b/store/src/main/resources/native/libcq_compaction_filter.so new file mode 100755 index 0000000000000000000000000000000000000000..46dabe1880aac739574ed21153ca81b184ebaeb5 GIT binary patch literal 23848 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 g{~uq&X%E($)L>JGtuag6seC`^ literal 0 HcmV?d00001 diff --git a/store/src/test/java/org/apache/rocketmq/store/rocksdb/CqCompactionFilterJniTest.java b/store/src/test/java/org/apache/rocketmq/store/rocksdb/CqCompactionFilterJniTest.java new file mode 100644 index 00000000000..eead66b0741 --- /dev/null +++ b/store/src/test/java/org/apache/rocketmq/store/rocksdb/CqCompactionFilterJniTest.java @@ -0,0 +1,184 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.store.rocksdb; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.UUID; +import org.apache.rocketmq.store.MessageStore; +import org.apache.rocketmq.store.config.MessageStoreConfig; +import org.junit.After; +import org.junit.Assert; +import org.junit.Assume; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.rocksdb.ColumnFamilyOptions; +import org.rocksdb.WriteBatch; + +public class CqCompactionFilterJniTest { + + private static final int TOPIC_COUNT = 100; + private static final int BATCH_SIZE = 100_000; + private static final int MSG_SIZE = 1000; + + private static final byte CTRL_1 = '\u0001'; + private ConsumeQueueRocksDBStorage storage; + + @Before + public void setUp() throws Exception { + Assume.assumeTrue("CqCompactionFilterJni native library must be loaded", CqCompactionFilterJni.isLoaded()); + String dbPath = Files.createTempDirectory("rocksdb-cq-compaction-" + UUID.randomUUID()).toString(); + MessageStore mockStore = Mockito.mock(MessageStore.class); + Mockito.when(mockStore.getMinPhyOffset()).thenReturn(0L); + Mockito.when(mockStore.getMessageStoreConfig()).thenReturn(new MessageStoreConfig()); + storage = new ConsumeQueueRocksDBStorage(mockStore, dbPath); + } + + @After + public void tearDown() { + if (storage != null) { + storage.shutdown(); + storage.destroy(); + } + } + + @Test + public void testCreateAndSetFilter() { + Assert.assertTrue("Native library should be loaded", CqCompactionFilterJni.isLoaded()); + + long ptr = CqCompactionFilterJni.createNativeFilter0(); + Assert.assertTrue("Native filter pointer should be non-zero", ptr != 0); + + CqCompactionFilterJni.setMinPhyOffset0(ptr, 1000); + CqCompactionFilterJni.setMinPhyOffset0(ptr, Long.MAX_VALUE); + + try (ColumnFamilyOptions options = new ColumnFamilyOptions()) { + CqCompactionFilterJni.setNativeFilter(options, ptr); + } + } + + @Test + public void testCompactionFilter_small() throws Exception { + runCompactionTest(1_000_000); + } + + @Test + public void testCompactionFilter_large() throws Exception { + runCompactionTest(10_000_000); + } + + private void runCompactionTest(int totalEntries) throws Exception { + long start = System.currentTimeMillis(); + boolean result = storage.start(); + if (!result) { + System.err.println("storage.start() returned false. Check ERROR logs above for details."); + } + Assert.assertTrue("ConsumeQueueRocksDBStorage failed to start", result); + log("Startup took %d ms", System.currentTimeMillis() - start); + + // Phase 1: Write entries + start = System.currentTimeMillis(); + writeEntries(totalEntries); + long writeTime = System.currentTimeMillis() - start; + log("Wrote %d entries in %d ms (%.0f entries/sec)", totalEntries, writeTime, totalEntries * 1000.0 / writeTime); + + // Phase 2: Count entries before compaction + start = System.currentTimeMillis(); + long countBefore = storage.countEntries(); + long countTime = System.currentTimeMillis() - start; + log("Count before compaction: %d (took %d ms)", countBefore, countTime); + Assert.assertEquals("Entry count should match total written", totalEntries, countBefore); + + // Flush memtables to SST files so compaction has something to process + start = System.currentTimeMillis(); + storage.flushAll(); + log("Flush took %d ms", System.currentTimeMillis() - start); + + // Phase 3: Set minPhyOffset at midpoint and trigger compaction + long minPhyOffset = (long) (totalEntries / 2.0) * MSG_SIZE; + start = System.currentTimeMillis(); + storage.triggerCompactionSync(minPhyOffset); + long compactTime = System.currentTimeMillis() - start; + log("Compaction with minPhyOffset=%d took %d ms", minPhyOffset, compactTime); + + // Phase 4: Count entries after compaction + start = System.currentTimeMillis(); + long countAfter = storage.countEntries(); + countTime = System.currentTimeMillis() - start; + log("Count after compaction: %d (took %d ms)", countAfter, countTime); + + // Verify: approximately half the entries should remain + long expectedSurvivors = totalEntries - totalEntries / 2; + long tolerance = Math.max(expectedSurvivors / 100, 100); + Assert.assertTrue( + "Expected ~" + expectedSurvivors + " entries after compaction, but got " + countAfter, + countAfter >= expectedSurvivors - tolerance && countAfter <= expectedSurvivors + tolerance + ); + + log("Test passed: %d -> %d entries (expected ~%d)", totalEntries, countAfter, expectedSurvivors); + } + + private void writeEntries(int totalEntries) throws Exception { + int entriesPerTopic = totalEntries / TOPIC_COUNT; + + for (int t = 0; t < TOPIC_COUNT; t++) { + String topic = "test-topic-" + t; + byte[] topicBytes = topic.getBytes(StandardCharsets.UTF_8); + int queueId = 0; + + try (WriteBatch batch = new WriteBatch()) { + for (int i = 0; i < entriesPerTopic; i++) { + int globalIndex = t * entriesPerTopic + i; + + // Key: [topic_len:4][CTRL_1][topic][CTRL_1][queue_id:4][CTRL_1][cq_offset:8] + int keyLen = Integer.BYTES + 1 + topicBytes.length + 1 + Integer.BYTES + 1 + Long.BYTES; + ByteBuffer keyBB = ByteBuffer.allocate(keyLen); + keyBB.putInt(topicBytes.length) + .put(CTRL_1) + .put(topicBytes) + .put(CTRL_1) + .putInt(queueId) + .put(CTRL_1) + .putLong(i); + + // Value: [phy_offset:8][msg_size:4][tags_code:8][store_timestamp:8] (28 bytes) + long phyOffset = (long) globalIndex * MSG_SIZE; + ByteBuffer valueBB = ByteBuffer.allocate(28); + valueBB.putLong(phyOffset) + .putInt(MSG_SIZE) + .putLong(0) + .putLong(System.currentTimeMillis()); + + batch.put(storage.getDefaultCFHandle(), keyBB.array(), valueBB.array()); + + if ((i + 1) % BATCH_SIZE == 0) { + storage.batchPut(batch); + } + } + if (entriesPerTopic % BATCH_SIZE != 0) { + storage.batchPut(batch); + } + } + } + } + + private void log(String format, Object... args) { + System.out.printf("[CqCompactionFilterJniTest] " + format + "%n", args); + } +} From 8d5f975bd79056ac925851de4f13d7ee7fac597c Mon Sep 17 00:00:00 2001 From: lizhimins <707364882@qq.com> Date: Tue, 19 May 2026 16:37:54 +0800 Subject: [PATCH 2/4] [ISSUE #10334] Add pre-built aarch64 native compaction filter shim for Linux The original commit only bundled libcq_compaction_filter.so for x86_64. Add libcq_compaction_filter_aarch64.so and update CqCompactionFilterJni.java to select the correct native library based on os.arch on Linux. --- docs/en/Native_RocksDB.md | 4 ++-- .../store/rocksdb/CqCompactionFilterJni.java | 10 +++++++--- .../native/libcq_compaction_filter_aarch64.so | Bin 0 -> 74824 bytes 3 files changed, 9 insertions(+), 5 deletions(-) create mode 100755 store/src/main/resources/native/libcq_compaction_filter_aarch64.so diff --git a/docs/en/Native_RocksDB.md b/docs/en/Native_RocksDB.md index 2eec489a038..f0d43a8137a 100644 --- a/docs/en/Native_RocksDB.md +++ b/docs/en/Native_RocksDB.md @@ -272,10 +272,10 @@ mvn test -pl store -Dtest=CqCompactionFilterJniTest -Djacoco.skip=true | Platform | Library name | Architecture | Status | |----------|-------------|--------------|--------| | Linux (glibc) | `libcq_compaction_filter.so` | x86_64 | Pre-built | -| Linux (glibc) | `libcq_compaction_filter.so` | aarch64 | Requires rebuild | +| Linux (glibc) | `libcq_compaction_filter_aarch64.so` | aarch64 | Pre-built | | macOS | `libcq_compaction_filter.dylib` | arm64 | Pre-built | | macOS | `libcq_compaction_filter.dylib` | x86_64 | Requires rebuild | -| Windows | `cq_compaction_filter.dll` | x86_64 | Requires MSVC rebuild | +| Windows | `cq_compaction_filter.dll` | x86_64 | Pre-built | ## Limitations 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 d78579f37c1..69d74e3364b 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 @@ -41,8 +41,8 @@ public class CqCompactionFilterJni { static { String os = System.getProperty("os.name").toLowerCase(); + String arch = System.getProperty("os.arch"); if (os.contains("mac") || os.contains("darwin") || os.contains("osx")) { - String arch = System.getProperty("os.arch"); SHIM_LIB_NAME = "libcq_compaction_filter.dylib"; SHIM_LIB_EXTENSION = ".dylib"; ROCKSDB_JNI_LIB_NAME = arch.contains("aarch") || arch.contains("arm") @@ -53,9 +53,13 @@ public class CqCompactionFilterJni { SHIM_LIB_EXTENSION = ".dll"; ROCKSDB_JNI_LIB_NAME = "librocksdbjni-win64.dll"; } else { - SHIM_LIB_NAME = "libcq_compaction_filter.so"; + 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 = "librocksdbjni-linux64.so"; + ROCKSDB_JNI_LIB_NAME = arch.contains("aarch") || arch.contains("arm") + ? "librocksdbjni-linux-aarch64.so" + : "librocksdbjni-linux64.so"; } } diff --git a/store/src/main/resources/native/libcq_compaction_filter_aarch64.so b/store/src/main/resources/native/libcq_compaction_filter_aarch64.so new file mode 100755 index 0000000000000000000000000000000000000000..b77869f063d20dd56757aa7d97136d7875c78d7a GIT binary patch literal 74824 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 BB^Lky literal 0 HcmV?d00001 From e65fbf61e51dee734f065678a8d96469018e4fd9 Mon Sep 17 00:00:00 2001 From: "terrance.lzm" Date: Tue, 19 May 2026 20:22:10 +0800 Subject: [PATCH 3/4] [ISSUE #10334] Skip JaCoCo on macOS/Windows CI to avoid native library crash The JaCoCo agent conflicts with dynamically loaded native libraries (libcq_compaction_filter) during JVM shutdown, causing "forked VM terminated without properly saying goodbye" on macOS. Coverage is already collected by the dedicated coverage.yml workflow on Linux. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/maven.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/maven.yaml b/.github/workflows/maven.yaml index 4eacd65b846..28c2d34f5d4 100644 --- a/.github/workflows/maven.yaml +++ b/.github/workflows/maven.yaml @@ -27,7 +27,7 @@ jobs: distribution: "corretto" cache: "maven" - name: Build with Maven - run: mvn -B package --file pom.xml + run: mvn -B package --file pom.xml ${{ matrix.os != 'ubuntu-latest' && '-Djacoco.skip=true' || '' }} - name: Upload Auth JVM crash logs if: failure() From 33d352809a0c23d61c8dc37258ec192f76eb308f Mon Sep 17 00:00:00 2001 From: "terrance.lzm" Date: Tue, 19 May 2026 20:28:17 +0800 Subject: [PATCH 4/4] [ISSUE #10334] Fix CI workflow: use shell:bash for cross-platform JaCoCo skip The previous inline expression was split incorrectly on Windows, causing Maven to receive ".skip=true" as a separate argument. Use explicit bash shell with a conditional variable instead. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/maven.yaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/maven.yaml b/.github/workflows/maven.yaml index 28c2d34f5d4..aaf165420c3 100644 --- a/.github/workflows/maven.yaml +++ b/.github/workflows/maven.yaml @@ -27,7 +27,13 @@ jobs: distribution: "corretto" cache: "maven" - name: Build with Maven - run: mvn -B package --file pom.xml ${{ matrix.os != 'ubuntu-latest' && '-Djacoco.skip=true' || '' }} + shell: bash + run: | + JACOCO_FLAG="" + if [ "${{ matrix.os }}" != "ubuntu-latest" ]; then + JACOCO_FLAG="-Djacoco.skip=true" + fi + mvn -B package --file pom.xml $JACOCO_FLAG - name: Upload Auth JVM crash logs if: failure()