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 +4 -4
- data/README.md +265 -179
- data/lib/local_bus/bus.rb +46 -29
- data/lib/local_bus/message.rb +54 -24
- data/lib/local_bus/publication.rb +29 -0
- data/lib/local_bus/station.rb +113 -155
- data/lib/local_bus/subscriber.rb +19 -10
- data/lib/local_bus/version.rb +1 -3
- data/lib/local_bus.rb +52 -3
- 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 +26 -19
- 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: f6f1119ac19b5642c84099312240ce0baf8320b42098cb48c918b627385a5aff
|
4
|
+
data.tar.gz: 936a119be41c3885de3d27bf7a68effb07bbda58fe0856e8ca7043b76bfbfe47
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ad441d27aacd1447fd9cebc7aaa5b55ab79b75eb6ce8ed756fc6f11d8b77203412796ce724067707e4c859fd28c384c16c2a6abe56139e4df3a727bd76cc104b
|
7
|
+
data.tar.gz: be8cc2ed7325347e6b3447777144dce7992fdc07385f0e20af8cfe300834c7cfe651fd7b6b0ffc9bdd797fb439ad702095220e8caa5a3c20651deae647fc97a0
|
data/README.md
CHANGED
@@ -1,300 +1,386 @@
|
|
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-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
|
-
|
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
|
110
121
|
|
111
|
-
|
112
|
-
|
122
|
+
# Publish a user sign-up event
|
123
|
+
LocalBus.publish "user.signed_up", user_id: 123
|
124
|
+
```
|
113
125
|
|
114
|
-
|
115
|
-
# => "Received message: {:user_id=>123}"
|
126
|
+
</details>
|
116
127
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
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
|
-
|
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.
|
127
161
|
|
128
|
-
|
129
|
-
|
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
|
-
###
|
187
|
+
### Requirements
|
133
188
|
|
134
|
-
|
189
|
+
- Ruby `>= 3.0`
|
135
190
|
|
136
|
-
|
137
|
-
station = LocalBus::Station.new # ... or LocalBus.instance.station
|
191
|
+
## Usage
|
138
192
|
|
139
|
-
|
140
|
-
|
141
|
-
|
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
|
-
|
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 ...>]
|
205
|
+
|
206
|
+
message.subscribers.first.value
|
207
|
+
#=> "It worked!"
|
146
208
|
|
147
|
-
|
148
|
-
|
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
|
-
|
217
|
+
### Bus
|
152
218
|
|
153
219
|
```ruby
|
154
|
-
|
155
|
-
LocalBus.instance.station.subscribe "email.welcome", callable: callable
|
220
|
+
bus = LocalBus::Bus.new # ... or LocalBus.instance.bus
|
156
221
|
|
157
|
-
|
158
|
-
|
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
|
-
|
233
|
+
message.subscribers.first.value
|
234
|
+
#=> "It worked!"
|
235
|
+
```
|
162
236
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
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
|
-
|
171
|
-
|
172
|
-
#
|
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
|
-
|
252
|
+
message.subscribers.first.value
|
253
|
+
#=> "It worked!"
|
176
254
|
```
|
177
255
|
|
178
|
-
## Advanced Usage
|
256
|
+
## Advanced Usage
|
179
257
|
|
180
258
|
### Concurrency Controls
|
181
259
|
|
182
|
-
#### Bus
|
260
|
+
#### Bus
|
183
261
|
|
184
|
-
The Bus
|
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(
|
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
|
-
|
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
|
274
|
+
#### Station
|
200
275
|
|
201
|
-
The Station
|
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
|
280
|
+
# Configure the Station
|
205
281
|
station = LocalBus::Station.new(
|
206
|
-
|
207
|
-
|
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
|
-
|
287
|
+
##### Message Priority
|
213
288
|
|
214
|
-
|
215
|
-
|
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
|
-
|
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
|
-
|
298
|
+
### Error Handling
|
299
|
+
|
300
|
+
Error boundaries prevent individual subscriber failures from affecting other subscribers.
|
221
301
|
|
222
302
|
```ruby
|
223
|
-
|
303
|
+
LocalBus.subscribe "user.created" do |message|
|
224
304
|
raise "Something went wrong!"
|
225
|
-
|
305
|
+
# never reached (business logic...)
|
226
306
|
end
|
227
307
|
|
228
|
-
|
229
|
-
# This still executes
|
230
|
-
|
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
|
-
|
236
|
-
|
237
|
-
|
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
|
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
|
-
#
|
331
|
+
# memory-efficient publishing of large datasets
|
246
332
|
large_dataset.each_slice(100) do |batch|
|
247
|
-
|
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
|
-
|
340
|
+
LocalBus facilitates non-blocking I/O but bottlenecks can still be triggered by CPU-intensive operations.
|
254
341
|
|
255
342
|
```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)
|
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
|
-
|
270
|
-
|
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
|
-
|
273
|
-
|
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
|
278
|
-
- The Station
|
279
|
-
- No
|
280
|
-
-
|
281
|
-
-
|
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>
|