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 +4 -4
- data/README.md +273 -182
- data/lib/local_bus/bus.rb +44 -25
- data/lib/local_bus/message.rb +54 -22
- data/lib/local_bus/publication.rb +31 -0
- data/lib/local_bus/station.rb +111 -150
- 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,57 +1,22 @@
|
|
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.
|
28
12
|
|
29
|
-
|
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.
|
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
|
-
|
31
|
+
<!-- Tocer[start]: Auto-generated, don't remove. -->
|
67
32
|
|
68
|
-
|
69
|
-
bundle add local_bus
|
70
|
-
```
|
33
|
+
## Table of Contents
|
71
34
|
|
72
|
-
|
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
|
-
|
64
|
+
<!-- Tocer[finish]: Auto-generated, don't remove. -->
|
75
65
|
|
76
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
75
|
+
### Ease of Use
|
88
76
|
|
89
|
-
|
90
|
-
|
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
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
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
|
-
|
98
|
-
|
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
|
-
|
101
|
-
|
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
|
-
|
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
|
-
|
108
|
-
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
|
109
121
|
|
110
|
-
|
111
|
-
|
122
|
+
# Publish a user sign-up event
|
123
|
+
LocalBus.publish "user.signed_up", user_id: 123
|
124
|
+
```
|
125
|
+
|
126
|
+
</details>
|
112
127
|
|
113
|
-
|
114
|
-
|
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
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
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
|
-
|
124
|
-
|
125
|
-
|
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
|
-
|
128
|
-
|
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
|
-
###
|
187
|
+
### Requirements
|
132
188
|
|
133
|
-
|
189
|
+
- Ruby `>= 3.0`
|
134
190
|
|
135
|
-
|
136
|
-
|
191
|
+
## Usage
|
192
|
+
|
193
|
+
### LocalBus
|
137
194
|
|
138
|
-
|
139
|
-
|
140
|
-
|
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
|
-
|
144
|
-
|
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
|
-
|
147
|
-
|
206
|
+
message.subscribers.first.value
|
207
|
+
#=> "It worked!"
|
148
208
|
```
|
149
209
|
|
150
|
-
|
210
|
+
### Bus
|
151
211
|
|
152
212
|
```ruby
|
153
|
-
|
154
|
-
LocalBus.instance.station.subscribe "email.welcome", callable: callable
|
213
|
+
bus = LocalBus::Bus.new # ... or LocalBus.instance.bus
|
155
214
|
|
156
|
-
|
157
|
-
|
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
|
-
|
160
|
-
# => "Received message: {:user_id=>123}"
|
233
|
+
### Station
|
161
234
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
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
|
-
|
170
|
-
|
171
|
-
#
|
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
|
-
|
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
|
259
|
+
## Advanced Usage
|
178
260
|
|
179
261
|
### Concurrency Controls
|
180
262
|
|
181
|
-
#### Bus
|
263
|
+
#### Bus
|
182
264
|
|
183
|
-
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.
|
184
267
|
|
185
268
|
```ruby
|
186
|
-
# Configure concurrency limits for the Bus
|
187
|
-
bus = LocalBus::Bus.new(
|
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
|
-
|
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
|
277
|
+
#### Station
|
199
278
|
|
200
|
-
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.
|
201
281
|
|
202
282
|
```ruby
|
203
|
-
# Configure the
|
283
|
+
# Configure the Station
|
204
284
|
station = LocalBus::Station.new(
|
205
|
-
|
206
|
-
threads: 10,
|
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
|
-
|
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
|
-
|
214
|
-
|
215
|
-
|
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
|
301
|
+
### Error Handling
|
218
302
|
|
219
|
-
|
303
|
+
Error boundaries prevent individual subscriber failures from affecting other subscribers.
|
220
304
|
|
221
305
|
```ruby
|
222
|
-
|
306
|
+
LocalBus.subscribe "user.created" do |message|
|
223
307
|
raise "Something went wrong!"
|
224
|
-
|
308
|
+
# never reached (business logic...)
|
225
309
|
end
|
226
310
|
|
227
|
-
|
228
|
-
# This still executes
|
229
|
-
|
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
|
-
|
235
|
-
|
236
|
-
|
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
|
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
|
-
#
|
334
|
+
# memory-efficient publishing of large datasets
|
245
335
|
large_dataset.each_slice(100) do |batch|
|
246
|
-
|
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
|
-
|
343
|
+
LocalBus facilitates non-blocking I/O but bottlenecks can still be triggered by CPU-intensive operations.
|
253
344
|
|
254
345
|
```ruby
|
255
|
-
|
256
|
-
|
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
|
-
|
269
|
-
|
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
|
-
|
272
|
-
|
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
|
277
|
-
- The Station
|
278
|
-
- No
|
279
|
-
-
|
280
|
-
-
|
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>
|