diff --git a/ZENOH_FLAT_TRANSITION.md b/ZENOH_FLAT_TRANSITION.md index 492d969a..f3b79d31 100644 --- a/ZENOH_FLAT_TRANSITION.md +++ b/ZENOH_FLAT_TRANSITION.md @@ -27,6 +27,7 @@ zenoh (Rust) | --- | --- | --- | | [#481](https://github.com/eclipse-zenoh/zenoh-java/pull/481) | Use receiver-style zenoh-flat-jni bindings (generated Session/Query/Publisher methods, split key-expr overloads, de-prefixed callback names); +61–83% subscriber throughput | merged | | [#484](https://github.com/eclipse-zenoh/zenoh-java/pull/484) | `Encoding`/`ZenohId.toString` become pure JVM values (fixes a per-message native leak); the conversion logic lives in the **shared tier** (`EncodingCodec`/`zidString` in zenoh-flat-jni, correspondence-tested against native, reusable by zenoh-kotlin) with only a thin facade here; publisher **default encoding set natively at declare** (plain puts cross no encoding data). Pairs with [zenoh-flat-jni#4](https://github.com/ZettaScaleLabs/zenoh-flat-jni/pull/4), [zenoh-flat#3](https://github.com/ZettaScaleLabs/zenoh-flat/pull/3) (merged), [prebindgen#80](https://github.com/milyin/prebindgen/pull/80) (merged) | in progress | +| [#491](https://github.com/eclipse-zenoh/zenoh-java/pull/491) | `Session.undeclare` detaches the key-expr handle even when the native undeclare fails (the generated wrapper consumes it either way); found on the zenoh-kotlin port ([zenoh-kotlin#668](https://github.com/eclipse-zenoh/zenoh-kotlin/pull/668)) | open | Companion PRs in the upstream repos are coordinated per constituent PR (e.g. [ZettaScaleLabs/zenoh-flat-jni#3](https://github.com/ZettaScaleLabs/zenoh-flat-jni/pull/3) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt index 1101ece5..b28291c2 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt @@ -421,10 +421,15 @@ class Session private constructor(private val config: Config) : AutoCloseable { if (handle == null || handle.isClosed()) { throw ZError("Attempting to undeclare a non declared key expression.") } - run { + try { zSession.undeclareKeyexpr(handle, throwZError) + } finally { + // The generated wrapper consumes the handle even when the native + // undeclare fails (the Rust side takes it by value) — detach it + // either way, degrading the KeyExpr to its string form instead of + // leaving a dead handle to be selected by later operations. + keyExpr.handle = null } - keyExpr.handle = null } /** diff --git a/zenoh-java/src/jvmTest/kotlin/io/zenoh/KeyExprHandleTest.kt b/zenoh-java/src/jvmTest/kotlin/io/zenoh/KeyExprHandleTest.kt index afae4ab3..e9a3c2e0 100644 --- a/zenoh-java/src/jvmTest/kotlin/io/zenoh/KeyExprHandleTest.kt +++ b/zenoh-java/src/jvmTest/kotlin/io/zenoh/KeyExprHandleTest.kt @@ -15,6 +15,7 @@ package io.zenoh import io.zenoh.bytes.ZBytes +import io.zenoh.exceptions.ZError import io.zenoh.keyexpr.KeyExpr import io.zenoh.sample.Sample import org.junit.Assert.assertEquals @@ -62,6 +63,30 @@ class KeyExprHandleTest { session.close() } + @Test + fun failedUndeclarationStillDetachesTheConsumedHandle() { + // Undeclaring through the WRONG session makes the native undeclare + // fail — and the generated wrapper consumes the handle even then. The + // KeyExpr must degrade to its string form (handle detached), not keep + // selecting a dead handle. + val session1 = Zenoh.open(Config.loadDefault()) + val session2 = Zenoh.open(Config.loadDefault()) + val declared = session1.declareKeyExpr("example/testing/keyexpr/wrongsession") + var failed = false + try { + session2.undeclare(declared) + } catch (e: ZError) { + failed = true + } + assertTrue(failed) + assertNull(declared.handle) + // String-backed operation keeps working after the failed undeclare. + assertTrue(declared.intersects(KeyExpr.tryFrom("example/testing/**"))) + session1.put(declared, ZBytes.from("test")) + session1.close() + session2.close() + } + @Test fun receivedKeyExprIsStringBackedAndReusable() { val session = Zenoh.open(Config.loadDefault())