ractor_shepherd 0.1.0

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,238 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RactorShepherd
4
+ # What a child is. Immutable and shareable.
5
+ #
6
+ # - `type`: `:worker` or `:supervisor`
7
+ # - `start`: a Class for a worker, a {SupervisorSpec} for a supervisor
8
+ ChildSpec = Data.define(:id, :type, :start, :args, :kwargs, :restart,
9
+ :shutdown_timeout, :start_timeout, :restart_delay)
10
+
11
+ # What a supervisor is. `kind` is `:static` or `:dynamic`.
12
+ SupervisorSpec = Data.define(:kind, :strategy, :children, :max_restarts,
13
+ :max_seconds, :max_children, :on_unresponsive)
14
+
15
+ # Exponential backoff settings.
16
+ BackoffSpec = Data.define(:initial, :max, :factor)
17
+
18
+ # Validates and builds specs. Every violation is listed in a single {InvalidSpec}.
19
+ #
20
+ # This is part of the functional core: no Ractors and no clock reads.
21
+ # (`Ractor.make_shareable` is used here purely as a conversion.)
22
+ module Validator
23
+ RESTARTS = %i[permanent transient temporary].freeze
24
+ STRATEGIES = %i[one_for_one one_for_all rest_for_one].freeze
25
+ ON_UNRESPONSIVE = %i[escalate abandon].freeze
26
+
27
+ SHAREABLE_HINT = "Procs, Threads and Mutexes cannot cross Ractors. " \
28
+ "Pass plain values and hand the behaviour over as a class."
29
+
30
+ module_function
31
+
32
+ # @return [ChildSpec]
33
+ def worker_spec(id, klass, args: [], kwargs: {}, restart: :permanent,
34
+ shutdown_timeout: 5.0, start_timeout: 5.0, restart_delay: nil, allow_nil_id: false)
35
+ errors = []
36
+ cause = nil
37
+
38
+ check_id(errors, id, allow_nil: allow_nil_id)
39
+ check_worker_class(errors, klass)
40
+ shareable_args, cause = share(errors, "args", args, cause)
41
+ shareable_kwargs, cause = share(errors, "kwargs", kwargs, cause)
42
+ check_restart(errors, restart)
43
+ check_timeout(errors, "shutdown_timeout", shutdown_timeout)
44
+ check_timeout(errors, "start_timeout", start_timeout)
45
+ delay = check_restart_delay(errors, restart_delay)
46
+
47
+ fail_spec!(errors, "worker #{id.inspect}", cause)
48
+
49
+ build(ChildSpec.new(id: id, type: :worker, start: klass,
50
+ args: shareable_args, kwargs: shareable_kwargs, restart: restart,
51
+ shutdown_timeout: shutdown_timeout, start_timeout: start_timeout,
52
+ restart_delay: delay))
53
+ end
54
+
55
+ # @return [ChildSpec] type: :supervisor
56
+ def supervisor_spec(id, kind: :static, strategy: :one_for_one, children: [], max_restarts: 3,
57
+ max_seconds: 5.0, max_children: nil, on_unresponsive: :escalate,
58
+ restart: :permanent, shutdown_timeout: :infinity, start_timeout: :infinity,
59
+ restart_delay: nil, allow_nil_id: false)
60
+ errors = []
61
+ check_id(errors, id, allow_nil: allow_nil_id)
62
+ inner = supervisor_body(errors, kind: kind, strategy: strategy, children: children,
63
+ max_restarts: max_restarts, max_seconds: max_seconds,
64
+ max_children: max_children, on_unresponsive: on_unresponsive)
65
+ check_restart(errors, restart)
66
+ check_timeout(errors, "shutdown_timeout", shutdown_timeout)
67
+ check_timeout(errors, "start_timeout", start_timeout)
68
+ delay = check_restart_delay(errors, restart_delay)
69
+
70
+ fail_spec!(errors, "supervisor #{id.inspect}", nil)
71
+
72
+ build(ChildSpec.new(id: id, type: :supervisor, start: inner,
73
+ args: [], kwargs: {}, restart: restart,
74
+ shutdown_timeout: shutdown_timeout, start_timeout: start_timeout,
75
+ restart_delay: delay))
76
+ end
77
+
78
+ # For a root supervisor: returns a bare {SupervisorSpec}, not wrapped in a ChildSpec.
79
+ def root_spec(kind: :static, strategy: :one_for_one, children: [], max_restarts: 3,
80
+ max_seconds: 5.0, max_children: nil, on_unresponsive: :escalate)
81
+ errors = []
82
+ inner = supervisor_body(errors, kind: kind, strategy: strategy, children: children,
83
+ max_restarts: max_restarts, max_seconds: max_seconds,
84
+ max_children: max_children, on_unresponsive: on_unresponsive)
85
+ fail_spec!(errors, "supervisor", nil)
86
+ build(inner)
87
+ end
88
+
89
+ # Child ids must be unique within one supervisor.
90
+ def check_unique_ids(errors, children)
91
+ ids = children.filter_map { |c| c.id if c.respond_to?(:id) }
92
+ dups = ids.tally.select { |_, n| n > 1 }.keys
93
+ errors << "duplicated child ids: #{dups.map(&:inspect).join(", ")}" unless dups.empty?
94
+ end
95
+
96
+ def supervisor_body(errors, kind:, strategy:, children:, max_restarts:, max_seconds:,
97
+ max_children:, on_unresponsive:)
98
+ errors << "kind must be :static or :dynamic (got #{kind.inspect})" unless %i[static dynamic].include?(kind)
99
+ strategy = check_strategy(errors, strategy, kind)
100
+ check_children(errors, children, kind)
101
+ check_non_negative_integer(errors, "max_restarts", max_restarts)
102
+ check_positive_number(errors, "max_seconds", max_seconds)
103
+ check_max_children(errors, max_children)
104
+ unless ON_UNRESPONSIVE.include?(on_unresponsive)
105
+ errors << "on_unresponsive must be one of #{ON_UNRESPONSIVE.inspect} (got #{on_unresponsive.inspect})"
106
+ end
107
+
108
+ SupervisorSpec.new(kind: kind, strategy: strategy, children: children.dup.freeze,
109
+ max_restarts: max_restarts, max_seconds: max_seconds,
110
+ max_children: max_children, on_unresponsive: on_unresponsive)
111
+ end
112
+
113
+ def check_id(errors, id, allow_nil: false)
114
+ return if id.nil? && allow_nil
115
+ return if id.is_a?(Symbol) || id.is_a?(Integer)
116
+ return if id.is_a?(String) && id.frozen?
117
+
118
+ errors << "id must be a Symbol, an Integer or a frozen String (got #{id.inspect})"
119
+ end
120
+
121
+ def check_worker_class(errors, klass)
122
+ unless klass.is_a?(Class)
123
+ errors << "start must be a Class (got #{klass.inspect})"
124
+ return
125
+ end
126
+ return if klass.include?(Worker)
127
+
128
+ errors << "#{klass} must include RactorShepherd::Worker"
129
+ end
130
+
131
+ def check_restart(errors, restart)
132
+ return if RESTARTS.include?(restart)
133
+
134
+ errors << "restart must be one of #{RESTARTS.inspect} (got #{restart.inspect})"
135
+ end
136
+
137
+ def check_strategy(errors, strategy, kind)
138
+ if kind == :dynamic
139
+ errors << "dynamic supervisors only support :one_for_one (got #{strategy.inspect})" unless
140
+ strategy == :one_for_one
141
+ return :one_for_one
142
+ end
143
+ errors << "strategy must be one of #{STRATEGIES.inspect} (got #{strategy.inspect})" unless
144
+ STRATEGIES.include?(strategy)
145
+ strategy
146
+ end
147
+
148
+ def check_children(errors, children, kind)
149
+ unless children.is_a?(Array)
150
+ errors << "children must be an Array (got #{children.inspect})"
151
+ return
152
+ end
153
+ if kind == :dynamic && !children.empty?
154
+ errors << "dynamic supervisors must start with no children"
155
+ return
156
+ end
157
+ bad = children.grep_v(ChildSpec)
158
+ errors << "children must all be ChildSpec (got #{bad.map(&:class).uniq.inspect})" unless bad.empty?
159
+ check_unique_ids(errors, children)
160
+ end
161
+
162
+ def check_timeout(errors, name, value)
163
+ return if value == :infinity
164
+ return if value.is_a?(Numeric) && value.positive?
165
+
166
+ errors << "#{name} must be a positive Numeric or :infinity (got #{value.inspect})"
167
+ end
168
+
169
+ def check_non_negative_integer(errors, name, value)
170
+ return if value.is_a?(Integer) && !value.negative?
171
+
172
+ errors << "#{name} must be an Integer >= 0 (got #{value.inspect})"
173
+ end
174
+
175
+ def check_positive_number(errors, name, value)
176
+ return if value.is_a?(Numeric) && value.positive?
177
+
178
+ errors << "#{name} must be a positive Numeric (got #{value.inspect})"
179
+ end
180
+
181
+ def check_max_children(errors, value)
182
+ return if value.nil?
183
+ return if value.is_a?(Integer) && value >= 1
184
+
185
+ errors << "max_children must be nil or an Integer >= 1 (got #{value.inspect})"
186
+ end
187
+
188
+ # @return [nil, Numeric, BackoffSpec]
189
+ def check_restart_delay(errors, value)
190
+ case value
191
+ when nil then nil
192
+ when Numeric
193
+ errors << "restart_delay must be >= 0 (got #{value.inspect})" if value.negative?
194
+ value
195
+ when Hash then backoff_spec(errors, value)
196
+ when BackoffSpec then value
197
+ else
198
+ errors << "restart_delay must be nil, a Numeric or a Hash (got #{value.inspect})"
199
+ nil
200
+ end
201
+ end
202
+
203
+ def backoff_spec(errors, hash)
204
+ unknown = hash.keys - %i[initial max factor]
205
+ errors << "restart_delay has unknown keys: #{unknown.inspect}" unless unknown.empty?
206
+ initial = hash.fetch(:initial, 0.1)
207
+ max = hash.fetch(:max, 5.0)
208
+ factor = hash.fetch(:factor, 2.0)
209
+ errors << "restart_delay[:initial] must be a Numeric >= 0 (got #{initial.inspect})" unless
210
+ initial.is_a?(Numeric) && !initial.negative?
211
+ errors << "restart_delay[:max] must be a Numeric >= initial (got #{max.inspect})" unless
212
+ max.is_a?(Numeric) && initial.is_a?(Numeric) && max >= initial
213
+ errors << "restart_delay[:factor] must be a Numeric >= 1 (got #{factor.inspect})" unless
214
+ factor.is_a?(Numeric) && factor >= 1
215
+ BackoffSpec.new(initial: initial, max: max, factor: factor)
216
+ end
217
+
218
+ # Make a shareable copy without freezing what the caller passed in.
219
+ def share(errors, name, value, cause)
220
+ [Ractor.make_shareable(value, copy: true), cause]
221
+ # Procs and Threads raise TypeError (allocator undefined); Mutexes raise Ractor::Error.
222
+ rescue Ractor::Error, TypeError => e
223
+ errors << "#{name} cannot be shared with a Ractor (#{e.class}: #{e.message}). #{SHAREABLE_HINT}"
224
+ [value, cause || e]
225
+ end
226
+
227
+ def fail_spec!(errors, subject, cause)
228
+ return if errors.empty?
229
+
230
+ message = "invalid #{subject}:\n" + errors.map { |e| " - #{e}" }.join("\n")
231
+ raise InvalidSpec, message, cause: cause
232
+ end
233
+
234
+ def build(data)
235
+ Ractor.make_shareable(data)
236
+ end
237
+ end
238
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RactorShepherd
4
+ # What `which_children` reports about one child.
5
+ ChildInfo = Data.define(:id, :type, :status, :ref, :restart_count)
6
+
7
+ # Maps an error name reported by a supervisor back to its class.
8
+ REMOTE_ERRORS = Ractor.make_shareable(
9
+ [ChildNotFound, ChildUnavailable, InvalidOperation, InvalidSpec,
10
+ MaxChildrenReached, ProtocolError, StartError].to_h { |klass| [klass.name, klass] }
11
+ )
12
+
13
+ # A handle on a supervisor.
14
+ #
15
+ # It is shareable, so it can be handed to children or sent to other Ractors.
16
+ # Every query is a synchronous call over the control port, with a default
17
+ # timeout of five seconds.
18
+ SupervisorRef = Data.define(:name, :path, :ractor, :control_port) do
19
+ # @return [Array<ChildInfo>]
20
+ def which_children(timeout: 5) = request([:which_children], timeout)
21
+
22
+ # @return [Hash] { specs:, active:, workers:, supervisors: }
23
+ def count_children(timeout: 5) = request([:count_children], timeout)
24
+
25
+ # @return [Ractor, SupervisorRef, nil] nil while the child is being restarted
26
+ def whereis(id, timeout: 5) = request([:whereis, id], timeout)
27
+
28
+ # Build a lazily resolved address.
29
+ #
30
+ # sup.lookup(:jobs, :poller).cast([:refresh])
31
+ #
32
+ # @return [Address]
33
+ def lookup(*path, retries: 3, resolve_timeout: 1.0)
34
+ Address.new(self, path, retries: retries, resolve_timeout: resolve_timeout)
35
+ end
36
+
37
+ # @return [Object] the id of the child that was added
38
+ def start_child(child_spec, timeout: 5) = request([:start_child, child_spec], timeout)
39
+
40
+ def terminate_child(id, timeout: 5) = request([:terminate_child, id], timeout)
41
+ def restart_child(id, timeout: 5) = request([:restart_child, id], timeout)
42
+ def delete_child(id, timeout: 5) = request([:delete_child, id], timeout)
43
+
44
+ # Stop every child in reverse order, then the supervisor, and wait for it to finish.
45
+ #
46
+ # Returns true right away if it had already stopped.
47
+ def stop(reason = :normal, timeout: :infinity)
48
+ port = Ractor::Port.new
49
+ timer = nil
50
+ return true unless ractor.monitor(port) # already finished
51
+
52
+ timer = Runtime::Timer.for_current_ractor.after(timeout, port, Runtime::Protocol.timeout(0)) unless
53
+ timeout == :infinity
54
+ return true unless send_shutdown(reason) # it finished just before we asked
55
+
56
+ case port.receive
57
+ in [Runtime::Protocol::TIMEOUT, _] then raise CallTimeout, "#{path} did not stop within #{timeout}s"
58
+ else true # a monitor notification: it stopped
59
+ end
60
+ ensure
61
+ timer&.cancel
62
+ # unmonitor is unusable (see Runtime::Call). Just close the port.
63
+ port&.close
64
+ end
65
+
66
+ # Leaves one monitor registration behind when the supervisor is alive,
67
+ # because unmonitor is unusable (see Runtime::Call).
68
+ def alive?
69
+ port = Ractor::Port.new
70
+ ractor.monitor(port)
71
+ ensure
72
+ port&.close
73
+ end
74
+
75
+ # Wait for the supervisor to finish.
76
+ #
77
+ # Call this only from the Ractor that started it: once another Ractor has
78
+ # taken a Ractor's value, nobody else can.
79
+ #
80
+ # @raise [SupervisorCrashed] if it escalated instead of stopping
81
+ def join
82
+ ractor.value
83
+ self
84
+ rescue Ractor::RemoteError => e
85
+ raise SupervisorCrashed, "#{path} crashed: #{e.cause.class}: #{e.cause.message}", cause: e.cause
86
+ end
87
+
88
+ private
89
+
90
+ def send_shutdown(reason)
91
+ control_port << Runtime::Protocol.shutdown(reason)
92
+ true
93
+ rescue Ractor::ClosedError
94
+ false
95
+ end
96
+
97
+ def request(body, timeout)
98
+ case Runtime::Call.perform(ractor, control_port, body, timeout: timeout, down_error: SupervisorDown)
99
+ in [:ok, value] then value
100
+ in [:error, error_class, message] then raise(REMOTE_ERRORS.fetch(error_class, Error), message)
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RactorShepherd
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RactorShepherd
4
+ # The low level worker interface: you write the loop yourself.
5
+ #
6
+ # class Poller
7
+ # include RactorShepherd::Worker
8
+ #
9
+ # def initialize(url) = @url = url
10
+ #
11
+ # def run(ctx)
12
+ # until ctx.shutdown_requested?
13
+ # fetch(@url)
14
+ # ctx.sleep(1.0)
15
+ # end
16
+ # end
17
+ # end
18
+ #
19
+ # `initialize` plays the role of OTP's `init`. Do not make a synchronous call
20
+ # to the supervisor from there: the supervisor is waiting for this child to
21
+ # finish starting, so the call would deadlock until `start_timeout` fires.
22
+ module Worker
23
+ # @param ctx [RactorShepherd::Runtime::Context]
24
+ def run(ctx)
25
+ raise NotImplementedError, "#{self.class} must implement #run(ctx)"
26
+ end
27
+
28
+ # Optional cleanup hook.
29
+ #
30
+ # @param reason [Symbol, Exception] :normal, :shutdown, or the exception that killed the worker
31
+ def terminate(reason) = nil # rubocop:disable Lint/UnusedMethodArgument
32
+ end
33
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "ractor_shepherd/version"
4
+ require_relative "ractor_shepherd/errors"
5
+ require_relative "ractor_shepherd/runtime/compat"
6
+
7
+ RactorShepherd::Runtime::Compat.check!
8
+
9
+ require_relative "ractor_shepherd/worker"
10
+ require_relative "ractor_shepherd/core/backoff"
11
+ require_relative "ractor_shepherd/core/event"
12
+ require_relative "ractor_shepherd/core/restart_intensity"
13
+ require_relative "ractor_shepherd/core/restart_policy"
14
+ require_relative "ractor_shepherd/core/strategy_planner"
15
+ require_relative "ractor_shepherd/spec"
16
+ require_relative "ractor_shepherd/runtime/protocol"
17
+ require_relative "ractor_shepherd/runtime/timer"
18
+ require_relative "ractor_shepherd/runtime/call"
19
+ require_relative "ractor_shepherd/runtime/context"
20
+ require_relative "ractor_shepherd/server"
21
+ require_relative "ractor_shepherd/runtime/child_state"
22
+ require_relative "ractor_shepherd/runtime/child_runner"
23
+ require_relative "ractor_shepherd/runtime/supervisor_server"
24
+ require_relative "ractor_shepherd/supervisor_ref"
25
+ require_relative "ractor_shepherd/address"
26
+ require_relative "ractor_shepherd/event_logger"
27
+ require_relative "ractor_shepherd/facade"
28
+
29
+ # Supervise Ractors the way Erlang/OTP supervisors do.
30
+ #
31
+ # A supervisor starts its children, watches them, restarts them according to a
32
+ # declared policy, and escalates to its own parent when restarting stops helping.
33
+ # Supervisors can themselves be children, so trees are just supervisors all the way down.
34
+ #
35
+ # @see DESIGN.md
36
+ module RactorShepherd
37
+ end
@@ -0,0 +1,179 @@
1
+ # Signatures for the public API.
2
+ # Runtime:: and Core:: are internal, so they are deliberately left out.
3
+ module RactorShepherd
4
+ VERSION: String
5
+
6
+ type child_id = Symbol | Integer | String
7
+ type restart_kind = :permanent | :transient | :temporary
8
+ type strategy = :one_for_one | :one_for_all | :rest_for_one
9
+ type timeout = Numeric | :infinity
10
+ type unresponsive_action = :escalate | :abandon
11
+ type restart_delay = nil | Numeric | Hash[Symbol, Numeric] | BackoffSpec
12
+ type child_status = :starting | :running | :stopping | :exited | :terminated
13
+ | :restart_scheduled | :start_failed | :unresponsive | :removed
14
+ type event = Hash[Symbol, untyped]
15
+
16
+ class Error < StandardError
17
+ end
18
+
19
+ class InvalidSpec < Error
20
+ end
21
+
22
+ class UnsupportedRuby < Error
23
+ end
24
+
25
+ class StartError < Error
26
+ end
27
+
28
+ class SupervisorCrashed < Error
29
+ end
30
+
31
+ class MaxRestartsExceeded < Error
32
+ end
33
+
34
+ class ChildUnresponsive < Error
35
+ end
36
+
37
+ class CallTimeout < Error
38
+ end
39
+
40
+ class SupervisorDown < Error
41
+ end
42
+
43
+ class WorkerDown < Error
44
+ end
45
+
46
+ class ChildNotFound < Error
47
+ end
48
+
49
+ class ChildUnavailable < Error
50
+ end
51
+
52
+ class InvalidOperation < Error
53
+ end
54
+
55
+ class MaxChildrenReached < Error
56
+ end
57
+
58
+ class ProtocolError < Error
59
+ end
60
+
61
+ class ShutdownSignal < Exception
62
+ end
63
+
64
+ class BackoffSpec
65
+ attr_reader initial: Numeric
66
+ attr_reader max: Numeric
67
+ attr_reader factor: Numeric
68
+ end
69
+
70
+ class SupervisorSpec
71
+ attr_reader kind: (:static | :dynamic)
72
+ attr_reader strategy: strategy
73
+ attr_reader children: Array[ChildSpec]
74
+ attr_reader max_restarts: Integer
75
+ attr_reader max_seconds: Numeric
76
+ attr_reader max_children: Integer?
77
+ attr_reader on_unresponsive: unresponsive_action
78
+ end
79
+
80
+ class ChildSpec
81
+ attr_reader id: child_id?
82
+ attr_reader type: (:worker | :supervisor)
83
+ attr_reader start: (Class | SupervisorSpec)
84
+ attr_reader args: Array[untyped]
85
+ attr_reader kwargs: Hash[Symbol, untyped]
86
+ attr_reader restart: restart_kind
87
+ attr_reader shutdown_timeout: timeout
88
+ attr_reader start_timeout: timeout
89
+ attr_reader restart_delay: restart_delay
90
+ end
91
+
92
+ class ChildInfo
93
+ attr_reader id: child_id
94
+ attr_reader type: (:worker | :supervisor)
95
+ attr_reader status: child_status
96
+ attr_reader ref: (Ractor | SupervisorRef)?
97
+ attr_reader restart_count: Integer
98
+ end
99
+
100
+ class SupervisorRef
101
+ attr_reader name: (Symbol | String)
102
+ attr_reader path: String
103
+ attr_reader ractor: Ractor
104
+ attr_reader control_port: untyped
105
+
106
+ def which_children: (?timeout: Numeric) -> Array[ChildInfo]
107
+ def count_children: (?timeout: Numeric) -> Hash[Symbol, Integer]
108
+ def whereis: (child_id, ?timeout: Numeric) -> (Ractor | SupervisorRef)?
109
+ def lookup: (*child_id, ?retries: Integer, ?resolve_timeout: Numeric) -> Address
110
+ def start_child: (ChildSpec, ?timeout: Numeric) -> child_id
111
+ def terminate_child: (child_id, ?timeout: Numeric) -> :ok
112
+ def restart_child: (child_id, ?timeout: Numeric) -> :ok
113
+ def delete_child: (child_id, ?timeout: Numeric) -> :ok
114
+ def stop: (?Symbol, ?timeout: timeout) -> bool
115
+ def alive?: () -> bool
116
+ def join: () -> SupervisorRef
117
+ end
118
+
119
+ class Address
120
+ attr_reader path: Array[child_id]
121
+
122
+ def ractor: () -> (Ractor | SupervisorRef)
123
+ def resolve!: () -> (Ractor | SupervisorRef)
124
+ def cast: (untyped) -> :ok
125
+ def call: (untyped, ?timeout: timeout) -> untyped
126
+ end
127
+
128
+ interface _Logger
129
+ def info: (String) -> void
130
+ def warn: (String) -> void
131
+ def error: (String) -> void
132
+ end
133
+
134
+ module EventLogger
135
+ def self.start: (untyped port, ?logger: _Logger) -> Thread
136
+ def self.level_for: (event) -> (:info | :warn | :error)
137
+ def self.format_event: (event) -> String
138
+ end
139
+
140
+ module Worker
141
+ def run: (untyped ctx) -> untyped
142
+ def terminate: ((Symbol | Exception)) -> untyped
143
+ end
144
+
145
+ module Server
146
+ include Worker
147
+
148
+ def context: () -> untyped
149
+ def handle_call: (untyped) -> untyped
150
+ def handle_cast: (untyped) -> untyped
151
+ end
152
+
153
+ def self.worker: (child_id?, Class,
154
+ ?args: Array[untyped], ?kwargs: Hash[Symbol, untyped],
155
+ ?restart: restart_kind, ?shutdown_timeout: timeout,
156
+ ?start_timeout: timeout, ?restart_delay: restart_delay) -> ChildSpec
157
+
158
+ def self.supervisor: (child_id, ?strategy: strategy, ?children: Array[ChildSpec],
159
+ ?max_restarts: Integer, ?max_seconds: Numeric,
160
+ ?on_unresponsive: unresponsive_action, ?restart: restart_kind,
161
+ ?shutdown_timeout: timeout, ?start_timeout: timeout,
162
+ ?restart_delay: restart_delay) -> ChildSpec
163
+
164
+ def self.dynamic_supervisor: (child_id, ?max_children: Integer?, ?max_restarts: Integer,
165
+ ?max_seconds: Numeric, ?on_unresponsive: unresponsive_action,
166
+ ?restart: restart_kind, ?shutdown_timeout: timeout,
167
+ ?start_timeout: timeout, ?restart_delay: restart_delay) -> ChildSpec
168
+
169
+ def self.start: (name: (Symbol | String), ?strategy: strategy, ?children: Array[ChildSpec],
170
+ ?max_restarts: Integer, ?max_seconds: Numeric,
171
+ ?on_unresponsive: unresponsive_action, ?event_port: untyped,
172
+ ?boot_timeout: timeout) -> SupervisorRef
173
+
174
+ def self.start_dynamic: (name: (Symbol | String), ?max_children: Integer?, ?max_restarts: Integer,
175
+ ?max_seconds: Numeric, ?on_unresponsive: unresponsive_action,
176
+ ?event_port: untyped, ?boot_timeout: timeout) -> SupervisorRef
177
+
178
+ def self.run: (**untyped) { (SupervisorRef) -> untyped } -> untyped
179
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ractor_shepherd
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Yudai Takada
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: 'ractor_shepherd supervises Ractors the way Erlang/OTP supervisors do:
13
+ declarative restart strategies, restart intensity, supervision trees and ordered
14
+ graceful shutdown. No runtime dependencies.'
15
+ email:
16
+ - t.yudai92@gmail.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - CHANGELOG.md
22
+ - LICENSE.txt
23
+ - README.md
24
+ - lib/ractor_shepherd.rb
25
+ - lib/ractor_shepherd/address.rb
26
+ - lib/ractor_shepherd/core/backoff.rb
27
+ - lib/ractor_shepherd/core/event.rb
28
+ - lib/ractor_shepherd/core/restart_intensity.rb
29
+ - lib/ractor_shepherd/core/restart_policy.rb
30
+ - lib/ractor_shepherd/core/strategy_planner.rb
31
+ - lib/ractor_shepherd/errors.rb
32
+ - lib/ractor_shepherd/event_logger.rb
33
+ - lib/ractor_shepherd/facade.rb
34
+ - lib/ractor_shepherd/runtime/call.rb
35
+ - lib/ractor_shepherd/runtime/child_runner.rb
36
+ - lib/ractor_shepherd/runtime/child_state.rb
37
+ - lib/ractor_shepherd/runtime/compat.rb
38
+ - lib/ractor_shepherd/runtime/context.rb
39
+ - lib/ractor_shepherd/runtime/protocol.rb
40
+ - lib/ractor_shepherd/runtime/supervisor_server.rb
41
+ - lib/ractor_shepherd/runtime/timer.rb
42
+ - lib/ractor_shepherd/server.rb
43
+ - lib/ractor_shepherd/spec.rb
44
+ - lib/ractor_shepherd/supervisor_ref.rb
45
+ - lib/ractor_shepherd/version.rb
46
+ - lib/ractor_shepherd/worker.rb
47
+ - sig/ractor_shepherd.rbs
48
+ homepage: https://github.com/ydah/ractor_shepherd
49
+ licenses:
50
+ - MIT
51
+ metadata:
52
+ homepage_uri: https://github.com/ydah/ractor_shepherd
53
+ changelog_uri: https://github.com/ydah/ractor_shepherd/blob/main/CHANGELOG.md
54
+ rubygems_mfa_required: 'true'
55
+ rdoc_options: []
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '4.0'
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ requirements: []
69
+ rubygems_version: 4.0.16
70
+ specification_version: 4
71
+ summary: OTP-style supervisor for Ruby Ractors.
72
+ test_files: []