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 +4 -4
- data/README.md +269 -179
- data/lib/local_bus/bus.rb +46 -27
- data/lib/local_bus/message.rb +54 -22
- data/lib/local_bus/publication.rb +31 -0
- data/lib/local_bus/station.rb +112 -151
- data/lib/local_bus/subscriber.rb +19 -8
- data/lib/local_bus/version.rb +1 -1
- data/lib/local_bus.rb +52 -1
- data/sig/generated/local_bus/bus.rbs +83 -0
- data/sig/generated/local_bus/message.rbs +60 -0
- data/sig/generated/local_bus/publication.rbs +20 -0
- data/sig/generated/local_bus/station.rbs +113 -0
- data/sig/generated/local_bus/subscriber.rbs +89 -0
- data/sig/generated/local_bus/version.rbs +5 -0
- data/sig/generated/local_bus.rbs +49 -0
- metadata +25 -18
- data/lib/local_bus/pledge.rb +0 -43
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fc19202ff3881f3519fd8cfe760b7b83f42fff39cb2b9658d2550fbd2b0bcca0
|
4
|
+
data.tar.gz: 2d2fc75beaa51eb84f2263dc82aa0049c3c2f51b24c32939f911c0b774ef74f9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 44e7b2c60c2be6b5435cc1a652ee624502b775ee503662f582cb32d884e082cb47ee2b8159a27080e820530988aaa4cc5fc3e47f420629d9a0a0b3be7b0a665d
|
7
|
+
data.tar.gz: 647481ca9e89a0239e26ea622c8551757d991595f52a1ba3aac06a0ac03a7266a8f42253dbd8cec30311361d49a2ed2bdd80e95f62648365d95e817de2a34853
|
data/README.md
CHANGED
@@ -1,300 +1,390 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
-
|
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
|
-
- [
|
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
|
-
|
36
|
-
|
37
|
-
- [
|
38
|
-
- [
|
39
|
-
|
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
|
42
|
-
- [Station
|
43
|
-
|
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
|
-
##
|
66
|
+
## Key Benefits
|
54
67
|
|
55
|
-
|
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
|
-
|
70
|
+
### Performance and Efficiency
|
58
71
|
|
59
|
-
- **
|
60
|
-
- **
|
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
|
-
|
75
|
+
### Ease of Use
|
68
76
|
|
69
|
-
|
70
|
-
|
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
|
-
|
80
|
+
### Decoupling and Modularity
|
74
81
|
|
75
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
92
|
-
|
93
|
-
|
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
|
-
#
|
99
|
-
|
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
|
-
|
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
|
-
|
109
|
-
LocalBus.
|
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
|
-
|
112
|
-
# => [#<LocalBus::Subscriber:0x0000000126b7cf38 ...>]
|
126
|
+
</details>
|
113
127
|
|
114
|
-
|
115
|
-
|
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
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
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
|
-
|
125
|
-
|
126
|
-
|
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
|
-
|
129
|
-
|
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
|
-
###
|
187
|
+
### Requirements
|
133
188
|
|
134
|
-
|
189
|
+
- Ruby `>= 3.0`
|
135
190
|
|
136
|
-
|
137
|
-
|
191
|
+
## Usage
|
192
|
+
|
193
|
+
### LocalBus
|
138
194
|
|
139
|
-
|
140
|
-
|
141
|
-
|
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
|
-
|
145
|
-
|
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
|
-
|
148
|
-
|
206
|
+
message.subscribers.first.value
|
207
|
+
#=> "It worked!"
|
149
208
|
```
|
150
209
|
|
151
|
-
|
210
|
+
### Bus
|
152
211
|
|
153
212
|
```ruby
|
154
|
-
|
155
|
-
|
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
|
-
|
158
|
-
#
|
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
|
-
|
161
|
-
|
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
|
-
|
164
|
-
|
165
|
-
|
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
|
-
|
171
|
-
|
172
|
-
#
|
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
|
-
|
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
|
259
|
+
## Advanced Usage
|
179
260
|
|
180
261
|
### Concurrency Controls
|
181
262
|
|
182
|
-
#### Bus
|
263
|
+
#### Bus
|
183
264
|
|
184
|
-
The Bus
|
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(
|
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
|
-
|
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
|
277
|
+
#### Station
|
200
278
|
|
201
|
-
The Station
|
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
|
283
|
+
# Configure the Station
|
205
284
|
station = LocalBus::Station.new(
|
206
|
-
|
207
|
-
|
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
|
-
|
290
|
+
##### Message Priority
|
213
291
|
|
214
|
-
|
215
|
-
|
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
|
-
|
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
|
-
|
301
|
+
### Error Handling
|
302
|
+
|
303
|
+
Error boundaries prevent individual subscriber failures from affecting other subscribers.
|
221
304
|
|
222
305
|
```ruby
|
223
|
-
|
306
|
+
LocalBus.subscribe "user.created" do |message|
|
224
307
|
raise "Something went wrong!"
|
225
|
-
|
308
|
+
# never reached (business logic...)
|
226
309
|
end
|
227
310
|
|
228
|
-
|
229
|
-
# This still executes
|
230
|
-
|
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
|
-
|
236
|
-
|
237
|
-
|
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
|
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
|
-
#
|
334
|
+
# memory-efficient publishing of large datasets
|
246
335
|
large_dataset.each_slice(100) do |batch|
|
247
|
-
|
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
|
-
|
343
|
+
LocalBus facilitates non-blocking I/O but bottlenecks can still be triggered by CPU-intensive operations.
|
254
344
|
|
255
345
|
```ruby
|
256
|
-
|
257
|
-
|
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
|
-
|
270
|
-
|
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
|
-
|
273
|
-
|
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
|
278
|
-
- The Station
|
279
|
-
- No
|
280
|
-
-
|
281
|
-
-
|
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>
|