local_bus 0.1.1 → 0.2.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 +21 -20
- data/lib/local_bus/bus.rb +12 -12
- data/lib/local_bus/station.rb +8 -6
- data/lib/local_bus/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f435ed1460835bdd4b50311c366c158f4dda0381038368cd325a13c8ceace3b9
|
4
|
+
data.tar.gz: 937b58aa97ed2e9ca2f65eb02b6c401f9e93c4d590aff5966816dd6dd47be8a9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 74828920b50a51ae3fc00583dee61657d88cadd62482172fc34c1e54e935a652306716242c9152d6a5f35e9660f6d52930c6840966dc281f158c391666588cf7
|
7
|
+
data.tar.gz: d7c86027a16144325f3ea4cd3e65f41ea55a56ac6b75e309a75a5397cd7042372002783948f64b6e76d3cdbb0200a1cd0b62be3ec8c632b3639c5c33b0a42a76
|
data/README.md
CHANGED
@@ -30,22 +30,23 @@ LocalBus is a lightweight pub/sub system for Ruby that helps organize and simpli
|
|
30
30
|
|
31
31
|
## Table of Contents
|
32
32
|
|
33
|
-
- [Why LocalBus?](#why-localbus)
|
34
|
-
- [Installation](#installation)
|
35
|
-
- [Quick Start](#quick-start)
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
- [Advanced Usage & Considerations](#advanced-usage--considerations)
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
- [
|
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
|
+
- [See Also](#see-also)
|
49
|
+
- [Sponsors](#sponsors)
|
49
50
|
|
50
51
|
<!-- Tocer[finish]: Auto-generated, don't remove. -->
|
51
52
|
|
@@ -184,7 +185,7 @@ The Bus interface uses Async's Semaphore to limit resource consumption:
|
|
184
185
|
|
185
186
|
```ruby
|
186
187
|
# Configure concurrency limits for the Bus
|
187
|
-
bus = LocalBus::Bus.new(
|
188
|
+
bus = LocalBus::Bus.new(max_concurrency: 10)
|
188
189
|
|
189
190
|
# The semaphore ensures only N concurrent operations run at once
|
190
191
|
bus.subscribe "resource.intensive" do |message|
|
@@ -193,7 +194,7 @@ bus.subscribe "resource.intensive" do |message|
|
|
193
194
|
end
|
194
195
|
```
|
195
196
|
|
196
|
-
When the concurrency limit is reached, new publish operations will wait until a slot becomes available. This prevents memory bloat but means you should be mindful of timeouts in your subscribers.
|
197
|
+
When the max concurrency limit is reached, new publish operations will wait until a slot becomes available. This prevents memory bloat but means you should be mindful of timeouts in your subscribers.
|
197
198
|
|
198
199
|
#### Station Interface (Thread Pool)
|
199
200
|
|
@@ -203,7 +204,7 @@ The Station interface uses Concurrent Ruby's fixed thread pool with a fallback p
|
|
203
204
|
# Configure the thread pool size for the Station
|
204
205
|
station = LocalBus::Station.new(
|
205
206
|
max_queue: 5_000, # Maximum number of queued items
|
206
|
-
|
207
|
+
max_threads: 10, # Maximum pool size
|
207
208
|
fallback_policy: :caller_runs # Runs on calling thread
|
208
209
|
)
|
209
210
|
```
|
@@ -275,7 +276,7 @@ For example, idempotency _(i.e. messages that can be re-published without uninte
|
|
275
276
|
|
276
277
|
- The Bus interface is single-threaded - long-running subscribers can impact latency
|
277
278
|
- The Station interface may drop messages if configured with `:discard` fallback policy
|
278
|
-
- No persistence - pending messages
|
279
|
+
- No persistence - pending messages may be lost on process restart
|
279
280
|
- No distributed support - communication limited to single process
|
280
281
|
- Large payloads can impact memory usage, especially under high load
|
281
282
|
- No built-in retry mechanism for failed subscribers
|
data/lib/local_bus/bus.rb
CHANGED
@@ -8,27 +8,27 @@ class LocalBus
|
|
8
8
|
include MonitorMixin
|
9
9
|
|
10
10
|
# Constructor
|
11
|
-
# @note Creates a new Bus instance with specified concurrency
|
12
|
-
# @rbs
|
13
|
-
def initialize(
|
11
|
+
# @note Creates a new Bus instance with specified max concurrency (i.e. number of tasks that can run in parallel)
|
12
|
+
# @rbs max_concurrency: Integer -- maximum number of concurrent tasks (default: Concurrent.processor_count)
|
13
|
+
def initialize(max_concurrency: Concurrent.processor_count)
|
14
14
|
super()
|
15
|
-
@
|
15
|
+
@max_concurrency = max_concurrency.to_i
|
16
16
|
@subscriptions = Concurrent::Hash.new do |hash, key|
|
17
17
|
hash[key] = Concurrent::Set.new
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
21
|
# Maximum number of concurrent tasks that can run in "parallel"
|
22
|
-
# @rbs return: Integer
|
23
|
-
def
|
24
|
-
synchronize { @
|
22
|
+
# @rbs return: Integer
|
23
|
+
def max_concurrency
|
24
|
+
synchronize { @max_concurrency }
|
25
25
|
end
|
26
26
|
|
27
|
-
# Sets the concurrency
|
28
|
-
# @rbs
|
27
|
+
# Sets the max concurrency
|
28
|
+
# @rbs value: Integer -- max number of concurrent tasks that can run in "parallel"
|
29
29
|
# @rbs return: Integer -- new concurrency value
|
30
|
-
def
|
31
|
-
synchronize { @
|
30
|
+
def max_concurrency=(value)
|
31
|
+
synchronize { @max_concurrency = value.to_i }
|
32
32
|
end
|
33
33
|
|
34
34
|
# Registered topics that have subscribers
|
@@ -112,7 +112,7 @@ class LocalBus
|
|
112
112
|
if subscribers.any?
|
113
113
|
Sync do |task|
|
114
114
|
task.with_timeout timeout.to_f do
|
115
|
-
semaphore = Async::Semaphore.new(
|
115
|
+
semaphore = Async::Semaphore.new(max_concurrency, parent: barrier)
|
116
116
|
|
117
117
|
subscribers.each do |subscriber|
|
118
118
|
semaphore.async do
|
data/lib/local_bus/station.rb
CHANGED
@@ -33,21 +33,21 @@ class LocalBus
|
|
33
33
|
|
34
34
|
# Constructor
|
35
35
|
# @rbs bus: Bus -- local message bus (default: Bus.new)
|
36
|
-
# @rbs
|
36
|
+
# @rbs max_threads: Integer -- number of max_threads (default: Concurrent.processor_count)
|
37
37
|
# @rbs default_timeout: Float -- seconds to wait for a future to complete
|
38
38
|
# @rbs shutdown_timeout: Float -- seconds to wait for all futures to complete on process exit
|
39
39
|
# @rbs options: Hash[Symbol, untyped] -- Concurrent::FixedThreadPool options
|
40
40
|
# @rbs return: void
|
41
41
|
def initialize(
|
42
42
|
bus: Bus.new,
|
43
|
-
|
43
|
+
max_threads: Concurrent.processor_count,
|
44
44
|
default_timeout: 0,
|
45
45
|
shutdown_timeout: 8,
|
46
46
|
**options
|
47
47
|
)
|
48
48
|
super()
|
49
49
|
@bus = bus
|
50
|
-
@
|
50
|
+
@max_threads = [2, max_threads].max.to_i
|
51
51
|
@default_timeout = default_timeout.to_f
|
52
52
|
@shutdown_timeout = shutdown_timeout.to_f
|
53
53
|
@shutdown = Concurrent::AtomicBoolean.new(false)
|
@@ -60,7 +60,7 @@ class LocalBus
|
|
60
60
|
|
61
61
|
# Number of threads used to process messages
|
62
62
|
# @rbs return: Integer
|
63
|
-
attr_reader :
|
63
|
+
attr_reader :max_threads
|
64
64
|
|
65
65
|
# Default timeout for message processing (in seconds)
|
66
66
|
# @rbs return: Float
|
@@ -78,7 +78,7 @@ class LocalBus
|
|
78
78
|
return if running?
|
79
79
|
|
80
80
|
start_shutdown_handler
|
81
|
-
@pool = Concurrent::FixedThreadPool.new(
|
81
|
+
@pool = Concurrent::FixedThreadPool.new(max_threads, THREAD_POOL_OPTIONS.merge(options))
|
82
82
|
enable_safe_shutdown on: ["HUP", "INT", "QUIT", "TERM"]
|
83
83
|
end
|
84
84
|
end
|
@@ -99,6 +99,8 @@ class LocalBus
|
|
99
99
|
|
100
100
|
@pool = nil
|
101
101
|
end
|
102
|
+
rescue
|
103
|
+
nil # ignore errors during shutdown
|
102
104
|
end
|
103
105
|
|
104
106
|
# Clean up shutdown handler
|
@@ -213,9 +215,9 @@ class LocalBus
|
|
213
215
|
# @rbs on: Array[String] -- signals to trap
|
214
216
|
# @rbs return: void
|
215
217
|
def enable_safe_shutdown(on:)
|
218
|
+
at_exit { stop }
|
216
219
|
on.each do |signal|
|
217
220
|
trap signal do
|
218
|
-
# Only queue the signal if we haven't started shutdown
|
219
221
|
@shutdown_queue.push signal unless @shutdown.true?
|
220
222
|
rescue
|
221
223
|
nil
|
data/lib/local_bus/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: local_bus
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nate Hopkins (hopsoft)
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-11-
|
11
|
+
date: 2024-11-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: async
|