local_bus 0.1.2 → 0.3.0

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: 3abe4359b671f5e051a9e3b4e2cb0ac73ca9c00ef63c1622c158ebf4c116a1b5
4
- data.tar.gz: 408ea743c2b5b8d3a000a8db5b8207f6e3d90edfb3976b8bf1aad3ec11dde8ab
3
+ metadata.gz: fc19202ff3881f3519fd8cfe760b7b83f42fff39cb2b9658d2550fbd2b0bcca0
4
+ data.tar.gz: 2d2fc75beaa51eb84f2263dc82aa0049c3c2f51b24c32939f911c0b774ef74f9
5
5
  SHA512:
6
- metadata.gz: de71a197041a072f69ad37e971663983da3f77a00889c71afc622d382e74480bf5bef25ebfdc5d97762d4a15f385b0d63bdd929f67c6e455dc378340d3afda93
7
- data.tar.gz: fe165204058f9e5f9df6aa00d4f3d07db69478eeca3232a7204be758e0b6418800e903c576c927b04b0a958946e729c385a27341bb1f9168bcb2cb83198396db
6
+ metadata.gz: 44e7b2c60c2be6b5435cc1a652ee624502b775ee503662f582cb32d884e082cb47ee2b8159a27080e820530988aaa4cc5fc3e47f420629d9a0a0b3be7b0a665d
7
+ data.tar.gz: 647481ca9e89a0239e26ea622c8551757d991595f52a1ba3aac06a0ac03a7266a8f42253dbd8cec30311361d49a2ed2bdd80e95f62648365d95e817de2a34853
data/README.md CHANGED
@@ -1,57 +1,22 @@
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-364-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.
28
12
 
29
- <!-- Tocer[start]: Auto-generated, don't remove. -->
30
-
31
- ## Table of Contents
32
-
33
- - [Why LocalBus?](#why-localbus)
34
- - [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)
40
- - [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)
44
- - [Memory Considerations](#memory-considerations)
45
- - [Blocking Operations](#blocking-operations)
46
- - [Shutdown & Cleanup](#shutdown--cleanup)
47
- - [Limitations](#limitations)
48
- - [Sponsors](#sponsors)
49
-
50
- <!-- Tocer[finish]: Auto-generated, don't remove. -->
13
+ > [!TIP]
14
+ > At under 400 lines of code. The LocalBus source can be reviewed quickly to grok its implementation and internals.
51
15
 
52
16
  ## Why LocalBus?
53
17
 
54
- 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.
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.
55
20
 
56
21
  Even within a single process, this pattern offers powerful benefits:
57
22
 
@@ -63,237 +28,363 @@ Even within a single process, this pattern offers powerful benefits:
63
28
  - **Stay Reliable**: Built-in error handling and thread safety
64
29
  - **Non-Blocking**: Efficient message processing with async I/O
65
30
 
66
- ## Installation
31
+ <!-- Tocer[start]: Auto-generated, don't remove. -->
67
32
 
68
- ```sh
69
- bundle add local_bus
70
- ```
33
+ ## Table of Contents
71
34
 
72
- ## Quick Start
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)
45
+ - [Installation](#installation)
46
+ - [Requirements](#requirements)
47
+ - [Usage](#usage)
48
+ - [LocalBus](#localbus-1)
49
+ - [Bus](#bus-1)
50
+ - [Station](#station-1)
51
+ - [Advanced Usage](#advanced-usage)
52
+ - [Concurrency Controls](#concurrency-controls)
53
+ - [Bus](#bus-2)
54
+ - [Station](#station-2)
55
+ - [Message Priority](#message-priority)
56
+ - [Error Handling](#error-handling)
57
+ - [Memory Considerations](#memory-considerations)
58
+ - [Blocking Operations](#blocking-operations)
59
+ - [Shutdown & Cleanup](#shutdown--cleanup)
60
+ - [Limitations](#limitations)
61
+ - [Demos & Benchmarks](#demos--benchmarks)
62
+ - [See Also](#see-also)
73
63
 
74
- ### Interfaces
64
+ <!-- Tocer[finish]: Auto-generated, don't remove. -->
75
65
 
76
- - **Bus**: Single-threaded, immediate message delivery using Socketry Async with non-blocking I/O operations
77
- - **Station**: Multi-threaded message queuing powered by Concurrent Ruby's thread pool, processing messages through the Bus without blocking the main thread
66
+ ## Key Benefits
78
67
 
79
- Both interfaces ensure optimal performance:
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:
80
69
 
81
- - Bus leverages async I/O to prevent blocking on network or disk operations
82
- - Station offloads work to a managed thread pool, keeping the main thread responsive
83
- - Both interfaces support an explicit `wait` for subscribers
70
+ ### Performance and Efficiency
84
71
 
85
- ### Bus (immediate processing)
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.
86
74
 
87
- Best for real-time operations like logging, metrics, and state updates.
75
+ ### Ease of Use
88
76
 
89
- ```ruby
90
- bus = LocalBus::Bus.new # ... or LocalBus.instance.bus
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.
91
79
 
92
- bus.subscribe "user.created" do |message|
93
- AuditLog.record(message.payload)
94
- true
95
- end
80
+ ### Decoupling and Modularity
81
+
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.
84
+
85
+ ### Reliability and Safety
86
+
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.
89
+
90
+ ## Use Cases
91
+
92
+ LocalBus is versatile and can be applied to various scenarios within a Ruby application. Here are some common use cases and examples:
96
93
 
97
- # publish returns a promise-like object that resolves to subscribers
98
- result = bus.publish("user.created", user_id: 123)
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.
99
98
 
100
- result.wait # blocks until all subscribers complete
101
- result.value # blocks and waits until all subscribers complete and returns the subscribers
99
+ ```ruby
100
+ # Component A subscribes to order creation events
101
+ LocalBus.subscribe "order.created" do |message|
102
+ InventoryService.update_stock message.payload[:order_id]
103
+ end
104
+
105
+ # Component B publishes an order creation event
106
+ LocalBus.publish "order.created", order_id: 789
102
107
  ```
103
108
 
104
- 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.
105
115
 
106
116
  ```ruby
107
- callable = ->(message) { "Received message: #{message.payload}" }
108
- 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
109
121
 
110
- subscribers = LocalBus.instance.bus.publish("user.created", user_id: 123).value
111
- # => [#<LocalBus::Subscriber:0x0000000126b7cf38 ...>]
122
+ # Publish a user sign-up event
123
+ LocalBus.publish "user.signed_up", user_id: 123
124
+ ```
125
+
126
+ </details>
112
127
 
113
- subscribers.first.value
114
- # => "Received message: {:user_id=>123}"
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.
115
132
 
116
- # you can use any object that responds to #call
117
- class ExampleCallable
118
- def call(message)
119
- "Received message: #{message.payload}"
120
- end
133
+ ```ruby
134
+ # Subscribe to report generation requests
135
+ LocalBus.subscribe "report.generate" do |message|
136
+ ReportService.generate message.payload[:report_id]
121
137
  end
122
138
 
123
- LocalBus.instance.bus.subscribe "user.created", callable: ExampleCallable.new
124
- subscribers = LocalBus.instance.bus.publish("user.created", user_id: 123).value
125
- # => [#<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.
161
+
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
126
174
 
127
- subscribers.first.value
128
- # => "Received message: {:user_id=>123}"
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
129
185
  ```
130
186
 
131
- ### Station (background processing)
187
+ ### Requirements
132
188
 
133
- Best for async operations like emails, notifications, and resource-intensive tasks.
189
+ - Ruby `>= 3.0`
134
190
 
135
- ```ruby
136
- station = LocalBus::Station.new # ... or LocalBus.instance.station
191
+ ## Usage
192
+
193
+ ### LocalBus
137
194
 
138
- station.subscribe "email.welcome" do |message|
139
- WelcomeMailer.deliver(message.payload[:user_id])
140
- true
195
+ ```ruby
196
+ LocalBus.subscribe "user.created" do |message|
197
+ # business logic (e.g. API calls, database queries, disk operations, etc.)
198
+ "It worked!"
141
199
  end
142
200
 
143
- # Returns a Promise or Future that resolves to subscribers
144
- 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 ...>]
145
205
 
146
- result.wait # blocks until all subscribers complete
147
- result.value # blocks and waits until all subscribers complete and returns the subscribers
206
+ message.subscribers.first.value
207
+ #=> "It worked!"
148
208
  ```
149
209
 
150
- Subscribe with an explicit `callable`.
210
+ ### Bus
151
211
 
152
212
  ```ruby
153
- callable = ->(message) { "Received message: #{message.payload}" }
154
- LocalBus.instance.station.subscribe "email.welcome", callable: callable
213
+ bus = LocalBus::Bus.new # ... or LocalBus.instance.bus
155
214
 
156
- subscribers = LocalBus.instance.station.publish("email.welcome", user_id: 123).value
157
- # => [#<LocalBus::Subscriber:0x0000000126b7cf38 ...>]
215
+ # register a subscriber
216
+ bus.subscribe "user.created" do |message|
217
+ # business logic (e.g. API calls, database queries, disk operations, etc.)
218
+ end
219
+
220
+ message = bus.publish("user.created", user_id: 123)
221
+ message.wait # blocks until all subscribers complete
222
+ message.subscribers # waits and returns the subscribers
223
+ #=> [#<LocalBus::Subscriber:0x000000012bbb79a8 ...>]
224
+
225
+ # subscribe with any object that responds to `#call`.
226
+ worker = ->(message) do
227
+ # business logic (e.g. API calls, database queries, disk operations, etc.)
228
+ "It worked!"
229
+ end
230
+ bus.subscribe "user.created", callable: worker
231
+ ```
158
232
 
159
- subscribers.first.value
160
- # => "Received message: {:user_id=>123}"
233
+ ### Station
161
234
 
162
- # you can use any object that responds to #call
163
- class ExampleCallable
164
- def call(message)
165
- "Received message: #{message.payload}"
166
- end
235
+ ```ruby
236
+ station = LocalBus::Station.new # ... or LocalBus.instance.station
237
+
238
+ station.subscribe "user.created" do |message|
239
+ # business logic (e.g. API calls, database queries, disk operations, etc.)
240
+ "It worked!"
167
241
  end
168
242
 
169
- LocalBus.instance.station.subscribe "email.welcome", callable: ExampleCallable.new
170
- subscribers = LocalBus.instance.station.publish("email.welcome", user_id: 123).value
171
- # => [#<LocalBus::Subscriber:0x0000000126b7cf38 ...>]
243
+ message = station.publish("user.created", user_id: 123)
244
+ message.wait # blocks until all subscribers complete
245
+ message.subscribers # blocks and waits until all subscribers complete and returns the subscribers
246
+ #=> [#<LocalBus::Subscriber:0x00000001253156e8 ...>]
172
247
 
173
- subscribers.first.value
174
- # => "Received message: {:user_id=>123}"
248
+ message.subscribers.first.value
249
+ #=> "It worked!"
250
+
251
+ # subscribe with any object that responds to `#call`.
252
+ worker = ->(message) do
253
+ # business logic (e.g. API calls, database queries, disk operations, etc.)
254
+ "It worked!"
255
+ end
256
+ station.subscribe "user.created", callable: worker
175
257
  ```
176
258
 
177
- ## Advanced Usage & Considerations
259
+ ## Advanced Usage
178
260
 
179
261
  ### Concurrency Controls
180
262
 
181
- #### Bus Interface (Async)
263
+ #### Bus
182
264
 
183
- The Bus interface uses Async's Semaphore to limit resource consumption:
265
+ The Bus leverages Async's Semaphore to limit resource consumption.
266
+ The configured `concurrency` limits how many operations can run at once.
184
267
 
185
268
  ```ruby
186
- # Configure concurrency limits for the Bus
187
- bus = LocalBus::Bus.new(concurrency_limit: 10)
188
-
189
- # The semaphore ensures only N concurrent operations run at once
190
- bus.subscribe "resource.intensive" do |message|
191
- # Only 10 of these will run concurrently
192
- perform_intensive_operation(message)
193
- end
269
+ # Configure concurrency limits for the Bus (default: Etc.nprocessors)
270
+ bus = LocalBus::Bus.new(concurrency: 10)
194
271
  ```
195
272
 
196
- When the 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.
273
+ > [!NOTE]
274
+ > When the max concurrency limit is reached, new publish operations will wait until a slot becomes available.
275
+ > This helps to ensure we don't over utilize system resources.
197
276
 
198
- #### Station Interface (Thread Pool)
277
+ #### Station
199
278
 
200
- The Station interface uses Concurrent Ruby's fixed thread pool with a fallback policy:
279
+ The Station uses a thread pool for multi-threaded message processing.
280
+ You can configure the queue size and the number of threads used to process messages.
201
281
 
202
282
  ```ruby
203
- # Configure the thread pool size for the Station
283
+ # Configure the Station
204
284
  station = LocalBus::Station.new(
205
- max_queue: 5_000, # Maximum number of queued items
206
- threads: 10, # Maximum pool size
207
- fallback_policy: :caller_runs # Runs on calling thread
285
+ limit: 5_000, # max number of pending messages (default: 10_000)
286
+ threads: 10, # max number of processing threads (default: Etc.nprocessors)
208
287
  )
209
288
  ```
210
289
 
211
- The fallback policy determines behavior when the thread pool is saturated:
290
+ ##### Message Priority
291
+
292
+ The Station supports assigning a priority to each message.
293
+ Messages with a higher priority are processed before lower priority messages.
212
294
 
213
- - `:caller_runs` - Executes the task in the publishing thread (can block)
214
- - `:abort` - Raises an error
215
- - `:discard` - Silently drops the task
295
+ ```ruby
296
+ LocalBus.publish("default") # 3rd to process
297
+ LocalBus.publish("important", priority: 5) # 2nd to process
298
+ LocalBus.publish("critical", priority: 10) # 1st to process
299
+ ```
216
300
 
217
- ### Error Handling & Recovery
301
+ ### Error Handling
218
302
 
219
- Both interfaces implement error boundaries to prevent individual subscriber failures from affecting others:
303
+ Error boundaries prevent individual subscriber failures from affecting other subscribers.
220
304
 
221
305
  ```ruby
222
- bus.subscribe "user.created" do |message|
306
+ LocalBus.subscribe "user.created" do |message|
223
307
  raise "Something went wrong!"
224
- true # Never reached
308
+ # never reached (business logic...)
225
309
  end
226
310
 
227
- bus.subscribe "user.created" do |message|
228
- # This still executes despite the error above
229
- notify_admin(message)
230
- true
311
+ LocalBus.subscribe "user.created" do |message|
312
+ # This still executes even though the other subscriber has an error
313
+ # business logic (e.g. API calls, database queries, disk operations, etc.)
231
314
  end
232
315
 
233
316
  # The publish operation completes with partial success
234
- result = bus.publish("user.created", user_id: 123)
235
- result.wait
236
- errored_subscribers = result.value.select(&:error)
317
+ message = LocalBus.publish("user.created", user_id: 123)
318
+ errored_subscribers = message.subscribers.select(&:errored?)
319
+ #=> [#<LocalBus::Subscriber:0x000000011ebbcaf0 ...>]
320
+
321
+ errored_subscribers.first.error
322
+ #=> #<LocalBus::Subscriber::Error: Invocation failed! Something went wrong!>
237
323
  ```
238
324
 
325
+ > [!IMPORTANT]
326
+ > It's up to you to check message subscribers and handle errors appropriately.
327
+
239
328
  ### Memory Considerations
240
329
 
241
- 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:
330
+ Messages are held in memory until all subscribers have completed.
331
+ Consider this when publishing large payloads or during high load scenarios.
242
332
 
243
333
  ```ruby
244
- # Memory-efficient publishing of large datasets
334
+ # memory-efficient publishing of large datasets
245
335
  large_dataset.each_slice(100) do |batch|
246
- station.publish("data.process", items: batch).wait
336
+ message = LocalBus.publish("data.process", items: batch)
337
+ message.wait # wait before processing more messages
247
338
  end
248
339
  ```
249
340
 
250
341
  ### Blocking Operations
251
342
 
252
- The Bus interface uses non-blocking I/O but can still be blocked by CPU-intensive operations:
343
+ LocalBus facilitates non-blocking I/O but bottlenecks can still be triggered by CPU-intensive operations.
253
344
 
254
345
  ```ruby
255
- # Bad - blocks the event loop
256
- bus.subscribe "cpu.intensive" do |message|
257
- perform_heavy_calculation(message)
258
- end
259
-
260
- # Better - offload to Station for CPU-intensive work
261
- station.subscribe "cpu.intensive" do |message|
262
- perform_heavy_calculation(message)
346
+ LocalBus.subscribe "cpu.intensive" do |message|
347
+ # CPU bound operation can trigger a bottleneck
263
348
  end
264
349
  ```
265
350
 
266
351
  ### Shutdown & Cleanup
267
352
 
268
- LocalBus does its best to handle graceful shutdown when the process exits, and works to ensure published messages are processed.
269
- However, it's possible that some messages may be lost when the process exits.
353
+ The Station delays process exit in an attempt to flush the queue and avoid dropped messages.
354
+ This delay can be configured via the `:wait` option in the constructor (default: 5).
270
355
 
271
- Factor for potential message loss when designing your system.
272
- For example, idempotency _(i.e. messages that can be re-published without unintended side effects)_.
356
+ > [!IMPORTANT]
357
+ > This wait time allows for processing pending messages at exit, but is not guaranteed.
358
+ > Factor for potential message loss when designing your system.
359
+ > For example, idempotency _i.e. messages that can be re-published without unintended side effects_.
273
360
 
274
361
  ### Limitations
275
362
 
276
- - The Bus interface is single-threaded - long-running subscribers can impact latency
277
- - The Station interface may drop messages if configured with `:discard` fallback policy
278
- - No persistence - pending messages are lost on process restart
279
- - No distributed support - communication limited to single process
280
- - Large payloads can impact memory usage, especially under high load
281
- - No built-in retry mechanism for failed subscribers
363
+ - The Bus is single-threaded - long-running or CPU-bound subscribers can impact latency
364
+ - The Station may drop messages at process exit _(messages are not persisted between process restarts)_
365
+ - No distributed support - limited to single process _(intra-process)_
366
+ - Large message payloads may impact memory usage, especially under high load
367
+ - No built-in retry mechanism for failed subscribers _(subscribers expose an error property, but you'll need to check and handle such errors)_
282
368
 
283
369
  Consider these limitations when designing your system architecture.
284
370
 
371
+ ### Demos & Benchmarks
372
+
373
+ The project includes demo scripts that showcase concurrent processing capabilities:
374
+
375
+ ```bash
376
+ bin/demo-bus # demonstrates Bus performance
377
+ bin/demo-station # demonstrates Station performance
378
+ ```
379
+
380
+ Both demos simulate I/O-bound operations _(1 second latency per subscriber)_ to show how LocalBus handles concurrent processing. For example, on an 10-core system:
381
+
382
+ - The Bus processes a message with 10 I/O-bound subscribers in ~1 second instead of 10 seconds
383
+ - The Station processes 10 messages with 10 I/O-bound subscribers each in ~1 second instead of 100 seconds
384
+
385
+ This demonstrates how LocalBus offers high throughput for I/O-bound operations. :raised_hands:
386
+
285
387
  ## See Also
286
388
 
287
389
  - [Message Bus](https://github.com/discourse/message_bus) - A reliable and robust messaging bus for Ruby and Rack
288
390
  - [Wisper](https://github.com/krisleech/wisper) - A micro library providing Ruby objects with Publish-Subscribe capabilities
289
-
290
- ## Sponsors
291
-
292
- <p align="center">
293
- <em>Proudly sponsored by</em>
294
- </p>
295
- <p align="center">
296
- <a href="https://www.clickfunnels.com?utm_source=hopsoft&utm_medium=open-source&utm_campaign=local_bus">
297
- <img src="https://images.clickfunnel.com/uploads/digital_asset/file/176632/clickfunnels-dark-logo.svg" width="575" />
298
- </a>
299
- </p>