supervisor_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 +28 -0
- data/Cargo.lock +286 -0
- data/Cargo.toml +24 -0
- data/LICENSE.txt +21 -0
- data/README.md +140 -0
- data/ext/supervisor_machines_native/core/Cargo.toml +24 -0
- data/ext/supervisor_machines_native/core/src/lib.rs +128 -0
- data/ext/supervisor_machines_native/core/src/supervisor.rs +155 -0
- data/ext/supervisor_machines_native/extconf.rb +3 -0
- data/ext/supervisor_machines_native/ffi/Cargo.toml +19 -0
- data/ext/supervisor_machines_native/ffi/extconf.rb +21 -0
- data/ext/supervisor_machines_native/ffi/src/decider.rs +199 -0
- data/ext/supervisor_machines_native/ffi/src/lib.rs +135 -0
- data/lib/supervisor_machines/errors.rb +9 -0
- data/lib/supervisor_machines/supervisor.rb +320 -0
- data/lib/supervisor_machines/version.rb +5 -0
- data/lib/supervisor_machines.rb +14 -0
- data/sig/supervisor_machines.rbs +173 -0
- metadata +111 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: bcf2434ae701ae257e80d7203631c5a771fe2f918fb30ccc654e7cfb8a4bdedd
|
|
4
|
+
data.tar.gz: e49e4fb0b7b99f34a0e81ac06e68bc3b22244253a34877410130568a4303bd9d
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: aba61a49c4e301984d91c9bd9e6c85da75672038ded38bb7a9d3ed26b661a1d16bf5a2befe4b5d58fdb47c1f5119e8050672feb1b2c1ba6504d800198a226260
|
|
7
|
+
data.tar.gz: 2cfeba21ac509d76ae75018c8c8811f0d52a3f9462c234b26b1d451f7afcd35b45dcdc7a99bd21afe8f6e8b3880753f655929fc3cab8462dde9789fd7d97a679
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.1.1](https://github.com/seuros/supervisor_machines/compare/supervisor_machines-v0.1.0...supervisor_machines/v0.1.1) (2026-11-01)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* OTP-style supervision decisions ([013b054](https://github.com/seuros/supervisor_machines/commit/013b0543abb42eff100032c286642e9c26f5a907))
|
|
9
|
+
* pass a rubygems OTP through the environment ([d6b00e6](https://github.com/seuros/supervisor_machines/commit/d6b00e6f10606b4d8584d4f58999a184d2f749df))
|
|
10
|
+
* supervise threads over the decision core ([c423f63](https://github.com/seuros/supervisor_machines/commit/c423f63ceef340edfaef7eb2774eb353046b7556))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* a source install can build, and a failed gem push fails the release ([5907461](https://github.com/seuros/supervisor_machines/commit/5907461674b3fc53bcbc6c077b39d6b61d959eaf))
|
|
16
|
+
* a started tree takes on no more children, and an unstarted one has no restarts ([49baae8](https://github.com/seuros/supervisor_machines/commit/49baae849adedfea8fc96ab18c8989e77e57edf7))
|
|
17
|
+
* a stop no longer waits out the restart backoff ([5de0f95](https://github.com/seuros/supervisor_machines/commit/5de0f95db1f15ceb2a4eb8fb8114d7ee48a5da8b))
|
|
18
|
+
* a stopped tree does not keep announcing its shutdown ([44f9e78](https://github.com/seuros/supervisor_machines/commit/44f9e78f8c5279be42fb29b10ef40ef8778b099b))
|
|
19
|
+
* an in-family floor, not a pessimistic pin ([7113e08](https://github.com/seuros/supervisor_machines/commit/7113e087b649320fe1d0b1dba1c2dabb268be175))
|
|
20
|
+
* bound the child index ([3761aab](https://github.com/seuros/supervisor_machines/commit/3761aab987111b4570aeaefc171175552d0c7948))
|
|
21
|
+
* call starters and the backoff outside the supervisor's lock ([b656111](https://github.com/seuros/supervisor_machines/commit/b656111a32fe5623a44b1dd7f665d708839b17b5))
|
|
22
|
+
* close six supervision lifecycle holes ([50d330f](https://github.com/seuros/supervisor_machines/commit/50d330f8bac893b75b8616b284bd0e1e1b006e99))
|
|
23
|
+
* collect the built gems whatever depth the download puts them at ([961776d](https://github.com/seuros/supervisor_machines/commit/961776db5edbee33b2fbe87494e6c6ac7ee0d3f7))
|
|
24
|
+
* give the tree lifecycle states so a failed start leaves nothing running ([dc7deba](https://github.com/seuros/supervisor_machines/commit/dc7debaff42f2440c80c6f43f057660c0a5c79a0))
|
|
25
|
+
* refuse a fractional restart intensity instead of tightening it quietly ([ea34650](https://github.com/seuros/supervisor_machines/commit/ea34650c0480f12e824ec488daa911daeda30ccf))
|
|
26
|
+
* ship the core's test files so the packaged crate builds ([4af208e](https://github.com/seuros/supervisor_machines/commit/4af208efd80a9e48275aedfe8e2137155acd59a0))
|
|
27
|
+
* the release tag carries the gem name, and the gate uses the aggregate output ([49982d6](https://github.com/seuros/supervisor_machines/commit/49982d60eed580789d6d52502c0f275576139df3))
|
|
28
|
+
* the tree reads the clock before taking its own lock ([6dd61ef](https://github.com/seuros/supervisor_machines/commit/6dd61efced111ae04b01ffdd7dfb4b559b0fd26e))
|
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 = "shell-words"
|
|
243
|
+
version = "1.1.1"
|
|
244
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
245
|
+
checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77"
|
|
246
|
+
|
|
247
|
+
[[package]]
|
|
248
|
+
name = "shlex"
|
|
249
|
+
version = "1.3.0"
|
|
250
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
251
|
+
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
|
252
|
+
|
|
253
|
+
[[package]]
|
|
254
|
+
name = "supervisor-machines"
|
|
255
|
+
version = "0.2.0"
|
|
256
|
+
|
|
257
|
+
[[package]]
|
|
258
|
+
name = "supervisor_machines_native"
|
|
259
|
+
version = "0.1.1"
|
|
260
|
+
dependencies = [
|
|
261
|
+
"magnus",
|
|
262
|
+
"supervisor-machines",
|
|
263
|
+
]
|
|
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/supervisor_machines_native/core", "ext/supervisor_machines_native/ffi"]
|
|
3
|
+
resolver = "3"
|
|
4
|
+
|
|
5
|
+
[workspace.package]
|
|
6
|
+
edition = "2024"
|
|
7
|
+
rust-version = "1.99"
|
|
8
|
+
|
|
9
|
+
[workspace.dependencies]
|
|
10
|
+
supervisor-machines = { path = "ext/supervisor_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,140 @@
|
|
|
1
|
+
# SupervisorMachines 🔁
|
|
2
|
+
|
|
3
|
+
> **OTP-style supervision trees over Ruby threads.** The command vessel that
|
|
4
|
+
> notices a station has gone quiet and decides who returns to duty.
|
|
5
|
+
|
|
6
|
+
A child died. Which children restart, and has that happened too often to keep
|
|
7
|
+
trying? Those answers come from the `no_std` [supervisor-machines][core] crate.
|
|
8
|
+
Everything the core refuses to touch, spawning, joining, killing, ordered
|
|
9
|
+
shutdown, is this gem. **AMS Armstrong** is the bridge around that decision.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 🚀 Launch Sequence
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Add to your ship's Gemfile
|
|
17
|
+
gem 'supervisor_machines'
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Requires **Ruby 4.0+** and a Rust toolchain, or a platform gem: the core is a
|
|
21
|
+
native extension, not an option.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 🎯 Mission Brief
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
SM = SupervisorMachines
|
|
29
|
+
|
|
30
|
+
tree = SM::Supervisor.new(strategy: :one_for_all, restarts: 3, window: 60.0)
|
|
31
|
+
tree.supervise(:cache) { Thread.new { cache.run } }
|
|
32
|
+
tree.supervise(:warmup, policy: :transient) { Thread.new { warm_the_cache } }
|
|
33
|
+
tree.start
|
|
34
|
+
|
|
35
|
+
tree.children # => [:cache, :warmup]
|
|
36
|
+
tree.restarts # => 0
|
|
37
|
+
tree.terminated? # => false
|
|
38
|
+
|
|
39
|
+
tree.stop(grace: 5.0)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
A starter block returns the `Thread` that runs the child, and is called again
|
|
43
|
+
for every restart. A child that stops is noticed, handed to the core, and
|
|
44
|
+
whatever the core decides is carried out.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 🔄 Lifecycle
|
|
49
|
+
|
|
50
|
+
A tree registers children, runs them, and ends either stopped or failed. Both
|
|
51
|
+
endings are final: `start` raises `SupervisorMachines::Error` for a tree that
|
|
52
|
+
is already running, has been stopped, or gave up. There is one generation of
|
|
53
|
+
children per tree, and starting a second one is a bug, not a refresh.
|
|
54
|
+
`supervise` is refused the same way once `start` has fixed the shape of the
|
|
55
|
+
tree: a tree past registering cannot take on a child.
|
|
56
|
+
|
|
57
|
+
A `start` that cannot finish, because a starter raises, takes the children it
|
|
58
|
+
already brought up back down in reverse start order, leaves the tree failed,
|
|
59
|
+
and raises what the starter raised. Nothing is left running and nothing is left
|
|
60
|
+
unsupervised. `stop` then raises `Escalated`, like any other tree that gave up,
|
|
61
|
+
every time it is called, and a tree that was never started has nothing to take
|
|
62
|
+
down.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 🎛️ Strategies
|
|
67
|
+
|
|
68
|
+
`:one_for_one` restarts only what died. `:one_for_all` restarts every child,
|
|
69
|
+
for a set that only works together. `:rest_for_one` restarts the child and
|
|
70
|
+
everything started after it, for a chain where later children depend on earlier
|
|
71
|
+
ones.
|
|
72
|
+
|
|
73
|
+
One restart is recorded per failure handled, not per child brought back, so the
|
|
74
|
+
strategy does not change how fast the restart intensity is spent. That is OTP's
|
|
75
|
+
accounting.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## 📋 Standing Orders
|
|
80
|
+
|
|
81
|
+
A child's policy is its standing order. `:permanent` always comes back, even
|
|
82
|
+
from a clean exit. `:transient` comes back
|
|
83
|
+
only from a bad one, because a clean exit means it finished. `:temporary` never
|
|
84
|
+
comes back, and never spends the restart budget.
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
tree.supervise(:migration, policy: :temporary) { Thread.new { migrate } }
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 🔥 Restart Intensity
|
|
93
|
+
|
|
94
|
+
`restarts` within `window` seconds is all the thrashing a tree will do. Past
|
|
95
|
+
it, the tree stops its remaining children in reverse start order and raises
|
|
96
|
+
`SupervisorMachines::Escalated`, and the supervisor's own supervisor now owns
|
|
97
|
+
the problem. That decision is sticky: `terminated?` stays true and every later
|
|
98
|
+
`stop` raises again.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 🚫 Rules of Engagement
|
|
103
|
+
|
|
104
|
+
- The delay between a restart decision and the respawn comes from
|
|
105
|
+
[chrono_machines][chrono]. Pass your own `backoff:` to change the curve.
|
|
106
|
+
- The shutdown budget comes from [deadline_machines][deadline]: `stop(grace:)`
|
|
107
|
+
bounds the whole ordered teardown, and a child that outstays it is left
|
|
108
|
+
behind rather than waited on forever.
|
|
109
|
+
- Restart intensity, strategy spans and policy decisions come from the core
|
|
110
|
+
crate. None of it is reimplemented here.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## 📐 Shape
|
|
115
|
+
|
|
116
|
+
The core carries its child count in its type, so a tree is dispatched to one of
|
|
117
|
+
`SupervisorMachines::Decider::SUPPORTED_CHILDREN` monomorphised shapes: 1
|
|
118
|
+
through 16, and 32. Any other count raises `ArgumentError` from `#start`.
|
|
119
|
+
|
|
120
|
+
Threads are the only supervised unit.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## 📡 Credits
|
|
125
|
+
|
|
126
|
+
**AMS Armstrong**, NCC-3186, *Armstrong*-class command vessel.
|
|
127
|
+
|
|
128
|
+
Named for Joe Armstrong's OTP supervision trees, which settled that the way to
|
|
129
|
+
keep a system up is to let a part of it die and have something above it decide
|
|
130
|
+
what comes back.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## License
|
|
135
|
+
|
|
136
|
+
MIT.
|
|
137
|
+
|
|
138
|
+
[core]: ext/supervisor_machines_native/core/README.md
|
|
139
|
+
[chrono]: https://github.com/seuros/chrono_machines
|
|
140
|
+
[deadline]: https://github.com/seuros/deadline_machines
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "supervisor-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 = "OTP-style supervision decisions: restart strategies, restart intensity and escalation - no_std with zero dependencies"
|
|
9
|
+
repository = "https://github.com/seuros/supervisor_machines"
|
|
10
|
+
keywords = ["supervisor", "supervision-tree", "otp", "restart", "no-std"]
|
|
11
|
+
categories = ["no-std", "concurrency"]
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
|
|
14
|
+
[lib]
|
|
15
|
+
name = "supervisor_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,128 @@
|
|
|
1
|
+
#![cfg_attr(not(feature = "std"), no_std)]
|
|
2
|
+
|
|
3
|
+
//! Supervision decisions, in the OTP sense.
|
|
4
|
+
//!
|
|
5
|
+
//! Which children restart after one dies, and whether that has happened too
|
|
6
|
+
//! often to keep trying. The caller owns the threads, fibers or processes.
|
|
7
|
+
|
|
8
|
+
/// Compiles and runs the README's examples as doctests.
|
|
9
|
+
#[cfg(doctest)]
|
|
10
|
+
#[doc = include_str!("../README.md")]
|
|
11
|
+
struct ReadmeDoctests;
|
|
12
|
+
|
|
13
|
+
pub mod supervisor;
|
|
14
|
+
|
|
15
|
+
pub use supervisor::Supervisor;
|
|
16
|
+
|
|
17
|
+
/// Which siblings a failure takes down with it.
|
|
18
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
19
|
+
pub enum Strategy {
|
|
20
|
+
/// Restart only the child that died.
|
|
21
|
+
OneForOne,
|
|
22
|
+
/// Restart every child.
|
|
23
|
+
OneForAll,
|
|
24
|
+
/// Restart the child that died and everything started after it.
|
|
25
|
+
RestForOne,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// When a child is worth restarting.
|
|
29
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
30
|
+
pub enum Policy {
|
|
31
|
+
/// Always bring it back, even after a clean exit.
|
|
32
|
+
Permanent,
|
|
33
|
+
/// Bring it back only if it died badly. A clean exit means it was done.
|
|
34
|
+
Transient,
|
|
35
|
+
/// Never bring it back.
|
|
36
|
+
Temporary,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// How a child's run ended.
|
|
40
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
41
|
+
pub enum Exit {
|
|
42
|
+
/// It finished its work.
|
|
43
|
+
Normal,
|
|
44
|
+
/// It raised, was killed, or vanished.
|
|
45
|
+
Abnormal,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/// What the supervisor should do about a child that stopped.
|
|
49
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
50
|
+
pub enum Decision {
|
|
51
|
+
/// Leave it stopped. Nothing restarts.
|
|
52
|
+
Ignore,
|
|
53
|
+
/// Restart this inclusive range of children, in index order.
|
|
54
|
+
Restart(Span),
|
|
55
|
+
/// Restarting is not working. The supervisor must terminate its remaining
|
|
56
|
+
/// children and let its own supervisor deal with it.
|
|
57
|
+
Escalate,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/// An inclusive range of child indices.
|
|
61
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
62
|
+
pub struct Span {
|
|
63
|
+
/// Lowest index, restarted first.
|
|
64
|
+
pub first: usize,
|
|
65
|
+
/// Highest index, restarted last.
|
|
66
|
+
pub last: usize,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
impl Span {
|
|
70
|
+
#[must_use]
|
|
71
|
+
pub const fn single(index: usize) -> Self {
|
|
72
|
+
Self {
|
|
73
|
+
first: index,
|
|
74
|
+
last: index,
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/// Children in the span. Always at least one.
|
|
79
|
+
#[must_use]
|
|
80
|
+
pub const fn len(&self) -> usize {
|
|
81
|
+
self.last - self.first + 1
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/// Never true. A span always covers the child that died.
|
|
85
|
+
#[must_use]
|
|
86
|
+
pub const fn is_empty(&self) -> bool {
|
|
87
|
+
false
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
#[must_use]
|
|
91
|
+
pub const fn contains(&self, index: usize) -> bool {
|
|
92
|
+
self.first <= index && index <= self.last
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/// How many restarts within how long before a supervisor gives up.
|
|
97
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
98
|
+
pub struct Intensity {
|
|
99
|
+
/// Restarts tolerated inside the window.
|
|
100
|
+
pub restarts: u32,
|
|
101
|
+
/// Length of the window, in nanoseconds.
|
|
102
|
+
pub window_ns: u64,
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/// Restarts OTP tolerates by default before escalating.
|
|
106
|
+
pub const DEFAULT_RESTARTS: u32 = 1;
|
|
107
|
+
|
|
108
|
+
/// The window OTP measures those restarts over, five seconds.
|
|
109
|
+
pub const DEFAULT_WINDOW_NS: u64 = 5_000_000_000;
|
|
110
|
+
|
|
111
|
+
impl Intensity {
|
|
112
|
+
#[must_use]
|
|
113
|
+
pub const fn new(restarts: u32, window_ns: u64) -> Self {
|
|
114
|
+
Self {
|
|
115
|
+
restarts,
|
|
116
|
+
window_ns,
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
impl Default for Intensity {
|
|
122
|
+
fn default() -> Self {
|
|
123
|
+
Self::new(DEFAULT_RESTARTS, DEFAULT_WINDOW_NS)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
#[cfg(test)]
|
|
128
|
+
mod tests;
|