Skip to content

[Bugifx] Shubhra/ Add buffer to FFI events#508

Merged
Shubhrakanti merged 10 commits into
mainfrom
shubhra/add-buffer-to-ffi-events
Jul 22, 2025
Merged

[Bugifx] Shubhra/ Add buffer to FFI events#508
Shubhrakanti merged 10 commits into
mainfrom
shubhra/add-buffer-to-ffi-events

Conversation

@Shubhrakanti

@Shubhrakanti Shubhrakanti commented Jul 18, 2025

Copy link
Copy Markdown
Contributor

This ports over a fix that was done in the python side, but never implemented in the node sdk (PR).

User reported that the Room would throw an error if there was already a participant inside and they tried to connect. This is because we were processing the participant_connected event before the trackSubscribed event.

Before fix:

  1. call room.connect
  2. receive trackSubscribed ← Tries to find participant that was never created!
  3. throw participant not found error

After fix:

  1. call room.connect
  2. receive participants_updated
  3. receive participant_connectedCreates the participant
  4. receive track_published
  5. receive track_subscribedWorks because participant exists

@changeset-bot

changeset-bot Bot commented Jul 18, 2025

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 772570f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 6 packages
Name Type
@livekit/rtc-node Patch
@livekit/rtc-node-darwin-arm64 Patch
@livekit/rtc-node-darwin-x64 Patch
@livekit/rtc-node-linux-arm64-gnu Patch
@livekit/rtc-node-linux-x64-gnu Patch
@livekit/rtc-node-win32-x64-msvc Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@Shubhrakanti Shubhrakanti changed the title Shubhra/ Add buffer to FFI events [Bugifx] Shubhra/ Add buffer to FFI events Jul 18, 2025
Comment thread tsconfig.json Outdated
{
"compilerOptions": {
"lib": ["es2015"],
"lib": ["es2015", "ES2021.WeakRef"],

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need this so that linter doesn't throw an error when using FinalizationRegistry

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lukasIO is there a reason this is es2015 can we just update it to something later.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we don't need FinalizationRegistry. But still would be good to upgrade this?

Comment thread tsconfig.json Outdated
{
"compilerOptions": {
"lib": ["es2015"],
"lib": ["es2015", "ES2021.WeakRef"],

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lukasIO is there a reason this is es2015 can we just update it to something later.

Comment thread packages/livekit-rtc/src/ffi_client.ts Outdated
}
}

subscribe(): ReadableStream<T> {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I chose this to be the equivalent of the asyncio's Queue we use in python. The main feature we care about is asynchronous reads and waiting for events while the stream is empty.

Comment thread packages/livekit-rtc/src/room.ts Outdated
remoteParticipants: Map<string, RemoteParticipant> = new Map();
localParticipant?: LocalParticipant;

private static cleanupRegistry = new FinalizationRegistry((cleanup: () => void) => {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use FinalizationRegistry (see docs) to act as an equivalent of python's __del__ method

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's also Symbol.dispose as defined in a ECMA proposal https://github.com/tc39/proposal-explicit-resource-management but I wasn't sure if this was merged in yet.

Comment thread packages/livekit-rtc/src/room.ts Outdated
} catch (error) {
log.debug(error, 'Listen task ended');
} finally {
this.listenTaskPromise = undefined;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it better to type it as | null?

@Shubhrakanti

Copy link
Copy Markdown
Contributor Author

@davidzhao this should fix the issues folks were having with inbound sip calls in agents js

Comment thread packages/livekit-rtc/src/ffi_client.ts Outdated
Comment on lines +45 to +53
} catch (error: unknown) {
log.error(error, 'Error enqueuing item to stream');
toRemove.add(controller);
}
}

for (const controller of toRemove) {
this.subscribers.delete(controller);
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this overkill? Just want to make sure there are no memory leaks.

Comment thread packages/livekit-rtc/src/ffi_client.ts Outdated
Comment on lines +72 to +80
/**
* @throws "TypeError: Invalid state: ReadableStream is locked"
* if the stream is locked, make sure the stream is not being read from
* before calling this method.
*/
unsubscribe(stream: ReadableStream<T>): void {
stream.cancel();
}
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love this, but it works for now.

Comment thread packages/livekit-rtc/src/room.ts Outdated
constructor() {
super();
// Register a finalizer to disconnect the room when it's garbage collected
Room.cleanupRegistry.register(this, () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need this. If the FfiHandle is dropped, the Rust side will already close the room

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to know. I saw in the python side so that's why I implemented it - but had a felling we didn't need this.

Comment thread packages/livekit-rtc/src/room.ts Outdated
});

// subscribe before connecting so we don't miss any events
this.ffiQueue = FfiClient.instance.queue.subscribe();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On JS it may be simpler, just hooking into the FfiEvent using on. We could just append to a list?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's fine, but IIRC that was the only difference. JS using events, but python using queues

@Shubhrakanti Shubhrakanti Jul 19, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On JS it may be simpler, just hooking into the FfiEvent using on that's exactly what this is doing.

We grab the events and append them to a queue. The only reason it's a stream is so reads are async and we don't block the main event loop.

@Shubhrakanti Shubhrakanti Jul 19, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean get rid of FfiClient.instance.queue all together. Replace this with

connect(){
// wire up the event listener first
FfiClient.instance.on(FfiClientEvent.FfiEvent, this.onFfiEvent)

// send the connection request
const res = FfiClient.instance.request<ConnectResponse>({ ...... })

}
//
onEvent(event): {
   this.buffer.push(event)
  // only start processing the events once we've connected.
   if (connected) {
      this.proccessEvent(event)
...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I understand it the order of operations are.

  1. Create a buffer for all the events coming in
  2. Connect to the room
  3. Immediately populate the buffer so we don't drop events
  4. Start processing events

Right now step 1 is done with this.ffiQueue = FfiClient.instance.queue.subscribe(); if we replace it with FfiClient.instance.on(FfiClientEvent.FfiEvent, this.onFfiEvent) we still need to buffer the events to process until we receive the ConnectResponse?

@Shubhrakanti Shubhrakanti requested a review from theomonnom July 19, 2025 19:31
@Shubhrakanti

Copy link
Copy Markdown
Contributor Author

Discussed offline with @theomonnom - we're just going to buffer the events that come in before the connect in room.ts. Once we have connected - we will process them in batch.

@Shubhrakanti Shubhrakanti merged commit 37211ca into main Jul 22, 2025
17 checks passed
@Shubhrakanti Shubhrakanti deleted the shubhra/add-buffer-to-ffi-events branch July 22, 2025 03:47
@github-actions github-actions Bot mentioned this pull request Jul 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants