local_bus 0.2.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f435ed1460835bdd4b50311c366c158f4dda0381038368cd325a13c8ceace3b9
4
- data.tar.gz: 937b58aa97ed2e9ca2f65eb02b6c401f9e93c4d590aff5966816dd6dd47be8a9
3
+ metadata.gz: f6f1119ac19b5642c84099312240ce0baf8320b42098cb48c918b627385a5aff
4
+ data.tar.gz: 936a119be41c3885de3d27bf7a68effb07bbda58fe0856e8ca7043b76bfbfe47
5
5
  SHA512:
6
- metadata.gz: 74828920b50a51ae3fc00583dee61657d88cadd62482172fc34c1e54e935a652306716242c9152d6a5f35e9660f6d52930c6840966dc281f158c391666588cf7
7
- data.tar.gz: d7c86027a16144325f3ea4cd3e65f41ea55a56ac6b75e309a75a5397cd7042372002783948f64b6e76d3cdbb0200a1cd0b62be3ec8c632b3639c5c33b0a42a76
6
+ metadata.gz: ad441d27aacd1447fd9cebc7aaa5b55ab79b75eb6ce8ed756fc6f11d8b77203412796ce724067707e4c859fd28c384c16c2a6abe56139e4df3a727bd76cc104b
7
+ data.tar.gz: be8cc2ed7325347e6b3447777144dce7992fdc07385f0e20af8cfe300834c7cfe651fd7b6b0ffc9bdd797fb439ad702095220e8caa5a3c20651deae647fc97a0
data/README.md CHANGED
@@ -1,300 +1,386 @@
1
- <p align="center">
2
- <a href="http://blog.codinghorror.com/the-best-code-is-no-code-at-all/">
3
- <img alt="Lines of Code" src="https://img.shields.io/badge/loc-328-47d299.svg" />
4
- </a>
5
- <a href="https://rubygems.org/gems/local_bus">
6
- <img alt="GEM Version" src="https://img.shields.io/gem/v/local_bus">
7
- </a>
8
- <a href="https://rubygems.org/gems/local_bus">
9
- <img alt="GEM Downloads" src="https://img.shields.io/gem/dt/local_bus">
10
- </a>
11
- <a href="https://github.com/hopsoft/local_bus/actions">
12
- <img alt="Tests" src="https://github.com/hopsoft/local_bus/actions/workflows/tests.yml/badge.svg" />
13
- </a>
14
- <a href="https://github.com/testdouble/standard">
15
- <img alt="Ruby Style" src="https://img.shields.io/badge/style-standard-168AFE?logo=ruby&logoColor=FE1616" />
16
- </a>
17
- <a href="https://github.com/sponsors/hopsoft">
18
- <img alt="Sponsors" src="https://img.shields.io/github/sponsors/hopsoft?color=eb4aaa&logo=GitHub%20Sponsors" />
19
- </a>
20
- <a href="https://twitter.com/hopsoft">
21
- <img alt="Twitter Follow" src="https://img.shields.io/twitter/url?label=%40hopsoft&style=social&url=https%3A%2F%2Ftwitter.com%2Fhopsoft">
22
- </a>
23
- </p>
1
+ [![Lines of Code](https://img.shields.io/badge/loc-365-47d299.svg)](http://blog.codinghorror.com/the-best-code-is-no-code-at-all/)
2
+ [![GEM Version](https://img.shields.io/gem/v/local_bus)](https://rubygems.org/gems/local_bus)
3
+ [![GEM Downloads](https://img.shields.io/gem/dt/local_bus)](https://rubygems.org/gems/local_bus)
4
+ [![Tests](https://github.com/hopsoft/local_bus/actions/workflows/tests.yml/badge.svg)](https://github.com/hopsoft/local_bus/actions)
5
+ [![Ruby Style](https://img.shields.io/badge/style-standard-168AFE?logo=ruby&logoColor=FE1616)](https://github.com/testdouble/standard)
6
+ [![Sponsors](https://img.shields.io/github/sponsors/hopsoft?color=eb4aaa&logo=GitHub%20Sponsors)](https://github.com/sponsors/hopsoft)
7
+ [![Twitter Follow](https://img.shields.io/twitter/url?label=%40hopsoft&style=social&url=https%3A%2F%2Ftwitter.com%2Fhopsoft)](https://twitter.com/hopsoft)
24
8
 
25
9
  # LocalBus
26
10
 
27
- LocalBus is a lightweight pub/sub system for Ruby that helps organize and simplify intra-process communication.
11
+ ### A lightweight single-process pub/sub system that enables clean, decoupled interactions.
12
+
13
+ > [!TIP]
14
+ > At under 400 lines of code. The LocalBus source can be reviewed quickly to grok its implementation and internals.
15
+
16
+ ## Why LocalBus?
17
+
18
+ A message bus (or enterprise service bus) is an architectural pattern that enables different parts of an application to communicate without direct knowledge of each other.
19
+ Think of it as a smart postal service for your application - components can send messages to topics, and other components can listen for those messages, all without knowing about each other directly.
20
+
21
+ Even within a single process, this pattern offers powerful benefits:
22
+
23
+ - **Decouple Components**: Break complex systems into maintainable parts that can evolve independently
24
+ - **Single Responsibility**: Each component can focus on its core task without handling cross-cutting concerns
25
+ - **Flexible Architecture**: Easily add new features by subscribing to existing events without modifying original code
26
+ - **Control Flow**: Choose immediate or background processing based on your needs
27
+ - **Testing**: Simplified testing as components can be tested in isolation
28
+ - **Stay Reliable**: Built-in error handling and thread safety
29
+ - **Non-Blocking**: Efficient message processing with async I/O
28
30
 
29
31
  <!-- Tocer[start]: Auto-generated, don't remove. -->
30
32
 
31
33
  ## Table of Contents
32
34
 
33
- - [Why LocalBus?](#why-localbus)
35
+ - [Key Benefits](#key-benefits)
36
+ - [Performance and Efficiency](#performance-and-efficiency)
37
+ - [Ease of Use](#ease-of-use)
38
+ - [Decoupling and Modularity](#decoupling-and-modularity)
39
+ - [Reliability and Safety](#reliability-and-safety)
40
+ - [Use Cases](#use-cases)
41
+ - [Key Components](#key-components)
42
+ - [Bus](#bus)
43
+ - [Station](#station)
44
+ - [LocalBus](#localbus)
34
45
  - [Installation](#installation)
35
- - [Quick Start](#quick-start)
36
- - [Interfaces](#interfaces)
37
- - [Bus (immediate processing)](#bus-immediate-processing)
38
- - [Station (background processing)](#station-background-processing)
39
- - [Advanced Usage & Considerations](#advanced-usage--considerations)
46
+ - [Requirements](#requirements)
47
+ - [Usage](#usage)
48
+ - [LocalBus](#localbus-1)
49
+ - [Bus](#bus-1)
50
+ - [Station](#station-1)
51
+ - [Advanced Usage](#advanced-usage)
40
52
  - [Concurrency Controls](#concurrency-controls)
41
- - [Bus Interface (Async)](#bus-interface-async)
42
- - [Station Interface (Thread Pool)](#station-interface-thread-pool)
43
- - [Error Handling & Recovery](#error-handling--recovery)
53
+ - [Bus](#bus-2)
54
+ - [Station](#station-2)
55
+ - [Message Priority](#message-priority)
56
+ - [Error Handling](#error-handling)
44
57
  - [Memory Considerations](#memory-considerations)
45
58
  - [Blocking Operations](#blocking-operations)
46
59
  - [Shutdown & Cleanup](#shutdown--cleanup)
47
60
  - [Limitations](#limitations)
61
+ - [Demos & Benchmarks](#demos--benchmarks)
48
62
  - [See Also](#see-also)
49
- - [Sponsors](#sponsors)
50
63
 
51
64
  <!-- Tocer[finish]: Auto-generated, don't remove. -->
52
65
 
53
- ## Why LocalBus?
66
+ ## Key Benefits
54
67
 
55
- A message bus (or enterprise service bus) is an architectural pattern that enables different parts of an application to communicate without direct knowledge of each other. Think of it as a smart postal service for your application - components can send messages to topics, and other components can listen for those messages, all without knowing about each other directly.
68
+ LocalBus offers several advantages that make it an attractive choice for Ruby developers looking to implement a pub/sub system within a single process:
56
69
 
57
- Even within a single process, this pattern offers powerful benefits:
70
+ ### Performance and Efficiency
58
71
 
59
- - **Decouple Components**: Break complex systems into maintainable parts that can evolve independently
60
- - **Single Responsibility**: Each component can focus on its core task without handling cross-cutting concerns
61
- - **Flexible Architecture**: Easily add new features by subscribing to existing events without modifying original code
62
- - **Control Flow**: Choose immediate or background processing based on your needs
63
- - **Testing**: Simplified testing as components can be tested in isolation
64
- - **Stay Reliable**: Built-in error handling and thread safety
65
- - **Non-Blocking**: Efficient message processing with async I/O
72
+ - **Non-Blocking I/O:** Leveraging the power of the `Async` library, LocalBus ensures efficient message processing without blocking the main thread, leading to improved performance in I/O-bound applications.
73
+ - **Optimized Resource Usage:** By using semaphores and thread pools, LocalBus efficiently manages system resources, allowing for high concurrency without overwhelming the system.
66
74
 
67
- ## Installation
75
+ ### Ease of Use
68
76
 
69
- ```sh
70
- bundle add local_bus
71
- ```
77
+ - **Simple Setup:** With straightforward installation and intuitive API, LocalBus allows developers to quickly integrate pub/sub capabilities into their applications.
78
+ - **Minimal Configuration:** Default settings are optimized for most use cases, reducing the need for complex configurations.
72
79
 
73
- ## Quick Start
80
+ ### Decoupling and Modularity
74
81
 
75
- ### Interfaces
82
+ - **Component Isolation:** LocalBus enables clean separation of concerns by allowing components to communicate through messages without direct dependencies or tight coupling.
83
+ - **Scalable Architecture:** Easily extend your application by adding new subscribers to existing topics, facilitating the addition of new features without modifying existing code.
76
84
 
77
- - **Bus**: Single-threaded, immediate message delivery using Socketry Async with non-blocking I/O operations
78
- - **Station**: Multi-threaded message queuing powered by Concurrent Ruby's thread pool, processing messages through the Bus without blocking the main thread
85
+ ### Reliability and Safety
79
86
 
80
- Both interfaces ensure optimal performance:
87
+ - **Built-in Error Handling:** LocalBus includes error boundaries to ensure that failures in one subscriber do not affect others, maintaining system stability.
88
+ - **Thread Safety:** Designed with concurrency in mind, LocalBus provides thread-safe operations to prevent race conditions and ensure data integrity.
81
89
 
82
- - Bus leverages async I/O to prevent blocking on network or disk operations
83
- - Station offloads work to a managed thread pool, keeping the main thread responsive
84
- - Both interfaces support an explicit `wait` for subscribers
90
+ ## Use Cases
85
91
 
86
- ### Bus (immediate processing)
92
+ LocalBus is versatile and can be applied to various scenarios within a Ruby application. Here are some common use cases and examples:
87
93
 
88
- Best for real-time operations like logging, metrics, and state updates.
94
+ <details>
95
+ <summary><b>Decoupled Communication</b></summary>
96
+ <br>
97
+ Facilitate communication between different parts of a component-based architecture without tight coupling.
89
98
 
90
99
  ```ruby
91
- bus = LocalBus::Bus.new # ... or LocalBus.instance.bus
92
-
93
- bus.subscribe "user.created" do |message|
94
- AuditLog.record(message.payload)
95
- true
100
+ # Component A subscribes to order creation events
101
+ LocalBus.subscribe "order.created" do |message|
102
+ InventoryService.update_stock message.payload[:order_id]
96
103
  end
97
104
 
98
- # publish returns a promise-like object that resolves to subscribers
99
- result = bus.publish("user.created", user_id: 123)
100
-
101
- result.wait # blocks until all subscribers complete
102
- result.value # blocks and waits until all subscribers complete and returns the subscribers
105
+ # Component B publishes an order creation event
106
+ LocalBus.publish "order.created", order_id: 789
103
107
  ```
104
108
 
105
- Subscribe with an explicit `callable`.
109
+ </details>
110
+
111
+ <details>
112
+ <summary><b>Real-Time Notifications</b></summary>
113
+ <br>
114
+ Use LocalBus to send real-time notifications to users when specific events occur, such as user sign-ups or order completions.
106
115
 
107
116
  ```ruby
108
- callable = ->(message) { "Received message: #{message.payload}" }
109
- LocalBus.instance.bus.subscribe "user.created", callable: callable
117
+ # Subscribe to user sign-up events
118
+ LocalBus.subscribe "user.signed_up" do |message|
119
+ NotificationService.send_welcome_email message.payload[:user_id]
120
+ end
110
121
 
111
- subscribers = LocalBus.instance.bus.publish("user.created", user_id: 123).value
112
- # => [#<LocalBus::Subscriber:0x0000000126b7cf38 ...>]
122
+ # Publish a user sign-up event
123
+ LocalBus.publish "user.signed_up", user_id: 123
124
+ ```
113
125
 
114
- subscribers.first.value
115
- # => "Received message: {:user_id=>123}"
126
+ </details>
116
127
 
117
- # you can use any object that responds to #call
118
- class ExampleCallable
119
- def call(message)
120
- "Received message: #{message.payload}"
121
- end
128
+ <details>
129
+ <summary><b>Background Processing</b></summary>
130
+ <br>
131
+ Offload non-critical tasks to be processed in the background, such as sending emails or generating reports.
132
+
133
+ ```ruby
134
+ # Subscribe to report generation requests
135
+ LocalBus.subscribe "report.generate" do |message|
136
+ ReportService.generate message.payload[:report_id]
122
137
  end
123
138
 
124
- LocalBus.instance.bus.subscribe "user.created", callable: ExampleCallable.new
125
- subscribers = LocalBus.instance.bus.publish("user.created", user_id: 123).value
126
- # => [#<LocalBus::Subscriber:0x0000000126b7cf38 ...>]
139
+ # Publish a report generation request
140
+ LocalBus.publish "report.generate", report_id: 456
141
+ ```
142
+
143
+ </details>
144
+
145
+ ## Key Components
146
+
147
+ ### Bus
148
+
149
+ The Bus acts as a direct transport mechanism for messages, akin to placing a passenger directly onto a bus.
150
+ When a message is published to the Bus, it is immediately delivered to all subscribers, ensuring prompt execution of tasks.
151
+ This is achieved through non-blocking I/O operations, which allow the Bus to handle multiple tasks efficiently without blocking the main thread.
152
+
153
+ > [!NOTE]
154
+ > While the Bus uses asynchronous operations to optimize performance,
155
+ > the actual processing of a message may still experience slight delays due to I/O wait times from prior messages.
156
+ > This means that while the Bus aims for immediate processing, the nature of asynchronous operations can introduce some latency.
157
+
158
+ ### Station
159
+
160
+ The Station serves as a queuing system for messages, similar to a bus station where passengers wait for their bus.
127
161
 
128
- subscribers.first.value
129
- # => "Received message: {:user_id=>123}"
162
+ When a message is published to the Station, it is queued and processed at a later time, allowing for deferred execution.
163
+ This is particularly useful for tasks that can be handled later.
164
+
165
+ The Station employs a thread pool to manage message processing, enabling high concurrency and efficient resource utilization.
166
+ Messages can also be prioritized, ensuring that higher-priority tasks are processed first.
167
+
168
+ > [!NOTE]
169
+ > While the Station provides a robust mechanism for background processing,
170
+ > it's important to understand that the exact timing of message processing is not controlled by the publisher,
171
+ > and messages will be processed as resources become available.
172
+
173
+ ### LocalBus
174
+
175
+ The LocalBus class serves as the primary interface to the library, providing a convenient singleton pattern for accessing both Bus and Station functionality.
176
+ It exposes singleton instances of both Bus and Station providing a simplified API for common pub/sub operations.
177
+
178
+ By default, LocalBus delegates to the Station singleton for all pub/sub operations, making it ideal for background processing scenarios.
179
+ This means that when you use `LocalBus.publish` or `LocalBus.subscribe`, you're actually working with default Station, benefiting from its queuing and thread pool capabilities.
180
+
181
+ ## Installation
182
+
183
+ ```bash
184
+ bundle add local_bus
130
185
  ```
131
186
 
132
- ### Station (background processing)
187
+ ### Requirements
133
188
 
134
- Best for async operations like emails, notifications, and resource-intensive tasks.
189
+ - Ruby `>= 3.0`
135
190
 
136
- ```ruby
137
- station = LocalBus::Station.new # ... or LocalBus.instance.station
191
+ ## Usage
138
192
 
139
- station.subscribe "email.welcome" do |message|
140
- WelcomeMailer.deliver(message.payload[:user_id])
141
- true
193
+ ### LocalBus
194
+
195
+ ```ruby
196
+ LocalBus.subscribe "user.created" do |message|
197
+ # business logic (e.g. API calls, database queries, disk operations, etc.)
198
+ "It worked!"
142
199
  end
143
200
 
144
- # Returns a Promise or Future that resolves to subscribers
145
- result = station.publish("email.welcome", user_id: 123)
201
+ message = LocalBus.publish("user.created", user_id: 123)
202
+ message.wait # blocks until all subscribers complete
203
+ message.subscribers # blocks and waits until all subscribers complete and returns the subscribers
204
+ #=> [#<LocalBus::Subscriber:0x0000000120f75c30 ...>]
205
+
206
+ message.subscribers.first.value
207
+ #=> "It worked!"
146
208
 
147
- result.wait # blocks until all subscribers complete
148
- result.value # blocks and waits until all subscribers complete and returns the subscribers
209
+ # subscribe with any object that responds to `#call`.
210
+ worker = ->(message) do
211
+ # business logic (e.g. API calls, database queries, disk operations, etc.)
212
+ "It worked!"
213
+ end
214
+ LocalBus.subscribe "user.created", callable: worker
149
215
  ```
150
216
 
151
- Subscribe with an explicit `callable`.
217
+ ### Bus
152
218
 
153
219
  ```ruby
154
- callable = ->(message) { "Received message: #{message.payload}" }
155
- LocalBus.instance.station.subscribe "email.welcome", callable: callable
220
+ bus = LocalBus::Bus.new # ... or LocalBus.instance.bus
156
221
 
157
- subscribers = LocalBus.instance.station.publish("email.welcome", user_id: 123).value
158
- # => [#<LocalBus::Subscriber:0x0000000126b7cf38 ...>]
222
+ # register a subscriber
223
+ bus.subscribe "user.created" do |message|
224
+ # business logic (e.g. API calls, database queries, disk operations, etc.)
225
+ "It worked!"
226
+ end
227
+
228
+ message = bus.publish("user.created", user_id: 123)
229
+ message.wait # blocks until all subscribers complete
230
+ message.subscribers # waits and returns the subscribers
231
+ #=> [#<LocalBus::Subscriber:0x000000012bbb79a8 ...>]
159
232
 
160
- subscribers.first.value
161
- # => "Received message: {:user_id=>123}"
233
+ message.subscribers.first.value
234
+ #=> "It worked!"
235
+ ```
162
236
 
163
- # you can use any object that responds to #call
164
- class ExampleCallable
165
- def call(message)
166
- "Received message: #{message.payload}"
167
- end
237
+ ### Station
238
+
239
+ ```ruby
240
+ station = LocalBus::Station.new # ... or LocalBus.instance.station
241
+
242
+ station.subscribe "user.created" do |message|
243
+ # business logic (e.g. API calls, database queries, disk operations, etc.)
244
+ "It worked!"
168
245
  end
169
246
 
170
- LocalBus.instance.station.subscribe "email.welcome", callable: ExampleCallable.new
171
- subscribers = LocalBus.instance.station.publish("email.welcome", user_id: 123).value
172
- # => [#<LocalBus::Subscriber:0x0000000126b7cf38 ...>]
247
+ message = station.publish("user.created", user_id: 123)
248
+ message.wait # blocks until all subscribers complete
249
+ message.subscribers # blocks and waits until all subscribers complete and returns the subscribers
250
+ #=> [#<LocalBus::Subscriber:0x00000001253156e8 ...>]
173
251
 
174
- subscribers.first.value
175
- # => "Received message: {:user_id=>123}"
252
+ message.subscribers.first.value
253
+ #=> "It worked!"
176
254
  ```
177
255
 
178
- ## Advanced Usage & Considerations
256
+ ## Advanced Usage
179
257
 
180
258
  ### Concurrency Controls
181
259
 
182
- #### Bus Interface (Async)
260
+ #### Bus
183
261
 
184
- The Bus interface uses Async's Semaphore to limit resource consumption:
262
+ The Bus leverages Async's Semaphore to limit resource consumption.
263
+ The configured `concurrency` limits how many operations can run at once.
185
264
 
186
265
  ```ruby
187
- # Configure concurrency limits for the Bus
188
- bus = LocalBus::Bus.new(max_concurrency: 10)
189
-
190
- # The semaphore ensures only N concurrent operations run at once
191
- bus.subscribe "resource.intensive" do |message|
192
- # Only 10 of these will run concurrently
193
- perform_intensive_operation(message)
194
- end
266
+ # Configure concurrency limits for the Bus (default: Etc.nprocessors)
267
+ bus = LocalBus::Bus.new(concurrency: 10)
195
268
  ```
196
269
 
197
- When the max concurrency limit is reached, new publish operations will wait until a slot becomes available. This prevents memory bloat but means you should be mindful of timeouts in your subscribers.
270
+ > [!NOTE]
271
+ > When the max concurrency limit is reached, new publish operations will wait until a slot becomes available.
272
+ > This helps to ensure we don't over utilize system resources.
198
273
 
199
- #### Station Interface (Thread Pool)
274
+ #### Station
200
275
 
201
- The Station interface uses Concurrent Ruby's fixed thread pool with a fallback policy:
276
+ The Station uses a thread pool for multi-threaded message processing.
277
+ You can configure the queue size and the number of threads used to process messages.
202
278
 
203
279
  ```ruby
204
- # Configure the thread pool size for the Station
280
+ # Configure the Station
205
281
  station = LocalBus::Station.new(
206
- max_queue: 5_000, # Maximum number of queued items
207
- max_threads: 10, # Maximum pool size
208
- fallback_policy: :caller_runs # Runs on calling thread
282
+ limit: 5_000, # max number of pending messages (default: 10_000)
283
+ threads: 10, # max number of processing threads (default: Etc.nprocessors)
209
284
  )
210
285
  ```
211
286
 
212
- The fallback policy determines behavior when the thread pool is saturated:
287
+ ##### Message Priority
213
288
 
214
- - `:caller_runs` - Executes the task in the publishing thread (can block)
215
- - `:abort` - Raises an error
216
- - `:discard` - Silently drops the task
289
+ The Station supports assigning a priority to each message.
290
+ Messages with a higher priority are processed before lower priority messages.
217
291
 
218
- ### Error Handling & Recovery
292
+ ```ruby
293
+ LocalBus.publish("default") # 3rd to process
294
+ LocalBus.publish("important", priority: 5) # 2nd to process
295
+ LocalBus.publish("critical", priority: 10) # 1st to process
296
+ ```
219
297
 
220
- Both interfaces implement error boundaries to prevent individual subscriber failures from affecting others:
298
+ ### Error Handling
299
+
300
+ Error boundaries prevent individual subscriber failures from affecting other subscribers.
221
301
 
222
302
  ```ruby
223
- bus.subscribe "user.created" do |message|
303
+ LocalBus.subscribe "user.created" do |message|
224
304
  raise "Something went wrong!"
225
- true # Never reached
305
+ # never reached (business logic...)
226
306
  end
227
307
 
228
- bus.subscribe "user.created" do |message|
229
- # This still executes despite the error above
230
- notify_admin(message)
231
- true
308
+ LocalBus.subscribe "user.created" do |message|
309
+ # This still executes even though the other subscriber has an error
310
+ # business logic (e.g. API calls, database queries, disk operations, etc.)
232
311
  end
233
312
 
234
313
  # The publish operation completes with partial success
235
- result = bus.publish("user.created", user_id: 123)
236
- result.wait
237
- errored_subscribers = result.value.select(&:error)
314
+ message = LocalBus.publish("user.created", user_id: 123)
315
+ errored_subscribers = message.subscribers.select(&:errored?)
316
+ #=> [#<LocalBus::Subscriber:0x000000011ebbcaf0 ...>]
317
+
318
+ errored_subscribers.first.error
319
+ #=> #<LocalBus::Subscriber::Error: Invocation failed! Something went wrong!>
238
320
  ```
239
321
 
322
+ > [!IMPORTANT]
323
+ > It's up to you to check message subscribers and handle errors appropriately.
324
+
240
325
  ### Memory Considerations
241
326
 
242
- Messages are held in memory until all subscribers complete processing. For the Station interface, this includes time spent in the thread pool queue. Consider this when publishing large payloads or during high load:
327
+ Messages are held in memory until all subscribers have completed.
328
+ Consider this when publishing large payloads or during high load scenarios.
243
329
 
244
330
  ```ruby
245
- # Memory-efficient publishing of large datasets
331
+ # memory-efficient publishing of large datasets
246
332
  large_dataset.each_slice(100) do |batch|
247
- station.publish("data.process", items: batch).wait
333
+ message = LocalBus.publish("data.process", items: batch)
334
+ message.wait # wait before processing more messages
248
335
  end
249
336
  ```
250
337
 
251
338
  ### Blocking Operations
252
339
 
253
- The Bus interface uses non-blocking I/O but can still be blocked by CPU-intensive operations:
340
+ LocalBus facilitates non-blocking I/O but bottlenecks can still be triggered by CPU-intensive operations.
254
341
 
255
342
  ```ruby
256
- # Bad - blocks the event loop
257
- bus.subscribe "cpu.intensive" do |message|
258
- perform_heavy_calculation(message)
259
- end
260
-
261
- # Better - offload to Station for CPU-intensive work
262
- station.subscribe "cpu.intensive" do |message|
263
- perform_heavy_calculation(message)
343
+ LocalBus.subscribe "cpu.intensive" do |message|
344
+ # CPU bound operation can trigger a bottleneck
264
345
  end
265
346
  ```
266
347
 
267
348
  ### Shutdown & Cleanup
268
349
 
269
- LocalBus does its best to handle graceful shutdown when the process exits, and works to ensure published messages are processed.
270
- However, it's possible that some messages may be lost when the process exits.
350
+ The Station delays process exit in an attempt to flush the queue and avoid dropped messages.
351
+ This delay can be configured via the `:wait` option in the constructor (default: 5).
271
352
 
272
- Factor for potential message loss when designing your system.
273
- For example, idempotency _(i.e. messages that can be re-published without unintended side effects)_.
353
+ > [!IMPORTANT]
354
+ > This wait time allows for processing pending messages at exit, but is not guaranteed.
355
+ > Factor for potential message loss when designing your system.
356
+ > For example, idempotency _i.e. messages that can be re-published without unintended side effects_.
274
357
 
275
358
  ### Limitations
276
359
 
277
- - The Bus interface is single-threaded - long-running subscribers can impact latency
278
- - The Station interface may drop messages if configured with `:discard` fallback policy
279
- - No persistence - pending messages may be lost on process restart
280
- - No distributed support - communication limited to single process
281
- - Large payloads can impact memory usage, especially under high load
282
- - No built-in retry mechanism for failed subscribers
360
+ - The Bus is single-threaded - long-running or CPU-bound subscribers can impact latency
361
+ - The Station may drop messages at process exit _(messages are not persisted between process restarts)_
362
+ - No distributed support - limited to single process _(intra-process)_
363
+ - Large message payloads may impact memory usage, especially under high load
364
+ - No built-in retry mechanism for failed subscribers _(subscribers expose an error property, but you'll need to check and handle such errors)_
283
365
 
284
366
  Consider these limitations when designing your system architecture.
285
367
 
368
+ ### Demos & Benchmarks
369
+
370
+ The project includes demo scripts that showcase concurrent processing capabilities:
371
+
372
+ ```bash
373
+ bin/demo-bus # demonstrates Bus performance
374
+ bin/demo-station # demonstrates Station performance
375
+ ```
376
+
377
+ Both demos simulate I/O-bound operations _(1 second latency per subscriber)_ to show how LocalBus handles concurrent processing.
378
+
379
+ For example,
380
+ LocalBus can process 10 messages with 10 I/O-bound subscribers each in **~1 second instead of 100 seconds**,
381
+ on a 10-core system.
382
+
286
383
  ## See Also
287
384
 
288
385
  - [Message Bus](https://github.com/discourse/message_bus) - A reliable and robust messaging bus for Ruby and Rack
289
386
  - [Wisper](https://github.com/krisleech/wisper) - A micro library providing Ruby objects with Publish-Subscribe capabilities
290
-
291
- ## Sponsors
292
-
293
- <p align="center">
294
- <em>Proudly sponsored by</em>
295
- </p>
296
- <p align="center">
297
- <a href="https://www.clickfunnels.com?utm_source=hopsoft&utm_medium=open-source&utm_campaign=local_bus">
298
- <img src="https://images.clickfunnel.com/uploads/digital_asset/file/176632/clickfunnels-dark-logo.svg" width="575" />
299
- </a>
300
- </p>