Skip to content

Return a PublishDataInstruction from PublishData#301

Open
MaxHeimbrock wants to merge 4 commits into
mainfrom
max/publish-data-return-result
Open

Return a PublishDataInstruction from PublishData#301
MaxHeimbrock wants to merge 4 commits into
mainfrom
max/publish-data-return-result

Conversation

@MaxHeimbrock

@MaxHeimbrock MaxHeimbrock commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Dependency

Depends on livekit/rust-sdks#1137
Fails without: https://github.com/livekit/client-sdk-unity/actions/runs/27275793150

Summary

LocalParticipant.PublishData was fire-and-forget (void), so callers had no way to know whether the Rust side accepted the packet — most importantly, packets exceeding the negotiated maximum message size were dropped with only a log::warn!.

This wires through the result the FFI transport already carried (no proto regen, no Rust changes):

  • PublishDataRequest already has request_async_id; Rust already emits PublishDataCallback { async_id, error } and data_task forwards the publish result into it. The C# side simply discarded the event.

Changes:

  • New PublishDataInstruction (YieldInstruction) exposing IsError + Error.
  • The three PublishData overloads now return it instead of void (source-compatible — callers ignoring the result still compile).
  • Route the callback by adding PublishData to FFIClient.ExtractRequestAsyncId.

Oversized packets now surface the error via instruction.Error:

data packet size (66602 bytes) exceeds the negotiated maximum message size (64000 bytes)

Tests

  • ReturnsSuccess_ForSmallPayload — verifies the success path; runs on any FFI binary.
  • ReturnsError_ForOversizedPayload — verifies the size-limit error. Marked [Explicit]: it requires the client-sdk-rust datamessage_size FFI binary that enforces the 64000-byte limit (shipped plugins do not yet). Against the old binary an oversized reliable send wedges the publisher transport instead of returning cleanly — which is the behavior the limit fixes.

Verified locally: all 2 PublishDataTests pass (3.3s) against a macOS FFI lib rebuilt from datamessage_size + livekit-server --dev.

MaxHeimbrock and others added 3 commits June 9, 2026 11:14
LocalParticipant.PublishData is fire-and-forget — the C# API has no
callback, so server-side drops of oversized packets are silent. These
tests probe the boundary empirically by publishing payloads of varying
sizes between two participants and asserting delivery via the
subscriber's DataReceived event. Retries on a 200 ms interval to avoid
SFU warm-up flakiness. Verified against livekit-server 1.12.0: 1 KiB
and 15 KiB arrive intact (length + bytes); 65 KiB is dropped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PublishData was fire-and-forget (void), so callers could not tell whether
the Rust side accepted the packet. The FFI transport already carried the
result end-to-end — PublishDataRequest has a request_async_id, Rust emits
a PublishDataCallback { async_id, error }, and the data_task forwards the
publish result into it — but the C# side discarded the event.

Wire it through:
- New PublishDataInstruction (YieldInstruction) exposing IsError + Error.
- The three PublishData overloads now return it instead of void
  (source-compatible: callers ignoring the result still compile).
- Route the callback by adding PublishData to FFIClient.ExtractRequestAsyncId.

Packets over the negotiated maximum message size now surface the error
("data packet size (N bytes) exceeds the negotiated maximum message size
(64000 bytes)") via instruction.Error.

Tests: ReturnsSuccess_ForSmallPayload verifies the success path on any
binary. ReturnsError_ForOversizedPayload (Explicit) verifies the size-limit
error and requires the client-sdk-rust datamessage_size FFI binary.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@MaxHeimbrock MaxHeimbrock changed the base branch from max/publish-data-size-tests to main June 10, 2026 12:10

@xianshijing-lk xianshijing-lk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

some nits / questions

/// Check <see cref="YieldInstruction.IsError"/> and read <see cref="PublishDataInstruction.Error"/>
/// to handle the result (e.g. payloads exceeding the negotiated maximum message size).
/// </returns>
public PublishDataInstruction PublishData(byte[] data, IReadOnlyCollection<string> destination_identities = null, bool reliable = true, string topic = null)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Instruction does not seem to be a common technology for such purpose ?
how about PublishDataTask ?

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 the instruction term since the base type is CustomYieldInstruction

Other examples in our code base:

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.

Instructions use Coroutines underneath, Tasks use C# async

IsDone = true;
}

void OnCanceled()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit, should onCanceled() private ?

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.

It is private by default, and we also use the implicit private style around in the other instruction classes:

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