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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e7d6beef9d67eb3befeb6df39c14194bef86cefa104924e7eaff052571efbf68
4
- data.tar.gz: d18ac658a4dac083de68527533e1f476dfe56085b4ba67a46fa9e3ba8e92fa45
3
+ metadata.gz: d8b5dae742b5445c83c515d4745a49df725f9eea920e43340913b19239af8aa6
4
+ data.tar.gz: b4153930bb6ee37055e5675c921193ec27221c754ced2250975a2ba13fb0fda8
5
5
  SHA512:
6
- metadata.gz: 285a31c7314187bae1a4866e5865f817a33f1f4192dc6df0991d345b71f8ca148de56bc3cae8c2c36199cffafa281b20efec533d2ba636fa11b031afed287496
7
- data.tar.gz: c8d2458ee451774b353512e8eeab6351d85695775d9a3d09f326f83bb3b462a31830701b82e8867e177bcb9f58b4085b19ed1832c7ab8229afb63b718fb1ac05
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
- def self.run(*arguments, **options, &block)
36
- self.new.run(*arguments, **options, &block)
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
- Console.warn(self, "Child failed health check!", child: child, age: age_clock.total, health_check_timeout: health_check_timeout)
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)
@@ -25,6 +25,7 @@ module Async
25
25
  @queue = nil
26
26
  end
27
27
 
28
+ # @returns [String] A human-readable representation of the group.
28
29
  def inspect
29
30
  "#<#{self.class} running=#{@running.size}>"
30
31
  end
@@ -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. Normally a symbol or a file-system path.
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. Normally a child instance of some sort.
22
- # @attribute [Object]
24
+ # @attribute [Object] The value, normally a child instance.
23
25
  attr :value
24
26
 
25
- # Has the instance been marked?
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)
@@ -9,6 +9,7 @@ module Async
9
9
  module Container
10
10
  # Tracks various statistics relating to child instances in a container.
11
11
  class Statistics
12
+ # Initialize the statistics all to 0.
12
13
  def initialize
13
14
  @spawns = 0
14
15
  @restarts = 0
@@ -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
@@ -5,6 +5,6 @@
5
5
 
6
6
  module Async
7
7
  module Container
8
- VERSION = "0.23.2"
8
+ VERSION = "0.24.0"
9
9
  end
10
10
  end
@@ -5,7 +5,9 @@
5
5
 
6
6
  require_relative "container/controller"
7
7
 
8
+ # @namespace
8
9
  module Async
10
+ # @namespace
9
11
  module Container
10
12
  end
11
13
  end
@@ -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
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2025, by Samuel Williams.
5
+
6
+ require_relative "container/generic"
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.23.2
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-02-28 00:00:00.000000000 Z
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