Skip to content

social: return registration cleanup from Subscribe - #20

Merged
HashimTheArab merged 4 commits into
lunarfrom
feat/social-per-handler-unsubscribe
Sep 11, 2026
Merged

social: return registration cleanup from Subscribe#20
HashimTheArab merged 4 commits into
lunarfrom
feat/social-per-handler-unsubscribe

Conversation

@HashimTheArab

@HashimTheArab HashimTheArab commented Jul 7, 2026

Copy link
Copy Markdown
Owner

social.Client.CloseContext clears every handler sharing its RTA subscription. A broadcaster shutting down needs to release its own registration while other users of the Xbox client keep receiving events.

Change Subscribe(ctx, handler) to return (func(context.Context) error, error). Each successful call returns its own cleanup function, backed by a unique registration. This removes the proposed handler-based Unsubscribe method, reflection, comparability restrictions, and ambiguous matching of duplicate handler values.

unsubscribe, err := client.Social().Subscribe(ctx, handler)
if err != nil {
    return err
}
// Later, when this registration is no longer needed:
return unsubscribe(cleanupCtx)

Repeated or concurrent cleanup is safe. Only final-registration cleanup tears down the shared RTA subscription; failed teardown can be retried. Cleanup from before a client reset cannot remove a new registration. Callbacks already queued may still finish.

This changes the Go method signature. The repository's caller and dependent broadcaster PR HashimTheArab/go-mcxboxbroadcast#13 are updated together; no compatibility wrapper or network-format change is introduced.

Validation: go test ./... -count=1, go test -race ./social ./rta -count=1, and git diff --check pass. Real local WebSocket tests cover selective delivery, duplicate and non-comparable handlers, cleanup retry, client reuse, and concurrent cleanup. Independent review found no actionable issues. No live Xbox calls are used.

Client.CloseContext unsubscribes the shared RTA subscription and clears every
registered handler, so a caller that only wants to remove its own handler (for
example on shutdown) also disables any other Subscribe users on the same client.

Add Client.Unsubscribe(ctx, handler), which removes a single handler by identity
and tears the RTA subscription down only once the last handler is gone. Handlers
must be comparable to be removed; a non-comparable handler returns an error
rather than panicking, preserving support for non-comparable handlers in
Subscribe/dispatch.
@cursor

cursor Bot commented Jul 7, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_088cb957-ff83-48a1-a3ee-5b4105ec807d)

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: d09b88ec-ff7b-4e15-9a7b-c00629fee797

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/social-per-handler-unsubscribe

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

HashimTheArab added a commit to HashimTheArab/go-mcxboxbroadcast that referenced this pull request Jul 7, 2026
Closing one broadcaster called social CloseContext, which unsubscribes the
shared RTA subscription and clears every handler on the client. That could
disable other Social().Subscribe users on a shared xsapi.Client.

Use the new go-xsapi social Client.Unsubscribe(ctx, handler) to remove only the
broadcaster's own handler; the RTA subscription is torn down only when its last
handler is gone. Bumps the go-xsapi replace to the commit that adds Unsubscribe.

Depends on HashimTheArab/go-xsapi#20; the replace pins that branch commit and
should be re-pinned to lunar once it merges.
@HashimTheArab HashimTheArab changed the title social: add Client.Unsubscribe for per-handler removal social: return registration cleanup from Subscribe Sep 11, 2026
@HashimTheArab
HashimTheArab merged commit 5e75342 into lunar Sep 11, 2026
1 check passed
HashimTheArab added a commit to HashimTheArab/go-mcxboxbroadcast that referenced this pull request Sep 11, 2026
* Accept friend requests reactively via social RTA subscription

Friend sync accepted incoming friend requests only on the poll interval, so a
new request waited up to one UpdateInterval to be accepted. Java MCXboxBroadcast
accepts them immediately off an RTA social subscription
(IncomingFriendRequestCountChanged -> acceptPendingFriendRequests).

Subscribe to each account's go-xsapi/v2 social RTA feed and feed its events into
the existing sync loop through a new FriendSyncer.Trigger channel, so an
incoming friend request (or any relationship change) runs an immediate sync pass
in the same goroutine that owns the rate-limit state. Events are coalesced
(non-blocking send to a size-1 channel). The subscription is best-effort: the
periodic syncer remains the backstop, the RTA connection re-subscribes after
transient drops, and a nil Trigger keeps a syncer purely periodic.

Closes #12.

* Undo social RTA subscription on broadcaster close

The social subscription created for reactive friend sync was never released.
b.ctx only scopes the subscribe handshake; once subscribed, go-xsapi keeps the
subscription active and appends a handler on each later Subscribe. A library
caller that reuses an external xsapi.Client across broadcaster restarts would
accumulate stale handlers, and events would keep dispatching after the syncer
stopped (Close only closes clients the broadcaster created itself).

Bind each subscription's lifetime to the broadcaster: a dedicated goroutine
unsubscribes once b.ctx is canceled, and Close waits on socialWg so the
subscriptions are undone before it returns. A reused client is left clean, so a
subsequent broadcaster re-subscribes without duplicating handlers.

* Guard against a closed friend-sync Trigger channel

A closed Trigger would receive forever, spinning Run through runSync and
bypassing UpdateInterval. Use a two-value receive and nil the channel out when
it is closed so the case stops firing.

* Release the social subscription per-handler instead of client-wide

Closing one broadcaster called social CloseContext, which unsubscribes the
shared RTA subscription and clears every handler on the client. That could
disable other Social().Subscribe users on a shared xsapi.Client.

Use the new go-xsapi social Client.Unsubscribe(ctx, handler) to remove only the
broadcaster's own handler; the RTA subscription is torn down only when its last
handler is gone. Bumps the go-xsapi replace to the commit that adds Unsubscribe.

Depends on HashimTheArab/go-xsapi#20; the replace pins that branch commit and
should be re-pinned to lunar once it merges.

* Use xsapi-owned social and presence clients

* Pin merged xsapi social subscription cleanup

* Bound reactive friend scans and honor read backoff

* Bound social RTA subscription setup
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.

1 participant