Skip to content

Use subscribe:true for GET operations instead of separate SUBSCRIBE #60

@sanity

Description

@sanity

Background

River currently uses subscribe: false for GET operations, followed by a separate SUBSCRIBE request. This pattern was adopted in CLI v0.1.7 (August 2025) based on the belief that "GET with subscribe:true requires performing sub-operations from within the main operation and waiting for them to complete, which was never implemented in Freenet."

Current Behavior

CLI (cli/src/api.rs):

let get_request = ContractRequest::Get {
    key: *contract_key.id(),
    return_contract_code: true,
    subscribe: false,  // Always false, we'll subscribe separately if needed
};

UI (ui/src/components/app/freenet_api/room_synchronizer.rs):

let get_request = ContractRequest::Get {
    key: *contract_key.id(),
    return_contract_code: true,
    subscribe: false,
};

Both then issue a separate SUBSCRIBE request after the GET completes.

Analysis of freenet-core

After reviewing the freenet-core implementation, subscribe:true IS fully implemented:

  1. Local GET path (client_events/mod.rs:1126-1143): If contract is found locally and subscribe=true, register_subscription_listener() is called before returning the result.

  2. Network GET path (client_events/mod.rs:1259-1277): After starting the GET operation, if subscribe=true, register_subscription_listener() is called immediately.

  3. Legacy GET path (client_events/mod.rs:1338-1356): Same pattern - subscription listener is registered after starting the operation.

The subscription is registered optimistically (before the GET result is known), which is actually beneficial - it means no updates can be missed between GET completing and SUBSCRIBE being processed.

Proposed Change

Switch to subscribe: true for GET operations:

let get_request = ContractRequest::Get {
    key: *contract_key.id(),
    return_contract_code: true,
    subscribe: true,  // Subscribe atomically with GET
};

And remove the separate SUBSCRIBE request that follows.

Benefits

  1. Reduced round-trips: Single request instead of two sequential requests
  2. No race condition: Updates cannot be missed between GET response and SUBSCRIBE request
  3. Simpler code: Remove the separate subscription logic after GET
  4. Consistency: PUT already uses subscribe: true (api.rs:144)

Considerations

  • The subscription is registered before the GET completes, so if the GET fails, the client has a registered listener but no state. This is fine - failed GETs would require user retry anyway.
  • Should verify behavior matches expectations in integration tests before merging.

[AI-assisted - Claude]

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