diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1524d036..b20aac79 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: uses: actions/checkout@v4 with: repository: ZettaScaleLabs/zenoh-flat-jni - ref: e4a8ed7a354f6f44ab1e44f70771298dc82c9c67 + ref: dbb1f8c6509e333f49d1c03ecb84dcf07b7af7b8 path: zenoh-flat-jni - name: Check out zenoh-flat diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/Config.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/Config.kt index d18977d6..d45049d7 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Config.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Config.kt @@ -34,6 +34,9 @@ import java.nio.file.Path * * A default configuration can be loaded using [Config.loadDefault]. * + * The native configuration a Config wraps is consumed by [Zenoh.open] (or [Zenoh.scout]); a Config that is + * never used is released by the garbage-collection backstop. + * * Visit the [default configuration](https://github.com/eclipse-zenoh/zenoh/blob/main/DEFAULT_CONFIG.json5) for more * information on the Zenoh config parameters. */ diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt index 693af037..1101ece5 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt @@ -58,6 +58,10 @@ import java.util.concurrent.LinkedBlockingDeque * Sessions are open upon creation and can be closed manually by calling [close]. Alternatively, the session will be * automatically closed when used with Java's try-with-resources statement or its Kotlin counterpart, [use]. * + * Native resources held by a session (and by its declarations) that is simply forgotten are released by a + * garbage-collection backstop once the objects become unreachable; still, explicit [close] is recommended for + * deterministic release. + * * For optimal performance and adherence to good practices, it is recommended to have only one running session, which * is sufficient for most use cases. You should _never_ construct one session per publisher/subscriber, as this will * significantly increase the size of your Zenoh network, while preventing potential locality-based optimizations. @@ -96,7 +100,8 @@ class Session private constructor(private val config: Config) : AutoCloseable { * Closing the session invalidates any attempt to perform a declaration or to perform an operation such as Put or Delete. * Attempting to do so will result in a failure. * - * However, any session declaration that was still alive and bound to the session previous to closing it, will still be alive. + * Every declaration still bound to the session (subscribers, queryables, publishers, queriers, …) is undeclared + * as part of closing it. */ override fun close() { strongDeclarations.removeIf { diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/ZBytes.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/ZBytes.kt index 9b91cb24..4d9fab88 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/ZBytes.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/ZBytes.kt @@ -32,6 +32,20 @@ import io.zenoh.jni.bytes.ZBytes as JniZBytes * encouraged to use any data format of their choice like JSON, protobuf, * flatbuffers, etc. * + * # Native memory lifecycle + * + * A ZBytes *created* from user data ([from]) is a plain value — it never + * holds native memory. A *received* ZBytes (a sample's payload or + * attachment, a query payload, a reply) wraps a native buffer that is + * freed automatically on the first [bytes] access (the bytes are copied + * out lazily, once). A received ZBytes whose content is **never read** + * keeps its native buffer allocated: unlike every other handle-owning + * class in this SDK, ZBytes is deliberately NOT covered by the + * garbage-collection backstop — payloads are the per-message hot path, + * and registering a GC cleaner per message measured −23% throughput at + * small payload sizes. In callback-based subscribers/queryables, access + * (or discard) payloads and attachments you care about; unread ones on + * dropped samples are the one place native memory can be retained. */ class ZBytes private constructor( private var eager: ByteArray?, diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt index 999da47d..f8846b05 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt @@ -54,7 +54,9 @@ import io.zenoh.query.Selector * A [KeyExpr] is its validated string. A native instance exists ONLY behind a * [Session.declareKeyExpr] result — the single case where zenoh attaches a wire * declaration (a compact id replacing the string on the wire) worth carrying; - * such an instance should be [close]d (or `use`d) when no longer needed. + * such an instance should be [close]d (or `use`d) when no longer needed — + * the declaring session keeps a strong reference to it and undeclares it when + * the session closes, with a garbage-collection backstop after that. * Every other [KeyExpr] — constructed via [tryFrom]/[autocanonize] or received * with a sample — is a plain value: nothing to close, no native resource. */ diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/LivelinessToken.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/LivelinessToken.kt index 8ce31f12..2945208d 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/LivelinessToken.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/LivelinessToken.kt @@ -26,7 +26,8 @@ import io.zenoh.session.SessionDeclaration * that declared the token has Zenoh connectivity with the Zenoh application * that monitors it. * - * Liveliness tokens are automatically undeclared when dropped. + * A token whose last reference is dropped is undeclared by the garbage-collection backstop + * (non-deterministic — call [undeclare] or [close] to withdraw the liveliness promptly). */ class LivelinessToken internal constructor(private var token: io.zenoh.jni.liveliness.LivelinessToken?): SessionDeclaration, AutoCloseable { diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Publisher.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Publisher.kt index 4aefe6fe..c1400660 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Publisher.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Publisher.kt @@ -35,7 +35,9 @@ import kotlin.Throws * A Zenoh Publisher. * * A publisher is automatically dropped when using it with the 'try-with-resources' statement (i.e. 'use' in Kotlin). - * The session from which it was declared will also keep a reference to it and undeclare it once the session is closed. + * The session keeps only a *weak* reference to it: a publisher whose last reference is dropped is undeclared by the + * garbage-collection backstop (non-deterministic — call [close] for prompt effect), and closing the session + * undeclares any publisher still reachable. * * In order to declare a publisher, [Session.declarePublisher] must be called. * diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Subscriber.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Subscriber.kt index 5dc69e16..bc536d1c 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Subscriber.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Subscriber.kt @@ -23,6 +23,10 @@ import io.zenoh.session.SessionDeclaration * * Its main purpose is to keep the subscription active as long as it exists. * + * The declaring session holds a *strong* reference to it: dropping your own reference does NOT stop the + * subscription — it stays active until [close] (or `undeclare`) is called or the session is closed, whichever + * comes first. Only after that does it become eligible for garbage collection. + * * Example using the default [BlockingQueueHandler] handler: * * ```java diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Querier.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Querier.kt index 51a8ac0d..aa51663f 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Querier.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Querier.kt @@ -41,7 +41,9 @@ import java.util.concurrent.LinkedBlockingDeque /** * A querier that allows to send queries to a [Queryable]. * - * Queriers are automatically undeclared when dropped. + * A querier whose last reference is dropped is undeclared by the garbage-collection backstop + * (non-deterministic — call `close` for prompt effect); closing the session undeclares any querier + * still reachable. * * Example: * ```java diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Queryable.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Queryable.kt index c70a5ce9..5cb9dbf6 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Queryable.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Queryable.kt @@ -24,6 +24,10 @@ import io.zenoh.session.SessionDeclaration * * Its main purpose is to keep the queryable active as long as it exists. * + * The declaring session holds a *strong* reference to it: dropping your own reference does NOT stop the + * queryable — it stays active until [close] (or `undeclare`) is called or the session is closed, whichever + * comes first. Only after that does it become eligible for garbage collection. + * * Example using the default [BlockingQueueHandler] handler: * ```java * try (Session session = Zenoh.open(config)) { diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/scouting/Scout.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/scouting/Scout.kt index 4a2a4725..fb7af94b 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/scouting/Scout.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/scouting/Scout.kt @@ -25,7 +25,8 @@ import io.zenoh.handlers.Callback * Scout for routers and/or peers. * * Scout spawns a task that periodically sends scout messages and waits for Hello replies. - * Drop the returned Scout to stop the scouting task. + * Call `stop` (or `close`) on the returned Scout to stop the scouting task; a Scout whose last + * reference is dropped is stopped by the garbage-collection backstop (non-deterministic). * * To launch a scout, use [io.zenoh.Zenoh.scout]: *