Skip to content

Expose consistent deterministic close lifecycles on public native owners #697

Description

@milyin

Context

The zenoh-flat transition exposes explicit lifecycle operations on the generated JNI handles, but the public SDK does not surface them consistently.

Most public resource wrappers already implement AutoCloseable (Session, declarations, Query, Scout, advanced declarations/listeners, etc.). Three ownership gaps remain:

  1. A received ZBytes owns a native handle until first materialization, but the public wrapper exposes neither close() nor a discard operation. Because ZBytes deliberately has no Cleaner backstop for throughput, an unread payload cannot currently be released by the user.
  2. Config directly owns a mutable, Cleaner-backed JNI Config, but provides no deterministic close.
  3. Every current SessionDeclaration implementation is already AutoCloseable, but the common interface does not express that contract.

The policy should be based on public ownership, not merely on whether an implementation happens to touch JNI:

A public wrapper exposes close() when it directly owns native state whose lifetime can escape the current call. Borrowed handles, consumed temporaries, pure values, and internal Cleaner-backed caches do not imply public closeability.

Proposed API

1. Make ZBytes explicitly discardable and closeable

Have concrete ZBytes implement AutoCloseable and expose a clearly named discard operation:

class ZBytes ... : IntoZBytes, AutoCloseable {
    /** Release an unread native payload without copying it. */
    fun discard() { ... }

    override fun close() = discard()
}

Requirements:

  • Synchronize discard/close on the same monitor used by lazy materialization.
  • Make close() and discard() idempotent.
  • If an unread handle is discarded, a later toBytes() must fail with a clear closed-state exception rather than handle!!/NPE.
  • Define and document behavior after the bytes have already been materialized and for ZBytes.from(...) instances that never owned native memory.
  • Do not add a Cleaner to ZBytes: explicit closeability must preserve the deliberate receive hot path and avoid per-message Cleaner registration.
  • Do not add AutoCloseable to IntoZBytes; user-defined implementations may be pure JVM values.

This supports Kotlin .use { ... } and Java try-with-resources, while also providing a direct discard() when the payload is intentionally ignored.

2. Make Config AutoCloseable

Config directly owns its generated JNI Config handle, so expose deterministic release by delegating close() to it. Keep the generated Cleaner as a forgotten-close fallback.

Closing should be idempotent, and later operations should fail through a documented closed-state path.

3. Put closeability on SessionDeclaration

interface SessionDeclaration : AutoCloseable {
    fun undeclare()

    override fun close() = undeclare()
}

All current implementations already provide equivalent close() behavior. Putting it on the interface makes polymorphic declarations usable with use/try-with-resources. Please verify source and binary behavior for external implementations and retain a default implementation to minimize compatibility impact.

Deliberate exclusions

Do not add close() solely because a type contains or references another closeable value:

  • Sample and Reply are containers and may share their ZBytes members with caller code.
  • Query.close() should continue to close the query/reply capability, not implicitly invalidate payload or attachment references extracted by the caller.
  • Encoding is a pure JVM value in zenoh-kotlin.
  • IntoZBytes, IntoSelector, IntoParameters, value/data types, and borrowing facades do not own an escaping native resource.

Acceptance criteria

  • An unread received payload or attachment can be released without materializing its bytes.
  • ZBytes read-versus-close races are safe and have deterministic outcomes.
  • All new close operations are idempotent and documented.
  • Config supports deterministic close while retaining Cleaner fallback.
  • A value typed as SessionDeclaration can be used as AutoCloseable.
  • Tests cover close/discard before materialization, after materialization, repeated close, application-created ZBytes, and concurrent read versus close.

This applies to the zenoh-flat-transition implementation; current main still uses eager JVM byte arrays.

Related: #695, #696, eclipse-zenoh/zenoh-java#482, eclipse-zenoh/zenoh-java#516, eclipse-zenoh/zenoh-java#517.

— Codex (GPT-5)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions