Skip to content

Switch IPCChannel to raw unix socket transport - #1132

Closed
creativecbr wants to merge 3 commits into
livekit:mainfrom
creativecbr:creativecbr/ipcchannel-raw-unix-socket
Closed

creativecbr wants to merge 3 commits into
livekit:mainfrom
creativecbr:creativecbr/ipcchannel-raw-unix-socket

Conversation

@creativecbr

Copy link
Copy Markdown
Contributor

Replaces the Network.framework IPC transport in IPCChannel with a raw UNIX domain stream socket implementation (socket/bind/listen/accept/connect) while preserving LiveKit’s existing IPC framing and public behavior.

On some iOS/iPadOS devices, the current NWListener/NWConnection path for UNIX-socket IPC fails during listener setup with NECP path evaluation errors (observed as SO_NECP_LISTENUUID failed [22: Invalid argument]). In that failure mode, the broadcast extension and host app do not complete IPC handshake, and ReplayKit publishing can come up without frame flow.

What changes in this PR:

  • Switches IPCChannel transport from Network.framework to raw POSIX UNIX sockets on iOS.
  • Keeps message semantics unchanged: framed messages with header + optional payload, same plist header encoding, same async incoming message shape.
  • Preserves compatibility for existing broadcast components (BroadcastReceiver, BroadcastUploader, codecs, SocketPath) with no public API changes.
  • Keeps cancellation/error behavior aligned with prior expectations (retry on transient connect errors, close on terminal I/O errors).
  • Adds concise [IPC] logs at key lifecycle points (listen/connect/accept/read-loop-end/close), consistent with project logging style.

Scope and non-goals:

  • iOS-only IPC transport internals; no changes to cross-platform API surface.
  • No protocol/framing redesign; wire format remains equivalent from caller perspective.
  • No functional changes outside IPCChannel.

Regression/testing status:

  • Manual regression verification completed on:

    • iOS 26.5, iPhone 15
    • iPadOS 17.7, iPad 6th gen
  • In both environments, IPC handshake and screen-share flow are stable, and the issue is no longer reproducible.

  • Compile/build sanity also passes for the updated path.

Replace the Network.framework IPC path with a POSIX UNIX domain socket implementation while keeping the same message framing and public behavior.

Also align observability and style with the SDK: add concise [IPC] logs at listen/connect/close boundaries and keep comments minimal.
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@creativecbr

Copy link
Copy Markdown
Contributor Author

@pblazej @hiroshihorie @xianshijing-lk

I made the necessary improvements to the first version and refactored it slightly, while trying to keep the changes as atomic as possible. This is a very important change for us, as some devices were not able to start screen sharing at all.

Regression tests were performed on physical devices:

  • iPhone 15 (iOS 26.2.1)
  • iPad 6th generation (iPadOS 17.7.11)

@pblazej

pblazej commented Sep 24, 2026

Copy link
Copy Markdown
Contributor

Hey @creativecbr thank you for a detailed report and PR, I think we need to be a little conservative about essentially reverting to #100 - by understanding the root cause first.

I added more specific logs in this branch, it would be great to check them before the next pass: blaze/ipc-logs.

@creativecbr

creativecbr commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor Author

Hey @pblazej !

That issue was difficult to investigate because my first attempt, with the additional logging enabled, was successful, the connection was established and streaming started correctly. As far as I know, no functional changes were introduced apart from the extra logs, which made the result unexpected and left me without a clear direction for further analysis.

However, all subsequent attempts on the same device failed. The room connection was established successfully, but the stream itself never started.

To provide the full context, I have attached logs from both scenarios:
successful.txt
fail.txt

pblazej added a commit that referenced this pull request Sep 28, 2026
The reporter's failing run showed no IPC problem: the host rejected the
auto-publish with "Participant does not have permission to publish", so
no listener was ever created and the extension retried against a stale
socket file until the user stopped the broadcast.

- Check for a room and publisher before permissions in _publish. A
  disconnected room resets the local participant to default permissions
  (canPublish false), so a publish while disconnected reported a
  permission error instead of the missing publisher
- Stop the broadcast when the automatic publish fails, instead of leaving
  the extension running with nothing to connect to
- Describe every LiveKitErrorType case; insufficientPermissions and
  deviceAccessDenied fell through to "Unknown". Drop the default so new
  cases need a description
- Log local participant permission changes at .info

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@pblazej

pblazej commented Sep 28, 2026

Copy link
Copy Markdown
Contributor

Thanks @creativecbr, the failing run is actually Participant does not have permission to publish (so the IPC listener is never created) - I pushed a few fixes and permission logs to blaze/ipc-logs, it would be great to re-run the failing case from app launch.

@creativecbr

Copy link
Copy Markdown
Contributor Author

It seems that it works! @pblazej

I made three attempts and I can confirm 3/3 passes. :)

3_attempt.txt
2_attempt.txt
1_attempt.txt

Logs here.

pblazej added a commit that referenced this pull request Sep 28, 2026
In every successful run the host's listener comes up about 6 s after the
screen share publish begins, and the publish completes right after, so
startCapture itself (or the whole process) is stalled for that long.
Log with the call-site uptime at startCapture entry, after the main-actor
hop for the screen bounds, and when the receiver task starts; the SDK
logger writes on a utility queue, so its own timestamps can lag.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@pblazej

pblazej commented Sep 28, 2026

Copy link
Copy Markdown
Contributor

Great, thanks @creativecbr - these runs all start from a fresh connect though, while the failing one was a second broadcast in the same app session, could you repeat that flow (start → stop → start, without relaunching) on the latest blaze/ipc-logs?

@creativecbr

Copy link
Copy Markdown
Contributor Author

Okay, but.... :D We have a very simple scenario. After connecting to the room, we automatically start broadcasting. When the connection is about to end, we stop the broadcast and immediately leave the room.

There is no possibility of stopping the broadcast and starting it again within the same connection. Could there be some retry mechanism involved that is a black box from our perspective?

@pblazej

pblazej commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

@creativecbr I extracted (small) non-logging changes into the above PR - should do the job 🙆‍♂️

pblazej added a commit that referenced this pull request Sep 30, 2026
- Check for a room and publisher before permissions in _publish; a
  disconnected room resets the local participant to default permissions,
  so publishing while disconnected reported a permission error
- Stop the broadcast when the automatic screen share publish fails,
  instead of leaving the extension retrying against a stale socket
- Skip the automatic publish in a disconnected room, so a stale Room
  cannot stop another room's working broadcast
- Treat NWListener .waiting as transient rather than fatal
- Fix the IPC reconnect delay, which UInt64(0.1) truncated to zero
- Copy the shared NWParameters before setting requiredLocalEndpoint, so
  concurrent listeners stop binding each other's socket paths
- Describe every LiveKitErrorType case; insufficientPermissions and
  deviceAccessDenied printed as "Unknown"

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@creativecbr

Copy link
Copy Markdown
Contributor Author

Thanks a lot, so we are waiting for those changes in release.

@pblazej

pblazej commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Closing in favor of #1142

@pblazej pblazej closed this Sep 30, 2026
pblazej added a commit that referenced this pull request Oct 6, 2026
When the automatic screen share publish failed, the broadcast extension
kept retrying against a socket with no listener, so the broadcast came
up without frames.

- **Publish:** `_publish` checked permissions before the publisher, and
a disconnected room resets the local participant's permissions, so it
failed as `Unknown(Participant does not have permission to publish)`; it
now checks the publisher first, stops the broadcast on failure, and
every `LiveKitErrorType` has a description.
- **IPC:** the listener treats `.waiting` as transient, reconnects every
100 ms instead of busy-looping (`UInt64(0.1)` truncated the delay to
zero), and copies the shared `NWParameters` so concurrent listeners stop
binding each other's paths, which also de-flakes `IPCChannelTests`.
- **Capturer:** stopping before the extension connects now cancels the
waiting listener instead of leaking the capturer, and a second active
broadcast capturer (e.g. two connected rooms) logs a warning, since only
one of them can receive the broadcast.

Network.framework is not the cause: the `SO_NECP_LISTENUUID` warnings
also appear in successful runs.

Fixes: #1132

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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