supervisor_machines 0.1.1 → 0.1.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bcf2434ae701ae257e80d7203631c5a771fe2f918fb30ccc654e7cfb8a4bdedd
4
- data.tar.gz: e49e4fb0b7b99f34a0e81ac06e68bc3b22244253a34877410130568a4303bd9d
3
+ metadata.gz: 4bf7a41206e6cbcfee1fe15b9aa43d51bc97736624c453ae1cb52b8d80c401e9
4
+ data.tar.gz: ac38ec2c1d4e7ce698131772c65ff29947872bbce60d6cb00e26d90a29d0c094
5
5
  SHA512:
6
- metadata.gz: aba61a49c4e301984d91c9bd9e6c85da75672038ded38bb7a9d3ed26b661a1d16bf5a2befe4b5d58fdb47c1f5119e8050672feb1b2c1ba6504d800198a226260
7
- data.tar.gz: 2cfeba21ac509d76ae75018c8c8811f0d52a3f9462c234b26b1d451f7afcd35b45dcdc7a99bd21afe8f6e8b3880753f655929fc3cab8462dde9789fd7d97a679
6
+ metadata.gz: d45aef2c0fe1ddc6d452acf4f31dcda2346114ab49b8649ceb16c7dacaa215eba9e14d9dc5e4a85e196da27b4ba1717c7a38b122bc788c721dcf0b0e50979784
7
+ data.tar.gz: 30f63a1c9052a7bd623dea553a6d6d1d51b491cd032dfae7631d1d99296c3f248d0338348c31fe2881c1e2cbc889f4d6044ca12098488fe28dca030d386d10df
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.2](https://github.com/seuros/supervisor_machines/compare/supervisor_machines/v0.1.1...supervisor_machines/v0.1.2) (2026-11-01)
4
+
5
+
6
+ ### Features
7
+
8
+ * back off granted restarts by recent restart count ([e7532e8](https://github.com/seuros/supervisor_machines/commit/e7532e8e98e43171c8d76f176161fdea50403a27))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * a republished platform gem is skipped, not fatal ([39160e7](https://github.com/seuros/supervisor_machines/commit/39160e7e295d58500d846fb56d190eff6e803151))
14
+
3
15
  ## [0.1.1](https://github.com/seuros/supervisor_machines/compare/supervisor_machines-v0.1.0...supervisor_machines/v0.1.1) (2026-11-01)
4
16
 
5
17
 
data/Cargo.lock CHANGED
@@ -252,11 +252,11 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
252
252
 
253
253
  [[package]]
254
254
  name = "supervisor-machines"
255
- version = "0.2.0"
255
+ version = "0.3.0"
256
256
 
257
257
  [[package]]
258
258
  name = "supervisor_machines_native"
259
- version = "0.1.1"
259
+ version = "0.1.2"
260
260
  dependencies = [
261
261
  "magnus",
262
262
  "supervisor-machines",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "supervisor-machines"
3
- version = "0.2.0"
3
+ version = "0.3.0"
4
4
  edition.workspace = true
5
5
  rust-version.workspace = true
6
6
  authors = ["Abdelkader Boudih <oss@seuros.com>"]
@@ -97,6 +97,58 @@ impl<const CHILDREN: usize, const HISTORY: usize> Supervisor<CHILDREN, HISTORY>
97
97
  counted
98
98
  }
99
99
 
100
+ /// How long to hold off the restart just granted: `base_ns` doubled once
101
+ /// per restart already inside the window, capped at `ceiling_ns`.
102
+ ///
103
+ /// Neither OTP nor [`stopped`](Self::stopped) imposes a delay, so a
104
+ /// supervisor that restarts the moment a child dies spends its whole
105
+ /// intensity budget in the time it takes to fail that many times. For a
106
+ /// child that dies while being constructed that is well under a
107
+ /// millisecond, and the window the intensity was written for never gets to
108
+ /// mean anything. Waiting this long between the decision and the restart
109
+ /// spreads the budget back over that window.
110
+ ///
111
+ /// The count decays as the window slides, so a child that settles returns
112
+ /// to `base_ns` on its own. Jitter is the caller's to add.
113
+ ///
114
+ /// ```
115
+ /// use supervisor_machines::{Exit, Intensity, Policy, Strategy, Supervisor};
116
+ ///
117
+ /// const MS: u64 = 1_000_000;
118
+ ///
119
+ /// let mut tree: Supervisor<1, 8> = Supervisor::new(
120
+ /// Strategy::OneForOne,
121
+ /// Intensity::new(5, 60_000 * MS),
122
+ /// );
123
+ ///
124
+ /// // The first restart waits the base, each further one twice as long.
125
+ /// tree.stopped(0, Policy::Permanent, Exit::Abnormal, 0);
126
+ /// assert_eq!(tree.restart_delay_ns(100 * MS, 10_000 * MS, 0), 100 * MS);
127
+ ///
128
+ /// tree.stopped(0, Policy::Permanent, Exit::Abnormal, MS);
129
+ /// assert_eq!(tree.restart_delay_ns(100 * MS, 10_000 * MS, MS), 200 * MS);
130
+ ///
131
+ /// // Once the window has slid past them the delay is back to the base.
132
+ /// assert_eq!(
133
+ /// tree.restart_delay_ns(100 * MS, 10_000 * MS, 120_000 * MS),
134
+ /// 100 * MS,
135
+ /// );
136
+ /// ```
137
+ #[must_use]
138
+ pub const fn restart_delay_ns(&self, base_ns: u64, ceiling_ns: u64, now_ns: u64) -> u64 {
139
+ let mut delay = base_ns;
140
+ let mut doublings = self.recent_restarts(now_ns).saturating_sub(1);
141
+ while doublings > 0 && delay < ceiling_ns {
142
+ delay = delay.saturating_mul(2);
143
+ doublings -= 1;
144
+ }
145
+ if delay > ceiling_ns {
146
+ ceiling_ns
147
+ } else {
148
+ delay
149
+ }
150
+ }
151
+
100
152
  /// The children a failure at `index` takes with it under this strategy.
101
153
  #[must_use]
102
154
  pub const fn span(&self, index: usize) -> Span {
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "supervisor_machines_native"
3
- version = "0.1.1"
3
+ version = "0.1.2"
4
4
  edition.workspace = true
5
5
  rust-version.workspace = true
6
6
  authors = ["Abdelkader Boudih <oss@seuros.com>"]
@@ -30,6 +30,7 @@ trait Decide: Send {
30
30
  fn strategy(&self) -> Strategy;
31
31
  fn terminated(&self) -> bool;
32
32
  fn recent_restarts(&self, now_ns: u64) -> u32;
33
+ fn restart_delay_ns(&self, base_ns: u64, ceiling_ns: u64, now_ns: u64) -> u64;
33
34
  fn stopped(&mut self, index: usize, policy: Policy, exit: Exit, now_ns: u64) -> Decision;
34
35
  }
35
36
 
@@ -54,6 +55,10 @@ impl<const CHILDREN: usize> Decide for Shape<CHILDREN> {
54
55
  self.0.recent_restarts(now_ns)
55
56
  }
56
57
 
58
+ fn restart_delay_ns(&self, base_ns: u64, ceiling_ns: u64, now_ns: u64) -> u64 {
59
+ self.0.restart_delay_ns(base_ns, ceiling_ns, now_ns)
60
+ }
61
+
57
62
  fn stopped(&mut self, index: usize, policy: Policy, exit: Exit, now_ns: u64) -> Decision {
58
63
  self.0.stopped(index, policy, exit, now_ns)
59
64
  }
@@ -164,6 +169,23 @@ impl Wrapper {
164
169
  self.0.borrow().recent_restarts(now_ns)
165
170
  }
166
171
 
172
+ /// How long to hold off a granted restart, backing off per restart already
173
+ /// inside the window.
174
+ fn restart_delay_ns(rb_self: &Self, args: &[Value]) -> Result<u64, Error> {
175
+ let args = scan_args::<(), (), (), (), RHash, ()>(args)?;
176
+ let (Whole(base_ns), Whole(ceiling_ns), Whole(now_ns)) =
177
+ get_kwargs::<_, (Whole<u64>, Whole<u64>, Whole<u64>), (), ()>(
178
+ args.keywords,
179
+ &["base_ns", "ceiling_ns", "now_ns"],
180
+ &[],
181
+ )?
182
+ .required;
183
+ Ok(rb_self
184
+ .0
185
+ .borrow()
186
+ .restart_delay_ns(base_ns, ceiling_ns, now_ns))
187
+ }
188
+
167
189
  fn inspect(ruby: &Ruby, rb_self: &Self) -> String {
168
190
  format!(
169
191
  "#<SupervisorMachines::Decider strategy={} children={} terminated={}>",
@@ -185,6 +207,7 @@ pub fn define(ruby: &Ruby, module: RModule) -> Result<(), Error> {
185
207
  class.define_method("strategy", method!(Wrapper::strategy, 0))?;
186
208
  class.define_method("terminated?", method!(Wrapper::terminated, 0))?;
187
209
  class.define_method("recent_restarts", method!(Wrapper::recent_restarts, 1))?;
210
+ class.define_method("restart_delay_ns", method!(Wrapper::restart_delay_ns, -1))?;
188
211
  class.define_method("inspect", method!(Wrapper::inspect, 0))?;
189
212
  Ok(())
190
213
  }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SupervisorMachines
4
- VERSION = '0.1.1'
4
+ VERSION = '0.1.2'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: supervisor_machines
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Abdelkader Boudih