shed_machines 0.1.1-x86_64-linux

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 269a137c58effa8b2fd460e4904611cd107447326174d7ed21b961ae35fcd8f7
4
+ data.tar.gz: 8a4010d5116e9b0fe502bb786138c94aefca9458aba96140f5f19569b569ae6d
5
+ SHA512:
6
+ metadata.gz: b10db4366ee3ffdb1e06444feaf31040a85be364d1f0bb6bd3af07495bc6a925dea49ba90661837b69bae059ce478dac2f82dd61d5190482b05845f6711f5135
7
+ data.tar.gz: 159c5c7f7d6c91c46967424ec9d46e7783708b8cdbf23b7f4464066eb0648457e43932bad4200437b9b733b23cb200a7a8f02d738468e434faa8e97c2a61c3fd
data/CHANGELOG.md ADDED
@@ -0,0 +1,42 @@
1
+ # Changelog
2
+
3
+ ## [0.1.1](https://github.com/seuros/shed_machines/compare/shed_machines-v0.1.0...shed_machines/v0.1.1) (2026-11-01)
4
+
5
+
6
+ ### Features
7
+
8
+ * adaptive concurrency limits and load shedding ([e07f0dc](https://github.com/seuros/shed_machines/commit/e07f0dca6cc3dc65a49757d5eda0b96256310c61))
9
+ * pass a rubygems OTP through the environment ([8a4d15b](https://github.com/seuros/shed_machines/commit/8a4d15bc46fed686dd67da5566b8d5c252de617c))
10
+ * Ruby gem and magnus FFI for the control loops ([b323351](https://github.com/seuros/shed_machines/commit/b323351d11a2ee01782e93cafe769a24e4485ed0))
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * a configured CoDel duration goes through the same check as an observed one ([c424040](https://github.com/seuros/shed_machines/commit/c424040c95e419f88d20f06d19cd07bf5fba30f5))
16
+ * a source install can build, and a failed gem push fails the release ([4975e7f](https://github.com/seuros/shed_machines/commit/4975e7fcc39820ec681d79e8d0e3213878c61487))
17
+ * AIMD divided by zero, and both controllers saturated before dividing ([67006bd](https://github.com/seuros/shed_machines/commit/67006bdad51f9caef02594f042f2b5a5c7d34735))
18
+ * avoid re-entrant borrow and u32 clipping in shed control ([d4a1b09](https://github.com/seuros/shed_machines/commit/d4a1b0950e6de32bb2756f5ef183e649ff04d829))
19
+ * collect the built gems whatever depth the download puts them at ([7ffc2db](https://github.com/seuros/shed_machines/commit/7ffc2db96f97a1b30b3992b16d30dc45fd43f216))
20
+ * refuse a fractional call count instead of truncating it to zero ([869c3d8](https://github.com/seuros/shed_machines/commit/869c3d8988ae4e1be5924899466522053fa47981))
21
+ * reject durations and bounds the arithmetic cannot survive ([37edc6f](https://github.com/seuros/shed_machines/commit/37edc6f8849e9fff6192955cc41e910a930b3fcf))
22
+ * ship the core's test files so the packaged crate builds ([9a3e00d](https://github.com/seuros/shed_machines/commit/9a3e00dffffff31bbebfbaf6e9c57e2d21cea43e))
23
+ * the release tag carries the gem name, and the gate uses the aggregate output ([f0937ad](https://github.com/seuros/shed_machines/commit/f0937ad7ecbab770aaa81378c1035f80442291b7))
24
+
25
+ ## 0.1.0
26
+
27
+ - Initial release.
28
+ - `ShedMachines::Aimd`: additive increase per saturated success, multiplicative
29
+ decrease on overload, with a configurable `kept / of` backoff fraction.
30
+ - `ShedMachines::Vegas`: TCP Vegas applied to a concurrency limit, steering by
31
+ the queue estimate drawn from the gap between current and best round-trip
32
+ time, with a configurable `alpha`/`beta` band.
33
+ - `ShedMachines::Codel`: controlled delay, shedding queued work by how long it
34
+ waited once the wait stays above target for a full interval, with sheds that
35
+ come faster as the inverse square root of how many have happened.
36
+ - `ShedMachines::Budget`: client-side adaptive throttling, reporting the
37
+ fraction of the next retry wave to drop rather than making the decision.
38
+ - Durations cross the Ruby boundary as seconds in Floats and are converted once
39
+ to nanoseconds; the control laws themselves are integer-only.
40
+ - `shed-machines` core crate: the four control laws in `no_std` Rust with zero
41
+ dependencies. Builds and lints clean on bare metal.
42
+ - RBS signatures for the whole public API.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Abdelkader Boudih
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,174 @@
1
+ # ShedMachines 🌊
2
+
3
+ > **Adaptive concurrency limits and load shedding.** The escort that reads the
4
+ > convoy's own telemetry and says how wide the lane is, right now.
5
+
6
+ A fixed bulkhead is a guess that is wrong at every load except the one it was
7
+ tuned for. **AMS Jacobson** does not carry a tuned number. These four control
8
+ loops read what completed calls report and answer one question each: how many
9
+ calls may be in flight, whether this queued piece of work is still worth
10
+ running, and how much of the next retry wave to drop before it leaves the
11
+ process.
12
+
13
+ ---
14
+
15
+ ## 🚀 Launch Sequence
16
+
17
+ ```bash
18
+ # Add to your ship's Gemfile
19
+ gem 'shed_machines'
20
+ ```
21
+
22
+ Requires **Ruby 4.0+** and a Rust toolchain, or a platform gem.
23
+
24
+ ---
25
+
26
+ ## 🎯 Mission Brief
27
+
28
+ ```ruby
29
+ require 'shed_machines'
30
+
31
+ control = ShedMachines::Vegas.new(limit: 10, min: 1, max: 200)
32
+
33
+ control.observe(rtt: 0.004, in_flight: 10, outcome: :ok)
34
+ control.limit # => 11
35
+
36
+ control.observe(rtt: 0.040, in_flight: 11, outcome: :ok)
37
+ control.limit # => 10
38
+ ```
39
+
40
+ The escort advises, it does not fire. Nothing here enforces anything. The
41
+ caller owns the semaphore, the queue and the dice.
42
+
43
+ ---
44
+
45
+ ## 🔩 The Native Extension Is Required
46
+
47
+ There is no pure-Ruby fallback and no switch to turn one on. `require
48
+ 'shed_machines'` loads the extension, and fails if it is not there. A control
49
+ law with two implementations is a control law with two behaviours, and the whole
50
+ point of these four is that they are the same arithmetic everywhere: the gem is
51
+ a binding to the `shed-machines` crate in `ext/shed_machines_native/core`, which
52
+ is `no_std` with zero dependencies and also runs on bare metal. It needs Ruby
53
+ 4.0 and a Rust toolchain, or a platform gem.
54
+
55
+ ---
56
+
57
+ ## 🎚️ Moving a Concurrency Limit
58
+
59
+ `Aimd` grows the limit by one whenever a saturated call succeeds, and keeps
60
+ three quarters of it on any overload. It needs no latency signal.
61
+
62
+ ```ruby
63
+ control = ShedMachines::Aimd.new(limit: 10, min: 1, max: 100)
64
+
65
+ control.observe(rtt: 0.004, in_flight: 10, outcome: :ok) # saturated: grows
66
+ control.limit # => 11
67
+
68
+ control.observe(rtt: 0.004, in_flight: 11, outcome: :overload)
69
+ control.limit # => 8
70
+ ```
71
+
72
+ `Vegas` infers queue depth from the gap between the current round-trip time and
73
+ the best one it has seen, because latency alone cannot tell a slow dependency
74
+ from a deep queue. It grows while fewer than `alpha` calls look queued, shrinks
75
+ above `beta`, and holds in between.
76
+
77
+ ```ruby
78
+ control = ShedMachines::Vegas.new(limit: 10, min: 1, max: 200, alpha: 3, beta: 6)
79
+ control.best_rtt # => nil, until the first completed call
80
+ ```
81
+
82
+ Both take `outcome: :ok` or `outcome: :overload`, where overload means the call
83
+ timed out, was rejected, or otherwise says the dependency is loaded. Anything
84
+ else raises `ArgumentError`. Both clamp a starting limit into `min..max`, which
85
+ default to `LOWEST_FLOOR` and `DEFAULT_CEILING`.
86
+
87
+ ---
88
+
89
+ ## ⏱️ Shedding by Waiting Time
90
+
91
+ Queue length is the wrong signal, since a fast queue can be long and a stalled
92
+ one short. `Codel` watches how long work waited and sheds only once that stays
93
+ above target for a full interval, so a burst passes through and a standing queue
94
+ does not.
95
+
96
+ ```ruby
97
+ queue = ShedMachines::Codel.new # 5 ms target, 100 ms interval
98
+
99
+ queue.admit(sojourn: 0.020, now: 0.000) # => :admit, the burst is tolerated
100
+ queue.admit(sojourn: 0.020, now: 0.150) # => :shed, it is a standing queue
101
+ queue.shedding? # => true
102
+ queue.admit(sojourn: 0.001, now: 0.200) # => :admit, and the state clears
103
+ ```
104
+
105
+ Successive sheds come faster, as the inverse square root of how many have
106
+ happened, so the control law converges instead of oscillating.
107
+
108
+ ---
109
+
110
+ ## 🌀 Not Amplifying an Outage
111
+
112
+ A client that keeps retrying a failing dependency multiplies the load that broke
113
+ it. `Budget` tracks what the client asked for against what was accepted and
114
+ reports what fraction of the next wave to drop.
115
+
116
+ ```ruby
117
+ budget = ShedMachines::Budget.new # each accept earns 2 requests
118
+
119
+ 100.times { budget.requested.accepted }
120
+ budget.shed_probability # => 0.0
121
+
122
+ 300.times { budget.requested }
123
+ budget.shed_probability # => 0.4987...
124
+
125
+ budget.decay # halves both counts
126
+ ```
127
+
128
+ `shed_probability` is a fraction, not a decision, so nothing here needs a random
129
+ number generator.
130
+
131
+ ---
132
+
133
+ ## 📐 Seconds In, Nanoseconds Inside
134
+
135
+ Every duration crosses this boundary as seconds in a `Float`, and is converted
136
+ once to nanoseconds. The control laws are integer-only on purpose: a limit, a
137
+ queue estimate and a backoff are counts, and counting in floats invites a
138
+ limiter that is 10.999999 calls wide.
139
+
140
+ ---
141
+
142
+ ## 📊 Constants on the Instrument Panel
143
+
144
+ The Ruby constants mirror the crate's, with the same names and values.
145
+
146
+ | Constant | Value |
147
+ | ---------------------------------- | ----------- |
148
+ | `ShedMachines::LOWEST_FLOOR` | 1 |
149
+ | `ShedMachines::DEFAULT_CEILING` | 1000 |
150
+ | `Aimd::DEFAULT_BACKOFF_KEPT` | 3 |
151
+ | `Aimd::DEFAULT_BACKOFF_OF` | 4 |
152
+ | `Vegas::DEFAULT_ALPHA` | 3 |
153
+ | `Vegas::DEFAULT_BETA` | 6 |
154
+ | `Vegas::OVERLOAD_DIVISOR` | 2 |
155
+ | `Codel::DEFAULT_TARGET_NS` | 5_000_000 |
156
+ | `Codel::DEFAULT_INTERVAL_NS` | 100_000_000 |
157
+ | `Budget::DEFAULT_MULTIPLIER` | 2.0 |
158
+
159
+ ---
160
+
161
+ ## 📡 Credits
162
+
163
+ **AMS Jacobson**, NCC-2012, *Jacobson*-class escort.
164
+
165
+ Named for Kathleen Nichols and Van Jacobson, *Controlling Queue Delay* (2012),
166
+ the paper that introduced controlled delay, CoDel, and argued that a queue
167
+ should be judged by how long work waits in it rather than by how much work is
168
+ in it.
169
+
170
+ ---
171
+
172
+ ## License
173
+
174
+ MIT.
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'ffi/extconf'
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShedMachines
4
+ VERSION = '0.1.1'
5
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'shed_machines/version'
4
+
5
+ # Aimd, Vegas, Codel and Budget are defined by the native extension; there is no
6
+ # pure-Ruby implementation to fall back to.
7
+ require 'shed_machines_native/shed_machines_native'
@@ -0,0 +1,131 @@
1
+ # Adaptive concurrency limits and load shedding. Every class here is defined by
2
+ # the native extension, which binds the `shed-machines` crate.
3
+ #
4
+ # Nothing here enforces anything: these are the control loops, and the caller
5
+ # owns the semaphore, the queue and the dice.
6
+ module ShedMachines
7
+ VERSION: String
8
+
9
+ # Nanoseconds in a second. Durations cross this boundary as seconds and move
10
+ # through the control laws as nanoseconds.
11
+ NS_PER_SECOND: Integer
12
+
13
+ # A limit of zero would wedge the caller shut, so one is the lowest floor that
14
+ # still makes progress.
15
+ LOWEST_FLOOR: Integer
16
+
17
+ # Arbitrary, and deliberately so: there is no principled default ceiling for
18
+ # an unknown dependency. Set it from what the pool behind it can take.
19
+ DEFAULT_CEILING: Integer
20
+
21
+ # How a completed call ended, from the limiter's point of view. `:overload`
22
+ # means it timed out, was rejected, or otherwise says the dependency is
23
+ # loaded. Anything else raises `ArgumentError`.
24
+ type outcome = :ok | :overload
25
+
26
+ # What to do with a piece of work that has been waiting.
27
+ type verdict = :admit | :shed
28
+
29
+ # Additive increase, multiplicative decrease: grows the limit by one whenever
30
+ # a saturated call succeeds, and keeps `kept / of` of it on any overload.
31
+ class Aimd
32
+ DEFAULT_BACKOFF_KEPT: Integer
33
+ DEFAULT_BACKOFF_OF: Integer
34
+
35
+ # `limit` outside `min..max` is pulled inside it.
36
+ def self.new: (
37
+ limit: Integer,
38
+ ?min: Integer,
39
+ ?max: Integer,
40
+ ?kept: Integer,
41
+ ?of: Integer
42
+ ) -> instance
43
+
44
+ # Folds one completed call into the limit. `rtt` is in seconds, `in_flight`
45
+ # counts the calls in flight when this one started, including itself.
46
+ def observe: (rtt: Float, in_flight: Integer, outcome: outcome) -> self
47
+
48
+ # Calls allowed in flight right now.
49
+ def limit: () -> Integer
50
+
51
+ def inspect: () -> String
52
+ end
53
+
54
+ # TCP Vegas applied to a concurrency limit: estimates how many calls are
55
+ # queued rather than working from the gap between the current round-trip time
56
+ # and the best one seen, then steers the limit to keep that estimate between
57
+ # `alpha` and `beta`.
58
+ class Vegas
59
+ DEFAULT_ALPHA: Integer
60
+ DEFAULT_BETA: Integer
61
+ OVERLOAD_DIVISOR: Integer
62
+
63
+ def self.new: (
64
+ limit: Integer,
65
+ ?min: Integer,
66
+ ?max: Integer,
67
+ ?alpha: Integer,
68
+ ?beta: Integer
69
+ ) -> instance
70
+
71
+ def observe: (rtt: Float, in_flight: Integer, outcome: outcome) -> self
72
+
73
+ def limit: () -> Integer
74
+
75
+ # The best round-trip time seen so far in seconds, the stand-in for zero
76
+ # queueing. `nil` until the first completed call.
77
+ def best_rtt: () -> Float?
78
+
79
+ def inspect: () -> String
80
+ end
81
+
82
+ # Controlled delay: sheds by how long work waited rather than by how much of
83
+ # it there is, and only once the wait stays above target for a full interval.
84
+ class Codel
85
+ DEFAULT_TARGET_NS: Integer
86
+ DEFAULT_INTERVAL_NS: Integer
87
+
88
+ # Both in seconds, defaulting to the values the CoDel paper settled on.
89
+ def self.new: (?target: Float, ?interval: Float) -> instance
90
+
91
+ # `sojourn` is how long this work waited before being considered, `now` is
92
+ # the current time, both in seconds.
93
+ def admit: (sojourn: Float, now: Float) -> verdict
94
+
95
+ # The acceptable waiting time, in seconds.
96
+ def target: () -> Float
97
+ def interval: () -> Float
98
+
99
+ def shedding?: () -> bool
100
+
101
+ def inspect: () -> String
102
+ end
103
+
104
+ # Client-side adaptive throttling, so retries cannot amplify an outage.
105
+ class Budget
106
+ DEFAULT_MULTIPLIER: Float
107
+
108
+ # `multiplier` is how many requests each accepted one entitles the client
109
+ # to.
110
+ def self.new: (?multiplier: Float) -> instance
111
+
112
+ def requested: () -> self
113
+
114
+ def accepted: () -> self
115
+
116
+ # Halves both counts, so the budget forgets an outage instead of holding it
117
+ # against the dependency forever.
118
+ def decay: () -> self
119
+
120
+ def multiplier: () -> Float
121
+ def requests: () -> Integer
122
+
123
+ def accepts: () -> Integer
124
+
125
+ # The fraction of outbound work to drop locally, in `0.0..1.0`. A fraction
126
+ # rather than a decision: the caller owns the dice.
127
+ def shed_probability: () -> Float
128
+
129
+ def inspect: () -> String
130
+ end
131
+ end
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: shed_machines
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: x86_64-linux
6
+ authors:
7
+ - Abdelkader Boudih
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-11-01 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: 'AIMD and Vegas move a concurrency limit from what completed calls report,
14
+ CoDel sheds queued work by how long it waited, and a retry budget stops a client
15
+ from amplifying an outage. Bound to Ruby from the no_std shed-machines crate: the
16
+ control loops only, with the semaphore, the queue and the dice left to the caller.'
17
+ email:
18
+ - oss@seuros.com
19
+ executables: []
20
+ extensions: []
21
+ extra_rdoc_files: []
22
+ files:
23
+ - CHANGELOG.md
24
+ - LICENSE.txt
25
+ - README.md
26
+ - ext/shed_machines_native/extconf.rb
27
+ - lib/shed_machines.rb
28
+ - lib/shed_machines/version.rb
29
+ - lib/shed_machines_native/shed_machines_native.so
30
+ - sig/shed_machines.rbs
31
+ homepage: https://github.com/seuros/shed_machines
32
+ licenses:
33
+ - MIT
34
+ metadata:
35
+ source_code_uri: https://github.com/seuros/shed_machines
36
+ changelog_uri: https://github.com/seuros/shed_machines/blob/main/CHANGELOG.md
37
+ bug_tracker_uri: https://github.com/seuros/shed_machines/issues
38
+ rubygems_mfa_required: 'true'
39
+ cargo_crate_name: shed_machines_native
40
+ cargo_manifest_path: ext/shed_machines_native/ffi/Cargo.toml
41
+ post_install_message:
42
+ rdoc_options: []
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '4.0'
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: 4.1.dev
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ requirements: []
59
+ rubygems_version: 3.5.23
60
+ signing_key:
61
+ specification_version: 4
62
+ summary: Adaptive concurrency limits and load shedding for Ruby
63
+ test_files: []