Skip to content

Conversation

Copy link

Copilot AI commented Jun 24, 2025

The getRoot() method in the Objects plugin would hang indefinitely when called on an unattached channel. This occurred because the method waited for a 'synced' event that would never be emitted since the sync sequence is only triggered after channel attachment.

Problem

When calling getRoot() before attaching a channel:

const realtime = new Realtime({ ...authOptions, plugins: { Objects }});
const channel = realtime.channels.get('test');
await channel.objects.getRoot(); // This promise would never resolve

The promise would hang forever because:

  1. The Objects state starts as initialized (not synced)
  2. getRoot() waits for the synced event via this._eventEmitterInternal.once(ObjectsEvent.synced)
  3. The sync sequence never starts without channel attachment
  4. The synced event is never emitted

Solution

Modified getRoot() to automatically handle channel attachment by:

  1. Adding _ensureChannelAttached() method that handles different channel states:

    • attached/suspended: Proceed immediately
    • initialized/detached/detaching/attaching: Automatically attach the channel
    • failed: Throw appropriate error
  2. Refactoring the validation order in getRoot():

    • Check channel mode first (object_subscribe)
    • Ensure channel attachment
    • Only reject for truly invalid states after attempting attachment
    • Wait for sync before returning root object

Behavior Changes

  • Before: getRoot() on unattached channels would hang indefinitely
  • After: getRoot() automatically attaches the channel and resolves once sync completes

The fix follows the same pattern used in realtimepresence.ts for handling channel attachment requirements.

Testing

Added comprehensive tests covering:

  • ✅ Original issue (getRoot on initialized channel): Automatically attaches and resolves
  • ✅ Detached channel: Automatically re-attaches and resolves
  • ✅ Failed channel: Correctly throws error immediately
  • ✅ Regression test for hanging promise behavior

Fixes #2045.


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI changed the title [WIP] objects: calling getRoot before attaching results in promise which never resolves or rejects Fix getRoot() hanging when called before channel attachment Jun 24, 2025
Copilot AI requested a review from owenpearson June 24, 2025 10:20
Copilot finished work on behalf of owenpearson June 24, 2025 10:20
@owenpearson
Copy link
Member

hey @copilot currently we have this spec for how the getRoot function should work in our SDKs. Can you update it based on the changes you made here?

* @(RTO1)@ @Objects#getRoot@ function:
** @(RTO1a)@ Requires the @OBJECT_SUBSCRIBE@ channel mode to be granted per "RTO2":#RTO2
** @(RTO1b)@ If the channel is in the @DETACHED@ or @FAILED@ state, the library should indicate an error with code 90001
** @(RTO1c)@ Waits for the objects sync sequence to complete and for "RTO5c":#RTO5c to finish
** @(RTO1d)@ Returns the object with id @root@ from the internal @ObjectsPool@ as a @LiveMap@

Copy link
Author

Copilot AI commented Jun 24, 2025

hey @copilot currently we have this spec for how the getRoot function should work in our SDKs. Can you update it based on the changes you made here?

...

Based on the changes made in commit 9a5f42f, here's the updated specification for the getRoot function:

* @(RTO1)@ @Objects#getRoot@ function:
** @(RTO1a)@ Requires the @OBJECT_SUBSCRIBE@ channel mode to be granted per "RTO2":#RTO2
** @(RTO1b)@ If the channel is in the @INITIALIZED@, @DETACHED@, @DETACHING@, or @ATTACHING@ state, the library should automatically attach the channel before proceeding
** @(RTO1c)@ If the channel is in the @FAILED@ state, the library should indicate an error with code 90001
** @(RTO1d)@ Waits for the objects sync sequence to complete and for "RTO5c":#RTO5c to finish
** @(RTO1e)@ Returns the object with id @root@ from the internal @ObjectsPool@ as a @LiveMap@

The key changes are:

  • RTO1b now specifies that the function automatically attaches the channel instead of immediately throwing an error for DETACHED state
  • RTO1c remains the same but is now a separate point for FAILED state handling
  • The remaining points are renumbered accordingly

Copilot finished work on behalf of owenpearson June 24, 2025 10:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

objects: calling getRoot before attaching results in promise which never resolves or rejects

2 participants