Skip to content

Bidirectional calling with Durable Objects (Error: RPC stub used after being disposed) #110

@bndkt

Description

@bndkt

First of all, this is an awesome library and I'm heavily experimenting with very interesting use cases.

One thing that I still struggle with after lots of experimentation is bidirectional calling and I'd like to clarify if what I'm aiming for is even intended to work. Simply put, I'd like to register a callback from a client in a Durable Object, which then executes the callback(s) upon certain events happening.

Here are some relevant sections of the code:

Server (Durable Object)

import { DurableObject, RpcStub } from "cloudflare:workers";

export class MyRpcServer extends DurableObject {
  #subscriptions: Set<RpcStub<(message: string) => void>> = new Set();

  async addMessage(message: string) {
    console.log("DO: Adding message:", message);
    console.log("DO: Number of subscribers:", this.#subscriptions.size);

    for (const sub of this.#subscriptions) {
      console.log("DO: Sending message to subscriber");
      await sub(message);
      console.log("DO: Should be sent");
    }
  }

  subscribe(callback: RpcStub<(message: string) => void>) {
    console.log("DO: Subscribing");

    this.#subscriptions.add(callback);

    return () => {
      console.log("DO: Unsubscribing");
      this.#subscriptions.delete(callback);
    };
  }
}

Client

const localReceiveMessage = (message: string) => {
  console.log("Adding message", message);
};

let api = newWebSocketRpcSession<MyRpcServer>("ws://localhost:5173/rpc");
api.subscribe(new RpcStub<(message: string) => void>(localReceiveMessage))

If I execute the callback immediately within the subscribe function, it works. But if I store it and then execute it later (upon receiving a message via addMessage within the DO), it fails with Error: RPC stub used after being disposed.. I have read the README section about automatic disposal and also tried lots of variations of the code, duplicating the stubs with dup(), but to no avail so far.

I've created a reproducible example here: https://github.com/bndkt/rpc-example and would appreciate any pointes on whether this should be a supported scenario at all and if yes, where I'm missing something.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions