Skip to content

fix(service): keep subscriptions made before Start, drop publishes before it - #4

Merged
inkly merged 2 commits into
ReCasaOS:mainfrom
ppenguin:fix/subscriptions-before-start
Oct 24, 2026
Merged

inkly merged 2 commits into
ReCasaOS:mainfrom
ppenguin:fix/subscriptions-before-start

Conversation

@ppenguin

Copy link
Copy Markdown
Contributor

Startup races in the event/action dispatch.

Problem Fix
Services.Start runs EventServiceWS.Start in a goroutine; YSK subscribes right after → Start resets subscriberChannels → that subscription is dropped (cards stop updating, depending on scheduling) Start keeps an existing subscriber map (event + action)
Publish / Trigger before Start: select on (*s.ctx).Done() with a nil ctx → panic drop (and log) until Start ran

Tests

  • subscription made before Start receives an event
  • publish before Start returns
  • red → green: both fail on main (timeout / nil-pointer panic)
  • ysk tests: already poll; comment updated (the boot wait stays: no readiness signal)

Context: ReCasaOS/CasaOS#11

🤖 Generated with Claude Code

…fore it

- Services.Start runs EventServiceWS/ActionServiceWS.Start in goroutines; YSK
  subscribes right after → Start's subscriberChannels reset dropped that
  subscription (cards stop updating, depending on scheduling)
- Publish/Trigger before Start: select on (*s.ctx).Done() with nil ctx → panic
- Start keeps an existing subscriber map; Publish/Trigger drop (and log) until Start ran
- tests: subscription before Start receives; publish before Start returns

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

@inkly inkly left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The event-service fix looks right.

Please apply the same locking to ActionServiceWS: Start's nil check, Subscribe and Trigger are still unsynchronised (and Start still resets s.mutex), so a pre-Start subscription can be lost and the reads race with Start. A test for that path would help, since CI runs -race.

publishUntilReceived republishes every 10 ms, so a slow first delivery under -race yields a duplicate that the next iteration receives, or a leaked forwarder that trips goleak. Consider a ready channel closed in Start: it would remove the republish loops and the 1 s sleep in ysk_test.go. Dropping before Start instead of queueing is fine with us; Warn would be enough for that log line.

We will squash-merge with a plain commit message (we keep tool attribution trailers out of main's history), so nothing to change there.

…eplaces republish loops

- ActionServiceWS: Start no longer resets the mutex; Start setup/teardown, Trigger's read,
  Subscribe, dispatch, heartbeat under it → pre-Start subscriptions kept, no race with Start
- Trigger waits for the dispatch like Publish (was: dropped when the dispatch was busy);
  routes trigger from their own goroutine
- Ready(): closed by Start once its state is in → tests wait on it: no 10 ms republish
  loops (duplicates under -race, leaked forwarders), no 1 s sleep in pkg/ysk
- before Start: Publish/Trigger drop with Warn
- tests: action subscription before Start, Trigger before Start, Subscribe/Trigger
  concurrent with Start (-race)

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

Copy link
Copy Markdown
Contributor Author

Done in f850e35:

  • ActionServiceWS: Start no longer resets the mutex; Start setup/teardown, Trigger's read, Subscribe, dispatch and heartbeat under it
  • Ready() on both services: closed by Start once its state is in
    • tests wait on it: republish loops and the 1 s sleep in pkg/ysk gone
  • Trigger waits for the dispatch like Publish (was: dropped when the dispatch was busy). Routes trigger from their own goroutine. Needed for a deterministic test after Ready(); say if you'd rather keep the drop.
  • before Start: Publish/Trigger drop with Warn
  • tests (-race): action subscription before Start survives it, Trigger before Start, 8× Subscribe/Trigger concurrent with Start; -count=30 clean

@inkly inkly left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks, this addresses both points: the action service now locks like the event service, and Ready() removes the republish loops and the sleep. Approving.

One small follow-up, older than this PR: Start's teardown closes the inbound channel while a Trigger/Publish may already hold it, and with ctx.Done also ready select can pick the send and panic (send on closed channel; reproduced with triggers racing cancel()). Since senders already select on ctx.Done(), setting the field to nil without closing it is enough. Optionally: subscriberBuffer for action subscribers too (still 1), and a timeout on the receive in TestEventTypeService.

We'll squash-merge with a plain commit message.

@inkly
inkly merged commit 17c0ab0 into ReCasaOS:main Oct 24, 2026
1 check passed
@ppenguin

Copy link
Copy Markdown
Contributor Author

Follow-up: #7 (teardown no longer closes the inbound channel, action subscriber buffer 64, receive timeout in TestEventTypeService).

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