Phase 2: Server Push — Implementation Plan #6
Closed
SeanTAllen
started this conversation in
Research
Replies: 2 comments
-
|
Decision: Option A — Built-in PubSub within livery. No plan to extract PubSub into a separate library. Pony's actor model handles inter-node communication natively, so a standalone PubSub library isn't needed. The simple |
Beta Was this translation helpful? Give feedback.
0 replies
-
|
Implemented in #7. Deviations from the plan:
|
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
Implementation plan for Phase 2 of livery, derived from the design in #3.
Scope
Server-side only. Adds external message delivery to LiveView connections, enabling real-time updates from server-side actors (timers, background jobs, PubSub). Covers Phase 2 deliverables from #3.
Deliverables:
handle_infoonLiveViewtrait (default no-op)infobehavior on_Connectionfor external message deliveryInfoReceiverinterface — public handle for sending messages to a connectionSocket.self()— returns the connection'sInfoReceiver tagPubSubactor — topic-based publish-subscribeSocket.subscribe(topic)/Socket.unsubscribe(topic)Design Decision: PubSub Location
The design (#3) lists PubSub as a "separate library" in scope, but Phase 2 deliverables include
socket.subscribe(topic)/socket.unsubscribe(topic). These are in tension.Options:
(A) Built-in PubSub within livery. A simple
PubSubactor in the livery package.Listeneraccepts it as a parameter and passes it to connections. Socket provides subscribe/unsubscribe convenience methods. Connections auto-cleanup on close.(B) Mechanisms only, no PubSub. Livery provides
handle_info,infobehavior,socket.self(), and aterminatecallback on LiveView. Users wire their own PubSub using the connection reference. No subscribe/unsubscribe on Socket.(C) Built-in PubSub now, extract later. Build a simple PubSub within livery for Phase 2. When a standalone PubSub library exists, livery can depend on it instead.
Recommendation: Option C. Delivers the Phase 2 deliverables as designed, keeps the API clean with automatic cleanup, and the PubSub can be extracted later.
Dependencies
No new external dependencies. Uses
collections(stdlib) forHashMap/HashSetin PubSub (imported asuse collections = "collections"per project qualified-import convention). Usestime(stdlib) for the ticker example.Files
Create
livery/info_receiver.pony— Public interface for sending messages to a connection:Structural typing:
_Connectionsatisfies this automatically by having theinfobehavior. Nois InfoReceiverneeded on_Connection.livery/pub_sub.pony— Topic-based publish-subscribe actor:Uses
collections.SetIs[InfoReceiver tag]for identity-based comparison of actor references.unsubscribe_alldoes a linear scan of all topics — acceptable for typical topic counts.examples/ticker/main.pony— Ticker-driven counter:Imports:
use "time",use "templates",use "json",use lori = "lori",use "../../livery".A
TickerViewsubscribes to a"tick"topic inmount. ATickeractor publishes a message to the topic every second usingTimers/Timer/TimerNotify. The view increments a counter inhandle_infoon each tick.Demonstrates:
handle_inforeceiving external messagesPubSubpassed to bothListenerand the externalTickeractorModify
livery/live_view.pony— Addhandle_info:Default no-op on the trait. Existing LiveViews (like
CounterView) don't need changes — they inherit the default.livery/socket.pony— Add connection reference and PubSub methods:New fields:
_self: InfoReceiver tag,_pub_sub: PubSub tagUpdated constructor:
New methods:
subscribecalls_pub_sub.subscribe(topic, _self).unsubscribecalls_pub_sub.unsubscribe(topic, _self).livery/_connection.pony— Addinfobehavior, PubSub integration, cleanup:New field:
let _pub_sub: PubSub tagUpdated constructor to accept and store
pub_sub: PubSub tag. Passthis(asInfoReceiver tag) andpub_subto the Socket constructor:In this constructor,
thisis_Connection ref(field initializers on the actor ensure all fields are defined before the constructor body runs, sothishasrefcapability)._Connection refis passable asInfoReceiver tagbecauserefis a subtype oftagand_Connectionstructurally satisfiesInfoReceivervia itsinfobehavior.New behavior:
When
_viewisNone(pre-mount), the message is silently dropped. This matches the existingon_text_messagepattern.Updated
on_closed:Calls
unsubscribe_allto clean up any PubSub subscriptions. No tracking needed — PubSub scans its own topic map.livery/listener.pony— Accept PubSub parameter:Updated constructor signature and field:
Pass
_pub_subto_Connectionin_on_accept:livery/livery.pony— Update package docstring to mentionhandle_info,PubSub,InfoReceiver, andSocket.self().livery/_test.pony— New tests + updates:New PubSub tests (async):
_TestPubSubDeliver— subscribe then publish delivers to subscriber_TestPubSubNoSubscribers— publish to empty topic doesn't crash_TestPubSubUnsubscribe— unsubscribe stops delivery_TestPubSubUnsubscribeAll— removes subscriber from all topics_TestPubSubMultipleSubscribers— all subscribers on a topic receiveNew Socket tests:
_TestSocketSubscribeDeliver— create PubSub, create Socket with InfoReceiver, callsocket.subscribe(topic), publish to topic, verify InfoReceiver receives the message. Tests the end-to-end wiring from Socket.subscribe through PubSub to InfoReceiver.info.These use
h.long_test()with test helper actors:actor \nodoc\ _TestInfoReceiver is InfoReceiver— receives messages, callsh.complete(true)on receiptactor \nodoc\ _DummyInfoReceiver is InfoReceiver— no-op receiver for tests that need a placeholderFor negative tests (verifying non-delivery after unsubscribe), use a two-subscriber pattern: subscribe both a "should not receive" receiver and a sentinel receiver, unsubscribe the first, publish, and have the sentinel verify delivery happened (proving publish was processed) while confirming the unsubscribed receiver got nothing.
Updated tests:
_TestSocketPushEvent— update Socket constructor call to include_DummyInfoReceiverand a freshPubSubas the new required parametersNot tested in Phase 2:
_Connection.infobehavior (the full path:info->handle_info-> assigns change -> re-render -> send HTML) requires WebSocket client infrastructure to test. Same as Phase 1, connection actor integration tests are deferred until an end-to-end test harness exists. The PubSub and Socket tests verify the plumbing up to theInfoReceiver.infocall; the internal_Connectiondispatch follows the same_maybe_rerenderpath already used byon_text_message.examples/counter/main.pony— UpdateListenercall to includePubSub:The counter doesn't use PubSub, but the
ListenerAPI now requires it.examples/README.md— Add ticker example description.CLAUDE.md— Update to reflect Phase 2 changes:InfoReceiver,PubSub,Socket.self(),Socket.subscribe(topic),Socket.unsubscribe(topic),LiveView.handle_info_Connection.infobehavior and PubSub cleanup inon_closedinfo_receiver.pony,pub_sub.pony,examples/ticker/No Changes
livery/assigns.ponylivery/factory.ponylivery/router.ponylivery/_wire_protocol.pony— No new wire messages (handle_info is server-side only)livery/_unreachable.ponyArchitecture
Message Flow
Capabilities
InfoReceiverisinterface tag— shareable across actors, can only call behaviorsPubSubis an actor — all external references aretagSocketstoresInfoReceiver tagandPubSub tagas fields — both are sendable, safe within therefclass_ConnectionsatisfiesInfoReceiverstructurally (hasbe info(message: Any val))_Connection's constructor,thisisref(field initializers ensure all fields are defined).refis a subtype oftag, sothisis passable asInfoReceiver tagCleanup
When a WebSocket connection closes:
on_closedon_Connection_Connectioncalls_pub_sub.unsubscribe_all(this)Build and Test
What's Not in Phase 2
terminatelifecycle callback on LiveView (not needed with auto-cleanup; can be added later if users need custom disconnect logic)lv-change/lv-submit(Phase 3)Beta Was this translation helpful? Give feedback.
All reactions