local_bus 0.1.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
[](http://blog.codinghorror.com/the-best-code-is-no-code-at-all/)
|
2
|
+
[](https://rubygems.org/gems/local_bus)
|
3
|
+
[](https://rubygems.org/gems/local_bus)
|
4
|
+
[](https://github.com/hopsoft/local_bus/actions)
|
5
|
+
[](https://github.com/testdouble/standard)
|
6
|
+
[](https://github.com/sponsors/hopsoft)
|
7
|
+
[](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>
|