async-container 0.23.2 → 0.24.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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/async/container/error.rb +9 -2
- data/lib/async/container/forked.rb +23 -2
- data/lib/async/container/generic.rb +14 -5
- data/lib/async/container/group.rb +1 -0
- data/lib/async/container/keyed.rb +9 -6
- data/lib/async/container/notify/client.rb +3 -0
- data/lib/async/container/notify/log.rb +1 -0
- data/lib/async/container/notify/server.rb +24 -0
- data/lib/async/container/statistics.rb +1 -0
- data/lib/async/container/threaded.rb +16 -3
- data/lib/async/container/version.rb +1 -1
- data/lib/async/container.rb +2 -0
- data/lib/metrics/provider/async/container/generic.rb +17 -0
- data/lib/metrics/provider/async/container.rb +6 -0
- data.tar.gz.sig +0 -0
- metadata +4 -2
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d8b5dae742b5445c83c515d4745a49df725f9eea920e43340913b19239af8aa6
|
4
|
+
data.tar.gz: b4153930bb6ee37055e5675c921193ec27221c754ced2250975a2ba13fb0fda8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 76a94fbd31a24ac6b6dccd52cde65bee5da8ec4e7fedb87493ffa4b67f64de387d88ddf7bff2edcec4e2075d928d52ed0273ffd6315cde69f8350d3e902a7db8
|
7
|
+
data.tar.gz: 424b331952bc76e338c21288fc710575bd651061ebcc8c5522b0c3aa4f3f4603726afdf75febba5e4ca6242d182d07603bed81043af14814ef7b397659ca3961
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
@@ -5,6 +5,7 @@
|
|
5
5
|
|
6
6
|
module Async
|
7
7
|
module Container
|
8
|
+
# Represents an error that occured during container execution.
|
8
9
|
class Error < StandardError
|
9
10
|
end
|
10
11
|
|
@@ -13,15 +14,18 @@ module Async
|
|
13
14
|
# Similar to {Interrupt}, but represents `SIGTERM`.
|
14
15
|
class Terminate < SignalException
|
15
16
|
SIGTERM = Signal.list["TERM"]
|
16
|
-
|
17
|
+
|
18
|
+
# Create a new terminate error.
|
17
19
|
def initialize
|
18
20
|
super(SIGTERM)
|
19
21
|
end
|
20
22
|
end
|
21
23
|
|
24
|
+
# Similar to {Interrupt}, but represents `SIGHUP`.
|
22
25
|
class Restart < SignalException
|
23
26
|
SIGHUP = Signal.list["HUP"]
|
24
27
|
|
28
|
+
# Create a new restart error.
|
25
29
|
def initialize
|
26
30
|
super(SIGHUP)
|
27
31
|
end
|
@@ -29,13 +33,16 @@ module Async
|
|
29
33
|
|
30
34
|
# Represents the error which occured when a container failed to start up correctly.
|
31
35
|
class SetupError < Error
|
36
|
+
# Create a new setup error.
|
37
|
+
#
|
38
|
+
# @parameter container [Generic] The container that failed.
|
32
39
|
def initialize(container)
|
33
40
|
super("Could not create container!")
|
34
41
|
|
35
42
|
@container = container
|
36
43
|
end
|
37
44
|
|
38
|
-
# The container that failed.
|
45
|
+
# @attribute [Generic] The container that failed.
|
39
46
|
attr :container
|
40
47
|
end
|
41
48
|
end
|
@@ -35,12 +35,18 @@ module Async
|
|
35
35
|
return instance
|
36
36
|
end
|
37
37
|
|
38
|
+
# Initialize the child process instance.
|
39
|
+
#
|
40
|
+
# @parameter io [IO] The IO object to use for communication.
|
38
41
|
def initialize(io)
|
39
42
|
super
|
40
43
|
|
41
44
|
@name = nil
|
42
45
|
end
|
43
46
|
|
47
|
+
# Generate a hash representation of the process.
|
48
|
+
#
|
49
|
+
# @returns [Hash] The process as a hash, including `process_id` and `name`.
|
44
50
|
def as_json(...)
|
45
51
|
{
|
46
52
|
process_id: ::Process.pid,
|
@@ -48,11 +54,15 @@ module Async
|
|
48
54
|
}
|
49
55
|
end
|
50
56
|
|
57
|
+
# Generate a JSON representation of the process.
|
58
|
+
#
|
59
|
+
# @returns [String] The process as JSON.
|
51
60
|
def to_json(...)
|
52
61
|
as_json.to_json(...)
|
53
62
|
end
|
54
63
|
|
55
64
|
# Set the process title to the specified value.
|
65
|
+
#
|
56
66
|
# @parameter value [String] The name of the process.
|
57
67
|
def name= value
|
58
68
|
@name = value
|
@@ -61,14 +71,17 @@ module Async
|
|
61
71
|
::Process.setproctitle(@name.to_s)
|
62
72
|
end
|
63
73
|
|
64
|
-
# The name of the process.
|
65
|
-
# @returns [String]
|
74
|
+
# @returns [String] The name of the process.
|
66
75
|
def name
|
67
76
|
@name
|
68
77
|
end
|
69
78
|
|
70
79
|
# Replace the current child process with a different one. Forwards arguments and options to {::Process.exec}.
|
71
80
|
# This method replaces the child process with the new executable, thus this method never returns.
|
81
|
+
#
|
82
|
+
# @parameter arguments [Array] The arguments to pass to the new process.
|
83
|
+
# @parameter ready [Boolean] If true, informs the parent process that the child is ready. Otherwise, the child process will need to use a notification protocol to inform the parent process that it is ready.
|
84
|
+
# @parameter options [Hash] Additional options to pass to {::Process.exec}.
|
72
85
|
def exec(*arguments, ready: true, **options)
|
73
86
|
if ready
|
74
87
|
self.ready!(status: "(exec)")
|
@@ -81,6 +94,7 @@ module Async
|
|
81
94
|
end
|
82
95
|
|
83
96
|
# Fork a child process appropriate for a container.
|
97
|
+
#
|
84
98
|
# @returns [Process]
|
85
99
|
def self.fork(**options)
|
86
100
|
# $stderr.puts fork: caller
|
@@ -105,6 +119,13 @@ module Async
|
|
105
119
|
end
|
106
120
|
end
|
107
121
|
|
122
|
+
# Spawn a child process using {::Process.spawn}.
|
123
|
+
#
|
124
|
+
# The child process will need to inform the parent process that it is ready using a notification protocol.
|
125
|
+
#
|
126
|
+
# @parameter arguments [Array] The arguments to pass to the new process.
|
127
|
+
# @parameter name [String] The name of the process.
|
128
|
+
# @parameter options [Hash] Additional options to pass to {::Process.spawn}.
|
108
129
|
def self.spawn(*arguments, name: nil, **options)
|
109
130
|
self.new(name: name) do |process|
|
110
131
|
Notify::Pipe.new(process.out).before_spawn(arguments, options)
|
@@ -32,12 +32,16 @@ module Async
|
|
32
32
|
|
33
33
|
# A base class for implementing containers.
|
34
34
|
class Generic
|
35
|
-
|
36
|
-
|
35
|
+
# Run a new container.
|
36
|
+
def self.run(...)
|
37
|
+
self.new.run(...)
|
37
38
|
end
|
38
39
|
|
39
40
|
UNNAMED = "Unnamed"
|
40
41
|
|
42
|
+
# Initialize the container.
|
43
|
+
#
|
44
|
+
# @parameter options [Hash] Options passed to the {Group} instance.
|
41
45
|
def initialize(**options)
|
42
46
|
@group = Group.new(**options)
|
43
47
|
@running = true
|
@@ -145,6 +149,13 @@ module Async
|
|
145
149
|
@running = true
|
146
150
|
end
|
147
151
|
|
152
|
+
protected def health_check_failed!(child, age_clock, health_check_timeout)
|
153
|
+
Console.warn(self, "Child failed health check!", child: child, age: age_clock.total, health_check_timeout: health_check_timeout)
|
154
|
+
|
155
|
+
# If the child has failed the health check, we assume the worst and kill it immediately:
|
156
|
+
child.kill!
|
157
|
+
end
|
158
|
+
|
148
159
|
# Spawn a child instance into the container.
|
149
160
|
# @parameter name [String] The name of the child instance.
|
150
161
|
# @parameter restart [Boolean] Whether to restart the child instance if it fails.
|
@@ -176,9 +187,7 @@ module Async
|
|
176
187
|
case message
|
177
188
|
when :health_check!
|
178
189
|
if health_check_timeout&.<(age_clock.total)
|
179
|
-
|
180
|
-
# If the child has failed the health check, we assume the worst and kill it immediately:
|
181
|
-
child.kill!
|
190
|
+
health_check_failed!(child, age_clock, health_check_timeout)
|
182
191
|
end
|
183
192
|
else
|
184
193
|
state.update(message)
|
@@ -8,22 +8,23 @@ module Async
|
|
8
8
|
# Tracks a key/value pair such that unmarked keys can be identified and cleaned up.
|
9
9
|
# This helps implement persistent processes that start up child processes per directory or configuration file. If those directories and/or configuration files are removed, the child process can then be cleaned up automatically, because those key/value pairs will not be marked when reloading the container.
|
10
10
|
class Keyed
|
11
|
+
# Initialize the keyed instance
|
12
|
+
#
|
13
|
+
# @parameter key [Object] The key.
|
14
|
+
# @parameter value [Object] The value.
|
11
15
|
def initialize(key, value)
|
12
16
|
@key = key
|
13
17
|
@value = value
|
14
18
|
@marked = true
|
15
19
|
end
|
16
20
|
|
17
|
-
# The key
|
18
|
-
# @attribute [Object]
|
21
|
+
# @attribute [Object] The key value, normally a symbol or a file-system path.
|
19
22
|
attr :key
|
20
23
|
|
21
|
-
# The value
|
22
|
-
# @attribute [Object]
|
24
|
+
# @attribute [Object] The value, normally a child instance.
|
23
25
|
attr :value
|
24
26
|
|
25
|
-
#
|
26
|
-
# @returns [Boolean]
|
27
|
+
# @returns [Boolean] True if the instance has been marked, during reloading the container.
|
27
28
|
def marked?
|
28
29
|
@marked
|
29
30
|
end
|
@@ -39,6 +40,8 @@ module Async
|
|
39
40
|
end
|
40
41
|
|
41
42
|
# Stop the instance if it was not marked.
|
43
|
+
#
|
44
|
+
# @returns [Boolean] True if the instance was stopped.
|
42
45
|
def stop?
|
43
46
|
unless @marked
|
44
47
|
@value.stop
|
@@ -7,6 +7,9 @@ module Async
|
|
7
7
|
module Container
|
8
8
|
# Handles the details of several process readiness protocols.
|
9
9
|
module Notify
|
10
|
+
# Represents a client that can send messages to the parent controller in order to notify it of readiness, status changes, etc.
|
11
|
+
#
|
12
|
+
# A process readiness protocol (e.g. `sd_notify`) is a simple protocol for a child process to notify the parent process that it is ready (e.g. to accept connections, to process requests, etc). This can help dependency-based startup systems to start services in the correct order, and to handle failures gracefully.
|
10
13
|
class Client
|
11
14
|
# Notify the parent controller that the child has become ready, with a brief status message.
|
12
15
|
# @parameters message [Hash] Additional details to send with the message.
|
@@ -9,6 +9,7 @@ require "socket"
|
|
9
9
|
module Async
|
10
10
|
module Container
|
11
11
|
module Notify
|
12
|
+
# Represents a client that uses a local log file to communicate readiness, status changes, etc.
|
12
13
|
class Log < Client
|
13
14
|
# The name of the environment variable which contains the path to the notification socket.
|
14
15
|
NOTIFY_LOG = "NOTIFY_LOG"
|
@@ -11,9 +11,14 @@ require "securerandom"
|
|
11
11
|
module Async
|
12
12
|
module Container
|
13
13
|
module Notify
|
14
|
+
# A simple UDP server that can be used to receive messages from a child process, tracking readiness, status changes, etc.
|
14
15
|
class Server
|
15
16
|
MAXIMUM_MESSAGE_SIZE = 4096
|
16
17
|
|
18
|
+
# Parse a message, according to the `sd_notify` protocol.
|
19
|
+
#
|
20
|
+
# @parameter message [String] The message to parse.
|
21
|
+
# @returns [Hash] The parsed message.
|
17
22
|
def self.load(message)
|
18
23
|
lines = message.split("\n")
|
19
24
|
|
@@ -38,6 +43,9 @@ module Async
|
|
38
43
|
return Hash[pairs]
|
39
44
|
end
|
40
45
|
|
46
|
+
# Generate a new unique path for the UNIX socket.
|
47
|
+
#
|
48
|
+
# @returns [String] The path for the UNIX socket.
|
41
49
|
def self.generate_path
|
42
50
|
File.expand_path(
|
43
51
|
"async-container-#{::Process.pid}-#{SecureRandom.hex(8)}.ipc",
|
@@ -45,21 +53,33 @@ module Async
|
|
45
53
|
)
|
46
54
|
end
|
47
55
|
|
56
|
+
# Open a new server instance with a temporary and unique path.
|
48
57
|
def self.open(path = self.generate_path)
|
49
58
|
self.new(path)
|
50
59
|
end
|
51
60
|
|
61
|
+
# Initialize the server with the given path.
|
62
|
+
#
|
63
|
+
# @parameter path [String] The path to the UNIX socket.
|
52
64
|
def initialize(path)
|
53
65
|
@path = path
|
54
66
|
end
|
55
67
|
|
68
|
+
# @attribute [String] The path to the UNIX socket.
|
56
69
|
attr :path
|
57
70
|
|
71
|
+
# Generate a bound context for receiving messages.
|
72
|
+
#
|
73
|
+
# @returns [Context] The bound context.
|
58
74
|
def bind
|
59
75
|
Context.new(@path)
|
60
76
|
end
|
61
77
|
|
78
|
+
# A bound context for receiving messages.
|
62
79
|
class Context
|
80
|
+
# Initialize the context with the given path.
|
81
|
+
#
|
82
|
+
# @parameter path [String] The path to the UNIX socket.
|
63
83
|
def initialize(path)
|
64
84
|
@path = path
|
65
85
|
@bound = Addrinfo.unix(@path, ::Socket::SOCK_DGRAM).bind
|
@@ -67,12 +87,16 @@ module Async
|
|
67
87
|
@state = {}
|
68
88
|
end
|
69
89
|
|
90
|
+
# Close the bound context.
|
70
91
|
def close
|
71
92
|
@bound.close
|
72
93
|
|
73
94
|
File.unlink(@path)
|
74
95
|
end
|
75
96
|
|
97
|
+
# Receive a message from the bound context.
|
98
|
+
#
|
99
|
+
# @returns [Hash] The parsed message.
|
76
100
|
def receive
|
77
101
|
while true
|
78
102
|
data, _address, _flags, *_controls = @bound.recvmsg(MAXIMUM_MESSAGE_SIZE)
|
@@ -11,9 +11,6 @@ module Async
|
|
11
11
|
module Container
|
12
12
|
# A multi-thread container which uses {Thread.fork}.
|
13
13
|
class Threaded < Generic
|
14
|
-
class Kill < Exception
|
15
|
-
end
|
16
|
-
|
17
14
|
# Indicates that this is not a multi-process container.
|
18
15
|
def self.multiprocess?
|
19
16
|
false
|
@@ -52,12 +49,18 @@ module Async
|
|
52
49
|
return instance
|
53
50
|
end
|
54
51
|
|
52
|
+
# Initialize the child thread instance.
|
53
|
+
#
|
54
|
+
# @parameter io [IO] The IO object to use for communication with the parent.
|
55
55
|
def initialize(io)
|
56
56
|
@thread = ::Thread.current
|
57
57
|
|
58
58
|
super
|
59
59
|
end
|
60
60
|
|
61
|
+
# Generate a hash representation of the thread.
|
62
|
+
#
|
63
|
+
# @returns [Hash] The thread as a hash, including `process_id`, `thread_id`, and `name`.
|
61
64
|
def as_json(...)
|
62
65
|
{
|
63
66
|
process_id: ::Process.pid,
|
@@ -66,6 +69,9 @@ module Async
|
|
66
69
|
}
|
67
70
|
end
|
68
71
|
|
72
|
+
# Generate a JSON representation of the thread.
|
73
|
+
#
|
74
|
+
# @returns [String] The thread as JSON.
|
69
75
|
def to_json(...)
|
70
76
|
as_json.to_json(...)
|
71
77
|
end
|
@@ -101,6 +107,9 @@ module Async
|
|
101
107
|
end
|
102
108
|
end
|
103
109
|
|
110
|
+
# Start a new child thread and execute the provided block in it.
|
111
|
+
#
|
112
|
+
# @parameter options [Hash] Additional options to to the new child instance.
|
104
113
|
def self.fork(**options)
|
105
114
|
self.new(**options) do |thread|
|
106
115
|
::Thread.new do
|
@@ -113,6 +122,7 @@ module Async
|
|
113
122
|
end
|
114
123
|
|
115
124
|
# Initialize the thread.
|
125
|
+
#
|
116
126
|
# @parameter name [String] The name to use for the child thread.
|
117
127
|
def initialize(name: nil)
|
118
128
|
super()
|
@@ -230,6 +240,9 @@ module Async
|
|
230
240
|
@error.nil?
|
231
241
|
end
|
232
242
|
|
243
|
+
# Convert the status to a hash, suitable for serialization.
|
244
|
+
#
|
245
|
+
# @returns [Boolean | String] If the status is an error, the error message is returned, otherwise `true`.
|
233
246
|
def as_json(...)
|
234
247
|
if @error
|
235
248
|
@error.inspect
|
data/lib/async/container.rb
CHANGED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
5
|
+
|
6
|
+
require_relative "../../../../async/container/generic"
|
7
|
+
require "metrics/provider"
|
8
|
+
|
9
|
+
Metrics::Provider(Async::Container::Generic) do
|
10
|
+
ASYNC_CONTAINER_GENERIC_HEALTH_CHECK_FAILED = Metrics.metric("async.container.generic.health_check_failed", :counter, description: "The number of health checks that failed.")
|
11
|
+
|
12
|
+
protected def health_check_failed!(child, age_clock, health_check_timeout)
|
13
|
+
ASYNC_CONTAINER_GENERIC_HEALTH_CHECK_FAILED.emit(1)
|
14
|
+
|
15
|
+
super
|
16
|
+
end
|
17
|
+
end
|
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: async-container
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.24.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
@@ -40,7 +40,7 @@ cert_chain:
|
|
40
40
|
Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
|
41
41
|
voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
|
42
42
|
-----END CERTIFICATE-----
|
43
|
-
date: 2025-
|
43
|
+
date: 2025-03-07 00:00:00.000000000 Z
|
44
44
|
dependencies:
|
45
45
|
- !ruby/object:Gem::Dependency
|
46
46
|
name: async
|
@@ -80,6 +80,8 @@ files:
|
|
80
80
|
- lib/async/container/statistics.rb
|
81
81
|
- lib/async/container/threaded.rb
|
82
82
|
- lib/async/container/version.rb
|
83
|
+
- lib/metrics/provider/async/container.rb
|
84
|
+
- lib/metrics/provider/async/container/generic.rb
|
83
85
|
- license.md
|
84
86
|
- readme.md
|
85
87
|
- releases.md
|
metadata.gz.sig
CHANGED
Binary file
|