Skip to content

pagination: byPage().stream() strands the held page's connection on short-circuit #203

Description

@OmarAlJarrah

Context

#202 unifies the pagination stacks and adds the auto-closing CloseablePages view returned
by byPage(). Item-level iteration is leak-safe by construction: PageWalker.items()
eager-closes each page (copying its materialized items) before yielding, so a partial
consume never strands a connection. Page-level access instead relies on the caller wrapping
the view in use {} / try-with-resources.

Problem

CloseablePages.stream() builds its Stream without registering an onClose handler:

public fun stream(): Stream<Page<T>> =
    StreamSupport.stream(
        Spliterators.spliteratorUnknownSize(iterator(), Spliterator.ORDERED),
        false,
    )

The backing iterator only closes a page when the consumer advances past it (next()) or the
source is exhausted (hasNext() returns false). A short-circuiting terminal —
byPage().stream().findFirst(), .limit(n), .anyMatch(...) — pulls the first page into
current and abandons the stream without advancing or exhausting, so that page is never
closed.

Because no onClose is wired, the idiomatic Java guard is a no-op too:

try (Stream<Page<X>> s = paginator.byPage().stream()) {
    s.findFirst();   // Stream.close() runs no handlers → page leaks
}

The held page's live Response (body + pooled connection) is stranded. The item-level
streamAll() does not have this problem because it eager-closes.

Suggested fix

Wire the view's close() into the returned stream so try-with-resources on the Stream
releases the held page:

public fun stream(): Stream<Page<T>> =
    StreamSupport.stream(
        Spliterators.spliteratorUnknownSize(iterator(), Spliterator.ORDERED),
        false,
    ).onClose(::close)

and/or make the KDoc explicit that the CloseablePages itself — not the Stream — is the
close handle (byPage().use { it.stream()... }). Add a regression test that short-circuits a
page-level stream and asserts the held response is released.

References

Introduced in #202 (CloseablePages.stream()); part of #30.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingsdk-coresdk-core toolkit

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions