shed_machines 0.1.1
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 +7 -0
- data/CHANGELOG.md +42 -0
- data/Cargo.lock +286 -0
- data/Cargo.toml +24 -0
- data/LICENSE.txt +21 -0
- data/README.md +174 -0
- data/ext/shed_machines_native/core/Cargo.toml +24 -0
- data/ext/shed_machines_native/core/src/aimd.rs +89 -0
- data/ext/shed_machines_native/core/src/budget.rs +101 -0
- data/ext/shed_machines_native/core/src/codel.rs +122 -0
- data/ext/shed_machines_native/core/src/lib.rs +118 -0
- data/ext/shed_machines_native/core/src/vegas.rs +113 -0
- data/ext/shed_machines_native/extconf.rb +3 -0
- data/ext/shed_machines_native/ffi/Cargo.toml +19 -0
- data/ext/shed_machines_native/ffi/extconf.rb +21 -0
- data/ext/shed_machines_native/ffi/src/aimd.rs +50 -0
- data/ext/shed_machines_native/ffi/src/budget.rs +79 -0
- data/ext/shed_machines_native/ffi/src/codel.rs +80 -0
- data/ext/shed_machines_native/ffi/src/lib.rs +161 -0
- data/ext/shed_machines_native/ffi/src/vegas.rs +61 -0
- data/lib/shed_machines/version.rb +5 -0
- data/lib/shed_machines.rb +7 -0
- data/sig/shed_machines.rbs +131 -0
- metadata +86 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 061e0fb85399ec23ed73e673a6ca55d406b3c1a775a6c3fade1a84e836bcb615
|
|
4
|
+
data.tar.gz: e02e410d9a5909bf71101a5d5cbe80cfc70e1aa6a42e0a6c3eb6509115078cb5
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 274e603824e19a217e2762cec86d6c43b1756ac955b753afb1061b9b5ed4dcd96a745b92c10c96fe6b80d5221fb95be4d3b109ce652d857d76d2557d096c0b78
|
|
7
|
+
data.tar.gz: 6e928923121a47f7c38f963c3f5c09b4e77c408c7b7a6d122f18b1587e54983949c2067cd2d73b3e4d16784bc51b03ae2a5a7bd29967f4685ec6dc40366781ab
|
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/Cargo.lock
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
# This file is automatically @generated by Cargo.
|
|
2
|
+
# It is not intended for manual editing.
|
|
3
|
+
version = 4
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "aho-corasick"
|
|
7
|
+
version = "1.1.5"
|
|
8
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
9
|
+
checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"memchr",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[[package]]
|
|
15
|
+
name = "bindgen"
|
|
16
|
+
version = "0.72.1"
|
|
17
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
18
|
+
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
|
|
19
|
+
dependencies = [
|
|
20
|
+
"bitflags",
|
|
21
|
+
"cexpr",
|
|
22
|
+
"clang-sys",
|
|
23
|
+
"itertools",
|
|
24
|
+
"proc-macro2",
|
|
25
|
+
"quote",
|
|
26
|
+
"regex",
|
|
27
|
+
"rustc-hash",
|
|
28
|
+
"shlex",
|
|
29
|
+
"syn",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[[package]]
|
|
33
|
+
name = "bitflags"
|
|
34
|
+
version = "2.13.2"
|
|
35
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
36
|
+
checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
|
|
37
|
+
|
|
38
|
+
[[package]]
|
|
39
|
+
name = "cexpr"
|
|
40
|
+
version = "0.6.0"
|
|
41
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
42
|
+
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
|
|
43
|
+
dependencies = [
|
|
44
|
+
"nom",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[[package]]
|
|
48
|
+
name = "cfg-if"
|
|
49
|
+
version = "1.0.5"
|
|
50
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
51
|
+
checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
|
|
52
|
+
|
|
53
|
+
[[package]]
|
|
54
|
+
name = "clang-sys"
|
|
55
|
+
version = "1.9.1"
|
|
56
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
57
|
+
checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a"
|
|
58
|
+
dependencies = [
|
|
59
|
+
"glob",
|
|
60
|
+
"libc",
|
|
61
|
+
"libloading",
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
[[package]]
|
|
65
|
+
name = "either"
|
|
66
|
+
version = "1.19.0"
|
|
67
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
68
|
+
checksum = "0e9c71c2167ca323c882b99918929403426e2373ea17242ff5653e0d5e1058be"
|
|
69
|
+
|
|
70
|
+
[[package]]
|
|
71
|
+
name = "glob"
|
|
72
|
+
version = "0.3.4"
|
|
73
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
74
|
+
checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b"
|
|
75
|
+
|
|
76
|
+
[[package]]
|
|
77
|
+
name = "itertools"
|
|
78
|
+
version = "0.13.0"
|
|
79
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
80
|
+
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
|
81
|
+
dependencies = [
|
|
82
|
+
"either",
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
[[package]]
|
|
86
|
+
name = "lazy_static"
|
|
87
|
+
version = "1.5.1"
|
|
88
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
89
|
+
checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb"
|
|
90
|
+
|
|
91
|
+
[[package]]
|
|
92
|
+
name = "libc"
|
|
93
|
+
version = "0.2.191"
|
|
94
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
95
|
+
checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a"
|
|
96
|
+
|
|
97
|
+
[[package]]
|
|
98
|
+
name = "libloading"
|
|
99
|
+
version = "0.8.9"
|
|
100
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
101
|
+
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
|
|
102
|
+
dependencies = [
|
|
103
|
+
"cfg-if",
|
|
104
|
+
"windows-link",
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
[[package]]
|
|
108
|
+
name = "magnus"
|
|
109
|
+
version = "0.9.3"
|
|
110
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
111
|
+
checksum = "40ef424404d166cbb71c8b8d19c9c589c748c4547c1afa1928c5504299f6c713"
|
|
112
|
+
dependencies = [
|
|
113
|
+
"magnus-macros",
|
|
114
|
+
"rb-sys",
|
|
115
|
+
"rb-sys-env",
|
|
116
|
+
"seq-macro",
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
[[package]]
|
|
120
|
+
name = "magnus-macros"
|
|
121
|
+
version = "0.9.1"
|
|
122
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
123
|
+
checksum = "9dafde2eeb66f090e0f326112323eb8324b12cb32b5d1210e541d3df6b0ce77e"
|
|
124
|
+
dependencies = [
|
|
125
|
+
"proc-macro2",
|
|
126
|
+
"quote",
|
|
127
|
+
"syn",
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
[[package]]
|
|
131
|
+
name = "memchr"
|
|
132
|
+
version = "2.8.3"
|
|
133
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
134
|
+
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
|
135
|
+
|
|
136
|
+
[[package]]
|
|
137
|
+
name = "minimal-lexical"
|
|
138
|
+
version = "0.2.1"
|
|
139
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
140
|
+
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
|
141
|
+
|
|
142
|
+
[[package]]
|
|
143
|
+
name = "nom"
|
|
144
|
+
version = "7.1.3"
|
|
145
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
146
|
+
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
|
147
|
+
dependencies = [
|
|
148
|
+
"memchr",
|
|
149
|
+
"minimal-lexical",
|
|
150
|
+
]
|
|
151
|
+
|
|
152
|
+
[[package]]
|
|
153
|
+
name = "proc-macro2"
|
|
154
|
+
version = "1.0.107"
|
|
155
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
156
|
+
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
|
157
|
+
dependencies = [
|
|
158
|
+
"unicode-ident",
|
|
159
|
+
]
|
|
160
|
+
|
|
161
|
+
[[package]]
|
|
162
|
+
name = "quote"
|
|
163
|
+
version = "1.0.47"
|
|
164
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
165
|
+
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
|
166
|
+
dependencies = [
|
|
167
|
+
"proc-macro2",
|
|
168
|
+
]
|
|
169
|
+
|
|
170
|
+
[[package]]
|
|
171
|
+
name = "rb-sys"
|
|
172
|
+
version = "0.9.130"
|
|
173
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
174
|
+
checksum = "02faf625bb10ba893e3ae620f19c9fb1b5f8fcae0fe4eb86bb3f2230fad75edb"
|
|
175
|
+
dependencies = [
|
|
176
|
+
"rb-sys-build",
|
|
177
|
+
]
|
|
178
|
+
|
|
179
|
+
[[package]]
|
|
180
|
+
name = "rb-sys-build"
|
|
181
|
+
version = "0.9.130"
|
|
182
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
183
|
+
checksum = "05be6f9c86fe5482808162826f6c047d093b3670582296c770de1d94f8066694"
|
|
184
|
+
dependencies = [
|
|
185
|
+
"bindgen",
|
|
186
|
+
"lazy_static",
|
|
187
|
+
"proc-macro2",
|
|
188
|
+
"quote",
|
|
189
|
+
"regex",
|
|
190
|
+
"shell-words",
|
|
191
|
+
"syn",
|
|
192
|
+
]
|
|
193
|
+
|
|
194
|
+
[[package]]
|
|
195
|
+
name = "rb-sys-env"
|
|
196
|
+
version = "0.2.3"
|
|
197
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
198
|
+
checksum = "cca7ad6a7e21e72151d56fe2495a259b5670e204c3adac41ee7ef676ea08117a"
|
|
199
|
+
|
|
200
|
+
[[package]]
|
|
201
|
+
name = "regex"
|
|
202
|
+
version = "1.13.1"
|
|
203
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
204
|
+
checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
|
|
205
|
+
dependencies = [
|
|
206
|
+
"aho-corasick",
|
|
207
|
+
"memchr",
|
|
208
|
+
"regex-automata",
|
|
209
|
+
"regex-syntax",
|
|
210
|
+
]
|
|
211
|
+
|
|
212
|
+
[[package]]
|
|
213
|
+
name = "regex-automata"
|
|
214
|
+
version = "0.4.18"
|
|
215
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
216
|
+
checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
|
|
217
|
+
dependencies = [
|
|
218
|
+
"aho-corasick",
|
|
219
|
+
"memchr",
|
|
220
|
+
"regex-syntax",
|
|
221
|
+
]
|
|
222
|
+
|
|
223
|
+
[[package]]
|
|
224
|
+
name = "regex-syntax"
|
|
225
|
+
version = "0.8.11"
|
|
226
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
227
|
+
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
|
228
|
+
|
|
229
|
+
[[package]]
|
|
230
|
+
name = "rustc-hash"
|
|
231
|
+
version = "2.1.3"
|
|
232
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
233
|
+
checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
|
|
234
|
+
|
|
235
|
+
[[package]]
|
|
236
|
+
name = "seq-macro"
|
|
237
|
+
version = "0.3.6"
|
|
238
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
239
|
+
checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc"
|
|
240
|
+
|
|
241
|
+
[[package]]
|
|
242
|
+
name = "shed-machines"
|
|
243
|
+
version = "0.2.0"
|
|
244
|
+
|
|
245
|
+
[[package]]
|
|
246
|
+
name = "shed_machines_native"
|
|
247
|
+
version = "0.1.1"
|
|
248
|
+
dependencies = [
|
|
249
|
+
"magnus",
|
|
250
|
+
"shed-machines",
|
|
251
|
+
]
|
|
252
|
+
|
|
253
|
+
[[package]]
|
|
254
|
+
name = "shell-words"
|
|
255
|
+
version = "1.1.1"
|
|
256
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
257
|
+
checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77"
|
|
258
|
+
|
|
259
|
+
[[package]]
|
|
260
|
+
name = "shlex"
|
|
261
|
+
version = "1.3.0"
|
|
262
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
263
|
+
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
|
264
|
+
|
|
265
|
+
[[package]]
|
|
266
|
+
name = "syn"
|
|
267
|
+
version = "2.0.119"
|
|
268
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
269
|
+
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
|
270
|
+
dependencies = [
|
|
271
|
+
"proc-macro2",
|
|
272
|
+
"quote",
|
|
273
|
+
"unicode-ident",
|
|
274
|
+
]
|
|
275
|
+
|
|
276
|
+
[[package]]
|
|
277
|
+
name = "unicode-ident"
|
|
278
|
+
version = "1.0.27"
|
|
279
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
280
|
+
checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
|
|
281
|
+
|
|
282
|
+
[[package]]
|
|
283
|
+
name = "windows-link"
|
|
284
|
+
version = "0.2.1"
|
|
285
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
286
|
+
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
data/Cargo.toml
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[workspace]
|
|
2
|
+
members = ["ext/shed_machines_native/core", "ext/shed_machines_native/ffi"]
|
|
3
|
+
resolver = "3"
|
|
4
|
+
|
|
5
|
+
[workspace.package]
|
|
6
|
+
edition = "2024"
|
|
7
|
+
rust-version = "1.99"
|
|
8
|
+
|
|
9
|
+
[workspace.dependencies]
|
|
10
|
+
shed-machines = { path = "ext/shed_machines_native/core" }
|
|
11
|
+
|
|
12
|
+
[workspace.lints.rust]
|
|
13
|
+
unsafe_code = "forbid"
|
|
14
|
+
unused_must_use = "warn"
|
|
15
|
+
|
|
16
|
+
[workspace.lints.clippy]
|
|
17
|
+
all = { level = "warn", priority = -1 }
|
|
18
|
+
pedantic = { level = "warn", priority = -1 }
|
|
19
|
+
nursery = { level = "warn", priority = -1 }
|
|
20
|
+
expect_used = "deny"
|
|
21
|
+
unwrap_used = "deny"
|
|
22
|
+
uninlined_format_args = "deny"
|
|
23
|
+
needless_borrow = "deny"
|
|
24
|
+
redundant_clone = "deny"
|
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,24 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "shed-machines"
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
edition.workspace = true
|
|
5
|
+
rust-version.workspace = true
|
|
6
|
+
authors = ["Abdelkader Boudih <oss@seuros.com>"]
|
|
7
|
+
license = "MIT"
|
|
8
|
+
description = "Adaptive concurrency limits and load shedding - AIMD, Vegas, CoDel and retry budgets, no_std with zero dependencies"
|
|
9
|
+
repository = "https://github.com/seuros/shed_machines"
|
|
10
|
+
keywords = ["load-shedding", "concurrency", "codel", "backpressure", "no-std"]
|
|
11
|
+
categories = ["no-std", "concurrency"]
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
|
|
14
|
+
[lib]
|
|
15
|
+
name = "shed_machines"
|
|
16
|
+
crate-type = ["lib"]
|
|
17
|
+
|
|
18
|
+
[features]
|
|
19
|
+
default = ["std"]
|
|
20
|
+
std = ["alloc"]
|
|
21
|
+
alloc = []
|
|
22
|
+
|
|
23
|
+
[lints]
|
|
24
|
+
workspace = true
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
//! Additive increase, multiplicative decrease.
|
|
2
|
+
|
|
3
|
+
use crate::{Bounds, Controller, Outcome, Sample};
|
|
4
|
+
|
|
5
|
+
/// Numerator of the fraction kept after an overload.
|
|
6
|
+
pub const DEFAULT_BACKOFF_KEPT: u32 = 3;
|
|
7
|
+
|
|
8
|
+
/// Denominator of the fraction kept after an overload. Three quarters backs
|
|
9
|
+
/// off faster than a dependency usually recovers, without the halving that
|
|
10
|
+
/// makes a limit oscillate.
|
|
11
|
+
pub const DEFAULT_BACKOFF_OF: u32 = 4;
|
|
12
|
+
|
|
13
|
+
/// Calls added per saturated success. One is what makes this additive.
|
|
14
|
+
const INCREASE: u32 = 1;
|
|
15
|
+
|
|
16
|
+
/// Grows the limit by one whenever a saturated call succeeds, and cuts it by a
|
|
17
|
+
/// factor on any overload. The limit only grows while the caller is actually
|
|
18
|
+
/// using it, so an idle client does not drift upward.
|
|
19
|
+
///
|
|
20
|
+
/// ```
|
|
21
|
+
/// use shed_machines::{Aimd, Bounds, Controller, Sample};
|
|
22
|
+
///
|
|
23
|
+
/// let mut control = Aimd::new(Bounds::new(1, 100), 10);
|
|
24
|
+
///
|
|
25
|
+
/// control.observe(Sample::ok(1_000, 10));
|
|
26
|
+
/// assert_eq!(control.limit(), 11);
|
|
27
|
+
///
|
|
28
|
+
/// control.observe(Sample::overload(1_000, 11));
|
|
29
|
+
/// assert_eq!(control.limit(), 8);
|
|
30
|
+
/// ```
|
|
31
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
32
|
+
pub struct Aimd {
|
|
33
|
+
bounds: Bounds,
|
|
34
|
+
limit: u32,
|
|
35
|
+
kept: u32,
|
|
36
|
+
of: u32,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
impl Aimd {
|
|
40
|
+
#[must_use]
|
|
41
|
+
pub const fn new(bounds: Bounds, limit: u32) -> Self {
|
|
42
|
+
Self {
|
|
43
|
+
bounds,
|
|
44
|
+
limit: bounds.clamp(limit),
|
|
45
|
+
kept: DEFAULT_BACKOFF_KEPT,
|
|
46
|
+
of: DEFAULT_BACKOFF_OF,
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/// Keep `kept / of` of the limit after an overload. See
|
|
51
|
+
/// [`DEFAULT_BACKOFF_KEPT`].
|
|
52
|
+
#[must_use]
|
|
53
|
+
pub const fn backoff(mut self, kept: u32, of: u32) -> Self {
|
|
54
|
+
self.kept = kept;
|
|
55
|
+
self.of = of;
|
|
56
|
+
self
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
impl Aimd {
|
|
61
|
+
/// A zero denominator keeps the limit rather than dividing by zero, and the
|
|
62
|
+
/// product is widened so a representable quotient is not lost to saturation.
|
|
63
|
+
fn kept_limit(&self) -> u32 {
|
|
64
|
+
if self.of == 0 {
|
|
65
|
+
return self.limit;
|
|
66
|
+
}
|
|
67
|
+
let scaled = u64::from(self.limit) * u64::from(self.kept) / u64::from(self.of);
|
|
68
|
+
u32::try_from(scaled).unwrap_or(u32::MAX)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
impl Controller for Aimd {
|
|
73
|
+
fn observe(&mut self, sample: Sample) {
|
|
74
|
+
self.limit = match sample.outcome {
|
|
75
|
+
Outcome::Overload => self.bounds.clamp(self.kept_limit()),
|
|
76
|
+
Outcome::Ok if sample.in_flight >= self.limit => {
|
|
77
|
+
self.bounds.clamp(self.limit.saturating_add(INCREASE))
|
|
78
|
+
}
|
|
79
|
+
Outcome::Ok => self.limit,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
fn limit(&self) -> u32 {
|
|
84
|
+
self.limit
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
#[cfg(test)]
|
|
89
|
+
mod tests;
|