rogue_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.
@@ -0,0 +1,146 @@
1
+ # Fault injection: making a dependency fail, stall or lie on purpose.
2
+ module RogueMachines
3
+ VERSION: String
4
+
5
+ # Nanoseconds in a second.
6
+ NS_PER_SECOND: Integer
7
+
8
+ # Millionths in a whole, the unit a share is held in.
9
+ PER_MILLION: Integer
10
+
11
+ # Experiments a process can arm at once, the width of the armed mask.
12
+ EXPERIMENTS: Integer
13
+
14
+ # Attempts at one consistent reading before giving up on a clock being
15
+ # replaced faster than it can be read.
16
+ READINGS: Integer
17
+
18
+ class Error < StandardError
19
+ end
20
+
21
+ # Raised by a `:raise` fault in place of the real call.
22
+ class Injected < Error
23
+ end
24
+
25
+ # The fault to perform for one call.
26
+ class Injection < ::Data
27
+ # `:raise`, `:delay` or `:garble`.
28
+ attr_reader fault: Symbol
29
+
30
+ # How long a `:delay` waits, and zero for the others.
31
+ attr_reader delay_ns: Integer
32
+ end
33
+
34
+ # Anything that reports monotonic nanoseconds.
35
+ interface _Clock
36
+ def now_ns: () -> Integer
37
+ end
38
+
39
+ # Anything that draws below a bound, which `Random` satisfies.
40
+ interface _Random
41
+ def rand: (Integer bound) -> Integer
42
+ end
43
+
44
+ # The experiments a process is running, by name. Reach it through the module.
45
+ class Rogue
46
+ def self.new: () -> instance
47
+
48
+ # Whether any experiment is armed as of the last sweep, which is the whole
49
+ # of the decision a disarmed process takes. A sweep happens on every
50
+ # `decide` and every `armed`, so this trails an expiry by one call.
51
+ def any_armed?: () -> bool
52
+
53
+ # Sweeps expired experiments at `now_ns`, then counts what is left.
54
+ # Reports 0 without sweeping for a superseded generation. Every seam
55
+ # taking a reading refuses a Float rather than truncating it.
56
+ def armed_count: (Integer now_ns, Integer generation) -> Integer
57
+
58
+ # Arms `name`, replacing any experiment already there. Raises
59
+ # `ArgumentError` for an unknown fault, a share outside a whole or below
60
+ # one millionth, a duration that is non-positive, below a nanosecond or
61
+ # longer than the cap, a `now_ns` that is not an Integer or is outside the
62
+ # range, or a `:delay`
63
+ # with no `delay:`. Raises `Error` when every slot is taken. Nothing is
64
+ # armed and no slot is taken when it raises.
65
+ def arm: (String name, fault: String, share: Float, expires_in: Float,
66
+ now_ns: Integer, generation: Integer, ?delay: Float) -> void
67
+
68
+ # Reports whether `name` was still running, which an experiment that
69
+ # expired on its own is not.
70
+ def disarm: (String name, Integer now_ns, Integer generation) -> bool
71
+
72
+ def disarm_all: () -> void
73
+
74
+ # Which clock the armed expiries were computed against. Every reading is
75
+ # only meaningful for one of these, and a call carrying another one is
76
+ # refused rather than acted on, since sweeping is destructive.
77
+ def generation: () -> Integer
78
+
79
+ # Disarms everything and starts a generation, retiring readings already
80
+ # taken against the outgoing clock. Returns the new generation.
81
+ def invalidate: () -> Integer
82
+
83
+ # The names still running at `now_ns`, in no particular order, and none
84
+ # for a superseded generation.
85
+ def armed: (Integer now_ns, Integer generation) -> Array[String]
86
+
87
+ # The fault to perform, or `nil` to make the real call. Raises
88
+ # `ArgumentError` for a `draw` or `now_ns` that is not an Integer, is
89
+ # outside its range, or is a `draw` at or above `PER_MILLION`. A Float is
90
+ # refused rather than truncated, because a truncated draw becomes 0,
91
+ # which every share covers.
92
+ def decide: (String name, Integer now_ns, Integer draw, Integer generation) -> Injection?
93
+
94
+ def inspect: () -> String
95
+ end
96
+
97
+ # Delegates to `DeadlineMachines.clock` when unset, rather than memoising
98
+ # it, so replacing that clock moves every expiry with it.
99
+ def self.clock: () -> _Clock
100
+
101
+ # Disarms everything first: an armed expiry was computed against the
102
+ # outgoing clock and is not meaningful against another one.
103
+ def self.clock=: (_Clock?) -> void
104
+ def self.random: () -> _Random
105
+ def self.random=: (_Random?) -> void
106
+ # The registry itself. Reach for the module methods instead: arming
107
+ # through this bypasses the substitute a `:garble` returns, and freezing
108
+ # it does not prevent arming, since the native side holds its own state.
109
+ def self.registry: () -> Rogue
110
+
111
+ # Arms an experiment under `name`. `expires_in` counts from now and is
112
+ # required, so no experiment outlives the person who armed it.
113
+ #
114
+ # Raises `ArgumentError` for a blank name, a name that is neither ASCII nor
115
+ # valid UTF-8, or any argument the registry refuses. Raises `Error` when
116
+ # every slot is taken, when the clock was replaced faster than a reading
117
+ # could be taken, and when the clock returned something that is not a
118
+ # non-negative Integer of nanoseconds.
119
+ #
120
+ # `share`, `expires_in` and `delay` go through `Float()`, so a String that
121
+ # parses as a number is accepted, one that does not raises `ArgumentError`,
122
+ # and a value with no numeric conversion at all raises `TypeError`.
123
+ def self.arm: (Symbol | String name, fault: Symbol | String, share: Numeric | String,
124
+ expires_in: Numeric | String, ?delay: (Numeric | String)?,
125
+ ?garbled: untyped) -> (Symbol | String)
126
+
127
+ def self.disarm: (Symbol | String name) -> bool
128
+ def self.disarm_all: () -> void
129
+
130
+ # With no name, whether anything at all is armed.
131
+ def self.armed?: (?(Symbol | String)?) -> bool
132
+
133
+ # The experiments still running, which excludes any that has expired.
134
+ def self.armed: () -> Array[Symbol]
135
+
136
+ # Runs the block, or the fault armed under `name` instead. Not the block's
137
+ # own type: a `:garble` returns the substitute it was armed with, which is
138
+ # whatever the caller passed as `garbled:`. Makes the real call when the
139
+ # clock was replaced faster than a reading could be taken.
140
+ #
141
+ # Raises `Injected` for a `:raise` fault, which is the point of the gem,
142
+ # `DeadlineMachines::Error` when a `:delay` spends the ambient budget,
143
+ # `ArgumentError` for a name it cannot key on or a draw the random gave it,
144
+ # and `Error` for a clock returning an unusable reading.
145
+ def self.run: (Symbol | String name) { () -> untyped } -> untyped
146
+ end
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rogue_machines
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Abdelkader Boudih
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: deadline_machines
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: 0.1.0
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: 0.1.0
26
+ - !ruby/object:Gem::Dependency
27
+ name: rb_sys
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '0.9'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '0.9'
40
+ description: 'Fault injection for chaos engineering: arms a named experiment that
41
+ makes a dependency fail, stall or lie for a share of calls, so the breakers, retries
42
+ and budgets around it can be shown to work. Bound to Ruby from the zero-dependency
43
+ rogue-machines crate. Disarmed by default, and every experiment carries an expiry
44
+ it cannot outlive.'
45
+ email:
46
+ - oss@seuros.com
47
+ executables: []
48
+ extensions:
49
+ - ext/rogue_machines_native/extconf.rb
50
+ extra_rdoc_files: []
51
+ files:
52
+ - CHANGELOG.md
53
+ - Cargo.lock
54
+ - Cargo.toml
55
+ - LICENSE.txt
56
+ - README.md
57
+ - ext/rogue_machines_native/core/Cargo.toml
58
+ - ext/rogue_machines_native/core/src/armed.rs
59
+ - ext/rogue_machines_native/core/src/experiment.rs
60
+ - ext/rogue_machines_native/core/src/lib.rs
61
+ - ext/rogue_machines_native/core/src/rogue.rs
62
+ - ext/rogue_machines_native/extconf.rb
63
+ - ext/rogue_machines_native/ffi/Cargo.toml
64
+ - ext/rogue_machines_native/ffi/extconf.rb
65
+ - ext/rogue_machines_native/ffi/src/injection.rs
66
+ - ext/rogue_machines_native/ffi/src/lib.rs
67
+ - ext/rogue_machines_native/ffi/src/registry.rs
68
+ - lib/rogue_machines.rb
69
+ - lib/rogue_machines/version.rb
70
+ - sig/rogue_machines.rbs
71
+ homepage: https://github.com/seuros/rogue_machines
72
+ licenses:
73
+ - MIT
74
+ metadata:
75
+ source_code_uri: https://github.com/seuros/rogue_machines
76
+ changelog_uri: https://github.com/seuros/rogue_machines/blob/master/CHANGELOG.md
77
+ bug_tracker_uri: https://github.com/seuros/rogue_machines/issues
78
+ rubygems_mfa_required: 'true'
79
+ cargo_crate_name: rogue_machines_native
80
+ cargo_manifest_path: ext/rogue_machines_native/ffi/Cargo.toml
81
+ rdoc_options: []
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: 4.0.0
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ requirements: []
95
+ rubygems_version: 4.0.20
96
+ specification_version: 4
97
+ summary: Fault injection and chaos engineering for Ruby
98
+ test_files: []