async-container 0.29.1 → 0.30.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/controller.rb +10 -7
- data/lib/async/container/forked.rb +2 -2
- data/lib/async/container/generic.rb +2 -2
- data/lib/async/container/group.rb +34 -38
- data/lib/async/container/notify/pipe.rb +1 -1
- data/lib/async/container/notify/server.rb +9 -0
- data/lib/async/container/threaded.rb +1 -1
- data/lib/async/container/version.rb +1 -1
- data/readme.md +5 -4
- data/releases.md +5 -0
- data.tar.gz.sig +0 -0
- metadata +1 -1
- 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: 84ca0883f2ae0742253c1f2bdb08c98152770c41609ed179a81f2249d9cc46f7
|
|
4
|
+
data.tar.gz: 5d551d20be155c60557783ecf4064d0d90a3748ba18d9c66324a515208671bf0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fddd4b0f2ea6f459a0ad39a3e806b776abda60cb97fbface314f6f93b0690fac817b0f8b09a486aa387c1dd06dc6f6ae3c9620d146ef4e2f50318532d36ac20d
|
|
7
|
+
data.tar.gz: cd14547b2e6be8a9139bcadfbf5593e777d04d6f26f02cff4c808d9b2ba3c205e36acfc19af646dca95e2f1bd52edf9020d79461a6c5987304c28eb5bc271516
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
|
@@ -134,9 +134,6 @@ module Async
|
|
|
134
134
|
if container.failed?
|
|
135
135
|
@notify&.error!("Container failed to start!")
|
|
136
136
|
|
|
137
|
-
Console.info(self, "Stopping failed container...")
|
|
138
|
-
container.stop(false)
|
|
139
|
-
|
|
140
137
|
raise SetupError, container
|
|
141
138
|
end
|
|
142
139
|
|
|
@@ -151,9 +148,14 @@ module Async
|
|
|
151
148
|
end
|
|
152
149
|
|
|
153
150
|
@notify&.ready!(size: @container.size)
|
|
151
|
+
rescue => error
|
|
152
|
+
raise
|
|
154
153
|
ensure
|
|
155
|
-
# If we are leaving this function with an exception,
|
|
156
|
-
container
|
|
154
|
+
# If we are leaving this function with an exception, kill the container:
|
|
155
|
+
if container
|
|
156
|
+
Console.warn(self, "Stopping failed container...", exception: error)
|
|
157
|
+
container.stop(false)
|
|
158
|
+
end
|
|
157
159
|
end
|
|
158
160
|
|
|
159
161
|
# Reload the existing container. Children instances will be reloaded using `SIGHUP`.
|
|
@@ -222,9 +224,10 @@ module Async
|
|
|
222
224
|
::Thread.current.raise(Interrupt)
|
|
223
225
|
end
|
|
224
226
|
|
|
227
|
+
# SIGTERM behaves the same as SIGINT by default.
|
|
225
228
|
terminate_action = Signal.trap(:TERM) do
|
|
226
|
-
# $stderr.puts "Received TERM signal,
|
|
227
|
-
::Thread.current.raise(
|
|
229
|
+
# $stderr.puts "Received TERM signal, interrupting...", caller
|
|
230
|
+
::Thread.current.raise(Interrupt) # Same as SIGINT
|
|
228
231
|
end
|
|
229
232
|
|
|
230
233
|
hangup_action = Signal.trap(:HUP) do
|
|
@@ -102,7 +102,7 @@ module Async
|
|
|
102
102
|
::Process.fork do
|
|
103
103
|
# We use `Thread.current.raise(...)` so that exceptions are filtered through `Thread.handle_interrupt` correctly.
|
|
104
104
|
Signal.trap(:INT){::Thread.current.raise(Interrupt)}
|
|
105
|
-
Signal.trap(:TERM){::Thread.current.raise(
|
|
105
|
+
Signal.trap(:TERM){::Thread.current.raise(Interrupt)} # Same as SIGINT.
|
|
106
106
|
Signal.trap(:HUP){::Thread.current.raise(Restart)}
|
|
107
107
|
|
|
108
108
|
# This could be a configuration option:
|
|
@@ -245,7 +245,7 @@ module Async
|
|
|
245
245
|
_, @status = ::Process.wait2(@pid, ::Process::WNOHANG)
|
|
246
246
|
|
|
247
247
|
if @status.nil?
|
|
248
|
-
Console.warn(self, "Process is blocking, sending kill signal...", child: {process_id: @pid},
|
|
248
|
+
Console.warn(self, "Process is blocking, sending kill signal...", child: {process_id: @pid}, timeout: timeout)
|
|
249
249
|
self.kill!
|
|
250
250
|
|
|
251
251
|
# Wait for the process to exit:
|
|
@@ -126,7 +126,7 @@ module Async
|
|
|
126
126
|
self.sleep
|
|
127
127
|
|
|
128
128
|
if self.status?(:ready)
|
|
129
|
-
Console.
|
|
129
|
+
Console.debug(self) do |buffer|
|
|
130
130
|
buffer.puts "All ready:"
|
|
131
131
|
@state.each do |child, state|
|
|
132
132
|
buffer.puts "\t#{child.inspect}: #{state}"
|
|
@@ -141,7 +141,7 @@ module Async
|
|
|
141
141
|
# Stop the children instances.
|
|
142
142
|
# @parameter timeout [Boolean | Numeric] Whether to stop gracefully, or a specific timeout.
|
|
143
143
|
def stop(timeout = true)
|
|
144
|
-
Console.
|
|
144
|
+
Console.info(self, "Stopping container...", timeout: timeout)
|
|
145
145
|
@running = false
|
|
146
146
|
@group.stop(timeout)
|
|
147
147
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2018-
|
|
4
|
+
# Copyright, 2018-2026, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
require "fiber"
|
|
7
7
|
require "async/clock"
|
|
@@ -10,11 +10,20 @@ require_relative "error"
|
|
|
10
10
|
|
|
11
11
|
module Async
|
|
12
12
|
module Container
|
|
13
|
-
# The default timeout for interrupting processes, before escalating to terminating.
|
|
14
|
-
INTERRUPT_TIMEOUT = ENV.fetch("ASYNC_CONTAINER_INTERRUPT_TIMEOUT", 10).to_f
|
|
15
|
-
|
|
16
13
|
# The default timeout for terminating processes, before escalating to killing.
|
|
17
|
-
|
|
14
|
+
GRACEFUL_TIMEOUT = ENV.fetch("ASYNC_CONTAINER_GRACEFUL_TIMEOUT", "true").then do |value|
|
|
15
|
+
case value
|
|
16
|
+
when "true"
|
|
17
|
+
true # Default timeout for graceful termination.
|
|
18
|
+
when "false"
|
|
19
|
+
false # Immediately kill the processes.
|
|
20
|
+
else
|
|
21
|
+
value.to_f
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# The default timeout for graceful termination.
|
|
26
|
+
DEFAULT_GRACEFUL_TIMEOUT = 10.0
|
|
18
27
|
|
|
19
28
|
# Manages a group of running processes.
|
|
20
29
|
class Group
|
|
@@ -155,50 +164,37 @@ module Async
|
|
|
155
164
|
# Stop all child processes with a multi-phase shutdown sequence.
|
|
156
165
|
#
|
|
157
166
|
# A graceful shutdown performs the following sequence:
|
|
158
|
-
# 1. Send SIGINT and wait up to `
|
|
159
|
-
# 2. Send
|
|
160
|
-
# 3. Send SIGKILL and wait indefinitely for process cleanup
|
|
167
|
+
# 1. Send SIGINT and wait up to `graceful` seconds if specified.
|
|
168
|
+
# 2. Send SIGKILL and wait indefinitely for process cleanup.
|
|
161
169
|
#
|
|
162
|
-
# If `graceful` is
|
|
170
|
+
# If `graceful` is true, default to `DEFAULT_GRACEFUL_TIMEOUT` (10 seconds).
|
|
171
|
+
# If `graceful` is false, skip the SIGINT phase and go directly to SIGKILL.
|
|
163
172
|
#
|
|
164
|
-
# @parameter graceful [Boolean] Whether to send SIGINT first or skip directly to
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
def stop(graceful = true, interrupt_timeout: INTERRUPT_TIMEOUT, terminate_timeout: TERMINATE_TIMEOUT)
|
|
168
|
-
case graceful
|
|
169
|
-
when true
|
|
170
|
-
# Use defaults.
|
|
171
|
-
when false
|
|
172
|
-
interrupt_timeout = nil
|
|
173
|
-
when Numeric
|
|
174
|
-
interrupt_timeout = graceful
|
|
175
|
-
terminate_timeout = graceful
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
Console.debug(self, "Stopping all processes...", interrupt_timeout: interrupt_timeout, terminate_timeout: terminate_timeout)
|
|
173
|
+
# @parameter graceful [Boolean | Numeric] Whether to send SIGINT first or skip directly to SIGKILL.
|
|
174
|
+
def stop(graceful = GRACEFUL_TIMEOUT)
|
|
175
|
+
Console.debug(self, "Stopping all processes...", graceful: graceful)
|
|
179
176
|
|
|
180
177
|
# If a timeout is specified, interrupt the children first:
|
|
181
|
-
if
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
# Interrupt the children:
|
|
178
|
+
if graceful
|
|
179
|
+
# Send SIGINT to the children:
|
|
185
180
|
self.interrupt
|
|
186
181
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
if terminate_timeout and self.any?
|
|
192
|
-
clock = Async::Clock.start
|
|
182
|
+
if graceful == true
|
|
183
|
+
graceful = DEFAULT_GRACEFUL_TIMEOUT
|
|
184
|
+
end
|
|
193
185
|
|
|
194
|
-
|
|
195
|
-
self.terminate
|
|
186
|
+
clock = Clock.start
|
|
196
187
|
|
|
197
188
|
# Wait for the children to exit:
|
|
198
|
-
self.wait_for_exit(clock,
|
|
189
|
+
self.wait_for_exit(clock, graceful)
|
|
199
190
|
end
|
|
200
|
-
|
|
191
|
+
ensure
|
|
192
|
+
# Do our best to clean up the children:
|
|
201
193
|
if any?
|
|
194
|
+
if graceful
|
|
195
|
+
Console.warn(self, "Killing processes after graceful shutdown failed...", size: self.size, clock: clock)
|
|
196
|
+
end
|
|
197
|
+
|
|
202
198
|
self.kill
|
|
203
199
|
self.wait
|
|
204
200
|
end
|
|
@@ -129,6 +129,15 @@ module Async
|
|
|
129
129
|
end
|
|
130
130
|
end
|
|
131
131
|
end
|
|
132
|
+
|
|
133
|
+
# Wait until a "ready" message is received from the child process.
|
|
134
|
+
def wait_until_ready
|
|
135
|
+
while message = receive
|
|
136
|
+
if message[:ready] == true
|
|
137
|
+
return
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
132
141
|
end
|
|
133
142
|
end
|
|
134
143
|
end
|
|
@@ -225,7 +225,7 @@ module Async
|
|
|
225
225
|
Console.debug(self, "Waiting for thread to exit...", child: {thread_id: @thread.object_id}, timeout: timeout)
|
|
226
226
|
|
|
227
227
|
unless @waiter.join(timeout)
|
|
228
|
-
Console.warn(self, "Thread is blocking, sending kill signal...", child: {thread_id: @thread.object_id},
|
|
228
|
+
Console.warn(self, "Thread is blocking, sending kill signal...", child: {thread_id: @thread.object_id}, timeout: timeout)
|
|
229
229
|
self.kill!
|
|
230
230
|
@waiter.join
|
|
231
231
|
end
|
data/readme.md
CHANGED
|
@@ -26,6 +26,11 @@ Please see the [project documentation](https://socketry.github.io/async-containe
|
|
|
26
26
|
|
|
27
27
|
Please see the [project releases](https://socketry.github.io/async-container/releases/index) for all releases.
|
|
28
28
|
|
|
29
|
+
### v0.30.0
|
|
30
|
+
|
|
31
|
+
- `SIGTERM` is now graceful, the same as `SIGINT`, for better compatibility with Kubernetes and systemd.
|
|
32
|
+
- `ASYNC_CONTAINER_INTERRUPT_TIMEOUT` and `ASYNC_CONTAINER_TERMINATE_TIMEOUT` are removed and replaced by `ASYNC_CONTAINER_GRACEFUL_TIMEOUT`.
|
|
33
|
+
|
|
29
34
|
### v0.29.0
|
|
30
35
|
|
|
31
36
|
- Introduce `Client#healthy!` for sending health check messages.
|
|
@@ -65,10 +70,6 @@ Please see the [project releases](https://socketry.github.io/async-container/rel
|
|
|
65
70
|
|
|
66
71
|
- [Production Reliability Improvements](https://socketry.github.io/async-container/releases/index#production-reliability-improvements)
|
|
67
72
|
|
|
68
|
-
### v0.25.0
|
|
69
|
-
|
|
70
|
-
- Introduce `async:container:notify:log:ready?` task for detecting process readiness.
|
|
71
|
-
|
|
72
73
|
## Contributing
|
|
73
74
|
|
|
74
75
|
We welcome contributions to this project.
|
data/releases.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Releases
|
|
2
2
|
|
|
3
|
+
## v0.30.0
|
|
4
|
+
|
|
5
|
+
- `SIGTERM` is now graceful, the same as `SIGINT`, for better compatibility with Kubernetes and systemd.
|
|
6
|
+
- `ASYNC_CONTAINER_INTERRUPT_TIMEOUT` and `ASYNC_CONTAINER_TERMINATE_TIMEOUT` are removed and replaced by `ASYNC_CONTAINER_GRACEFUL_TIMEOUT`.
|
|
7
|
+
|
|
3
8
|
## v0.29.0
|
|
4
9
|
|
|
5
10
|
- Introduce `Client#healthy!` for sending health check messages.
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
metadata.gz.sig
CHANGED
|
Binary file
|