Class: FastMcpPubsub::Service
- Inherits:
-
Object
- Object
- FastMcpPubsub::Service
- Extended by:
- Delivery
- Defined in:
- lib/fast_mcp_pubsub/service.rb
Overview
Core PostgreSQL NOTIFY/LISTEN service for broadcasting MCP messages across Puma workers
Constant Summary collapse
- MAX_PAYLOAD_SIZE =
PostgreSQL NOTIFY limit is 8000 bytes, leave some margin
7800
Class Attribute Summary collapse
-
.dedicated_connection ⇒ Object
readonly
Returns the value of attribute dedicated_connection.
-
.listener_thread ⇒ Object
readonly
Returns the value of attribute listener_thread.
-
.shutdown_requested ⇒ Object
Returns the value of attribute shutdown_requested.
Class Method Summary collapse
-
.broadcast(message, client_id = nil) ⇒ Object
Publishes one MCP message to the cluster.
- .start_listener ⇒ Object
- .stop_listener ⇒ Object
Class Attribute Details
.dedicated_connection ⇒ Object (readonly)
Returns the value of attribute dedicated_connection.
11 12 13 |
# File 'lib/fast_mcp_pubsub/service.rb', line 11 def dedicated_connection @dedicated_connection end |
.listener_thread ⇒ Object (readonly)
Returns the value of attribute listener_thread.
11 12 13 |
# File 'lib/fast_mcp_pubsub/service.rb', line 11 def listener_thread @listener_thread end |
.shutdown_requested ⇒ Object
Returns the value of attribute shutdown_requested.
12 13 14 |
# File 'lib/fast_mcp_pubsub/service.rb', line 12 def shutdown_requested @shutdown_requested end |
Class Method Details
.broadcast(message, client_id = nil) ⇒ Object
Publishes one MCP message to the cluster.
client_id names the SSE client the message answers; nil means it is for everyone, which is what a genuine notification is. A response carries an id because every worker receives every NOTIFY, and without one the worker holding an unrelated session would write this answer into it.
20 21 22 23 24 25 26 27 28 |
# File 'lib/fast_mcp_pubsub/service.rb', line 20 def broadcast(, client_id = nil) envelope = envelope_for() envelope[:_pubsub_target] = client_id if client_id send_payload(envelope.to_json) rescue StandardError => e FastMcpPubsub.logger.error "FastMcpPubsub: Error broadcasting message: #{e.}" raise end |
.start_listener ⇒ Object
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'lib/fast_mcp_pubsub/service.rb', line 30 def start_listener unless FastMcpPubsub.config.enabled FastMcpPubsub.logger.info "FastMcpPubsub: Not starting listener - disabled in config for PID #{Process.pid}" return end if @listener_thread&.alive? FastMcpPubsub.logger.info "FastMcpPubsub: Listener already running for PID #{Process.pid}" return end FastMcpPubsub.logger.info "FastMcpPubsub: Starting listener thread for PID #{Process.pid}" @listener_thread = Thread.new do Thread.current.name = "fast-mcp-pubsub-listener" listen_loop end # Register shutdown hook at_exit { stop_listener } end |
.stop_listener ⇒ Object
52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/fast_mcp_pubsub/service.rb', line 52 def stop_listener # A thread that has already died on its own still leaves its reference # behind, and every caller reads that reference as "a listener is # running". Clearing it is part of stopping, not a separate errand. return @listener_thread = nil unless @listener_thread&.alive? FastMcpPubsub.logger.info "FastMcpPubsub: Stopping listener thread for PID #{Process.pid}" @shutdown_requested = true wake_listener join_listener discard_dedicated_connection @shutdown_requested = false end |