local_bus 0.2.0 → 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: f435ed1460835bdd4b50311c366c158f4dda0381038368cd325a13c8ceace3b9
4
- data.tar.gz: 937b58aa97ed2e9ca2f65eb02b6c401f9e93c4d590aff5966816dd6dd47be8a9
3
+ metadata.gz: fc19202ff3881f3519fd8cfe760b7b83f42fff39cb2b9658d2550fbd2b0bcca0
4
+ data.tar.gz: 2d2fc75beaa51eb84f2263dc82aa0049c3c2f51b24c32939f911c0b774ef74f9
5
5
  SHA512:
6
- metadata.gz: 74828920b50a51ae3fc00583dee61657d88cadd62482172fc34c1e54e935a652306716242c9152d6a5f35e9660f6d52930c6840966dc281f158c391666588cf7
7
- data.tar.gz: d7c86027a16144325f3ea4cd3e65f41ea55a56ac6b75e309a75a5397cd7042372002783948f64b6e76d3cdbb0200a1cd0b62be3ec8c632b3639c5c33b0a42a76
6
+ metadata.gz: 44e7b2c60c2be6b5435cc1a652ee624502b775ee503662f582cb32d884e082cb47ee2b8159a27080e820530988aaa4cc5fc3e47f420629d9a0a0b3be7b0a665d
7
+ data.tar.gz: 647481ca9e89a0239e26ea622c8551757d991595f52a1ba3aac06a0ac03a7266a8f42253dbd8cec30311361d49a2ed2bdd80e95f62648365d95e817de2a34853
data/README.md CHANGED
@@ -1,300 +1,390 @@
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.
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
121
+
122
+ # Publish a user sign-up event
123
+ LocalBus.publish "user.signed_up", user_id: 123
124
+ ```
110
125
 
111
- subscribers = LocalBus.instance.bus.publish("user.created", user_id: 123).value
112
- # => [#<LocalBus::Subscriber:0x0000000126b7cf38 ...>]
126
+ </details>
113
127
 
114
- subscribers.first.value
115
- # => "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.
116
132
 
117
- # you can use any object that responds to #call
118
- class ExampleCallable
119
- def call(message)
120
- "Received message: #{message.payload}"
121
- end
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.
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.
127
164
 
128
- subscribers.first.value
129
- # => "Received message: {:user_id=>123}"
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
192
+
193
+ ### LocalBus
138
194
 
139
- station.subscribe "email.welcome" do |message|
140
- WelcomeMailer.deliver(message.payload[:user_id])
141
- 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!"
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 ...>]
146
205
 
147
- result.wait # blocks until all subscribers complete
148
- result.value # blocks and waits until all subscribers complete and returns the subscribers
206
+ message.subscribers.first.value
207
+ #=> "It worked!"
149
208
  ```
150
209
 
151
- Subscribe with an explicit `callable`.
210
+ ### Bus
152
211
 
153
212
  ```ruby
154
- callable = ->(message) { "Received message: #{message.payload}" }
155
- LocalBus.instance.station.subscribe "email.welcome", callable: callable
213
+ bus = LocalBus::Bus.new # ... or LocalBus.instance.bus
214
+
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
156
219
 
157
- subscribers = LocalBus.instance.station.publish("email.welcome", user_id: 123).value
158
- # => [#<LocalBus::Subscriber:0x0000000126b7cf38 ...>]
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 ...>]
159
224
 
160
- subscribers.first.value
161
- # => "Received message: {:user_id=>123}"
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
+ ```
232
+
233
+ ### Station
234
+
235
+ ```ruby
236
+ station = LocalBus::Station.new # ... or LocalBus.instance.station
162
237
 
163
- # you can use any object that responds to #call
164
- class ExampleCallable
165
- def call(message)
166
- "Received message: #{message.payload}"
167
- end
238
+ station.subscribe "user.created" do |message|
239
+ # business logic (e.g. API calls, database queries, disk operations, etc.)
240
+ "It worked!"
168
241
  end
169
242
 
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 ...>]
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 ...>]
173
247
 
174
- subscribers.first.value
175
- # => "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
176
257
  ```
177
258
 
178
- ## Advanced Usage & Considerations
259
+ ## Advanced Usage
179
260
 
180
261
  ### Concurrency Controls
181
262
 
182
- #### Bus Interface (Async)
263
+ #### Bus
183
264
 
184
- 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.
185
267
 
186
268
  ```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
269
+ # Configure concurrency limits for the Bus (default: Etc.nprocessors)
270
+ bus = LocalBus::Bus.new(concurrency: 10)
195
271
  ```
196
272
 
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.
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.
198
276
 
199
- #### Station Interface (Thread Pool)
277
+ #### Station
200
278
 
201
- 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.
202
281
 
203
282
  ```ruby
204
- # Configure the thread pool size for the Station
283
+ # Configure the Station
205
284
  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
285
+ limit: 5_000, # max number of pending messages (default: 10_000)
286
+ threads: 10, # max number of processing threads (default: Etc.nprocessors)
209
287
  )
210
288
  ```
211
289
 
212
- The fallback policy determines behavior when the thread pool is saturated:
290
+ ##### Message Priority
213
291
 
214
- - `:caller_runs` - Executes the task in the publishing thread (can block)
215
- - `:abort` - Raises an error
216
- - `:discard` - Silently drops the task
292
+ The Station supports assigning a priority to each message.
293
+ Messages with a higher priority are processed before lower priority messages.
217
294
 
218
- ### Error Handling & Recovery
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
+ ```
219
300
 
220
- Both interfaces implement error boundaries to prevent individual subscriber failures from affecting others:
301
+ ### Error Handling
302
+
303
+ Error boundaries prevent individual subscriber failures from affecting other subscribers.
221
304
 
222
305
  ```ruby
223
- bus.subscribe "user.created" do |message|
306
+ LocalBus.subscribe "user.created" do |message|
224
307
  raise "Something went wrong!"
225
- true # Never reached
308
+ # never reached (business logic...)
226
309
  end
227
310
 
228
- bus.subscribe "user.created" do |message|
229
- # This still executes despite the error above
230
- notify_admin(message)
231
- 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.)
232
314
  end
233
315
 
234
316
  # 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)
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!>
238
323
  ```
239
324
 
325
+ > [!IMPORTANT]
326
+ > It's up to you to check message subscribers and handle errors appropriately.
327
+
240
328
  ### Memory Considerations
241
329
 
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:
330
+ Messages are held in memory until all subscribers have completed.
331
+ Consider this when publishing large payloads or during high load scenarios.
243
332
 
244
333
  ```ruby
245
- # Memory-efficient publishing of large datasets
334
+ # memory-efficient publishing of large datasets
246
335
  large_dataset.each_slice(100) do |batch|
247
- station.publish("data.process", items: batch).wait
336
+ message = LocalBus.publish("data.process", items: batch)
337
+ message.wait # wait before processing more messages
248
338
  end
249
339
  ```
250
340
 
251
341
  ### Blocking Operations
252
342
 
253
- 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.
254
344
 
255
345
  ```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)
346
+ LocalBus.subscribe "cpu.intensive" do |message|
347
+ # CPU bound operation can trigger a bottleneck
264
348
  end
265
349
  ```
266
350
 
267
351
  ### Shutdown & Cleanup
268
352
 
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.
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).
271
355
 
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)_.
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_.
274
360
 
275
361
  ### Limitations
276
362
 
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
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)_
283
368
 
284
369
  Consider these limitations when designing your system architecture.
285
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
+
286
387
  ## See Also
287
388
 
288
389
  - [Message Bus](https://github.com/discourse/message_bus) - A reliable and robust messaging bus for Ruby and Rack
289
390
  - [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>