async-service 0.16.0 → 0.18.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: 1dc20befccfdbe1d36db11898e11a469ff64f2338a7b88942d59cb58e0e8c92f
4
- data.tar.gz: c2a09fad50e09a6188e30b213d921f832ac14706c8f23d721269e4e48cffe028
3
+ metadata.gz: a7fd9e53ca4f30c0166fb518aa104d292d67543377a6c93fa5df01b85148330d
4
+ data.tar.gz: ba8d1d6f6c994a40a7e7ff7536a81444faa9ebf01f087933a94ee1a3f590f6c0
5
5
  SHA512:
6
- metadata.gz: ef436a78425bfab299e187e37e81f6d442dd00ffbff6c3e526c94d5753395e03158219d3b92d7e3edec4a68babf277fc97448d2a18991b6ab1d79cfdd6cafb74
7
- data.tar.gz: 59468d0b53c26be506a8c4195cae58c91627352ebf09555bdcbf539d166c66d97f4c6a9ad3969fd0e8251f857cc058e4ad69e322ece8cd585b0579c0fbc44d69
6
+ metadata.gz: 74126e6fff458ce042d8233f148f8dd8042d8d26fce12bfea98a6d3622310bd5f8b3216344486391cdb9a60ee38416f6c3b2061228dc0fd2384c9034fddf53e1
7
+ data.tar.gz: 32572d45278190114e722909e14cd1d3bf3abcb66c17e5a70d97e4a0ed85ab0f323959e93a5b1bb46955897f9fc34ea6a2de66728b96ab43ed02321a2f94245c
checksums.yaml.gz.sig CHANGED
Binary file
@@ -208,6 +208,7 @@ class WebService < Async::Service::ManagedService
208
208
  # Managed::Service automatically handles:
209
209
  # - Container setup with proper options.
210
210
  # - Health checking with process title updates.
211
+ # - Status messages during startup to prevent premature timeouts.
211
212
  # - Preloading of scripts before startup.
212
213
 
213
214
  private def format_title(evaluator, server)
@@ -222,6 +223,28 @@ class WebService < Async::Service::ManagedService
222
223
  end
223
224
  ```
224
225
 
226
+ ### Configure Timeouts for Slow-Starting Services
227
+
228
+ If your service takes a long time to start up (e.g., loading large datasets, connecting to external services), configure appropriate timeouts:
229
+
230
+ ```ruby
231
+ module WebEnvironment
232
+ include Async::Service::ManagedEnvironment
233
+
234
+ # Allow 2 minutes for startup.
235
+ def startup_timeout
236
+ 120
237
+ end
238
+
239
+ # Require health checks every 30 seconds.
240
+ def health_check_timeout
241
+ 30
242
+ end
243
+ end
244
+ ```
245
+
246
+ The `startup_timeout` ensures processes that hang during startup are detected, while `health_check_timeout` monitors processes after they've become ready. `ManagedService` automatically sends `status!` messages during startup to keep the health check clock resetting until the service is ready.
247
+
225
248
  ### Implement Meaningful Process Titles
226
249
 
227
250
  Use the `format_title` method to provide dynamic process information:
@@ -262,7 +262,9 @@ end
262
262
 
263
263
  ### Health Checking
264
264
 
265
- For services using `Async::Service::ManagedService`, health checking is handled automatically. For services extending `GenericService`, you can set up health checking manually:
265
+ For services using `Async::Service::ManagedService`, health checking is handled automatically. The service sends `status!` messages during startup to prevent premature health check timeouts, then transitions to sending `ready!` messages once the service is actually ready.
266
+
267
+ For services extending `GenericService`, you can set up health checking manually:
266
268
 
267
269
  ```ruby
268
270
  def setup(container)
@@ -270,12 +272,15 @@ def setup(container)
270
272
  health_check_timeout = container_options[:health_check_timeout]
271
273
 
272
274
  container.run(**container_options) do |instance|
275
+ # Send status updates during startup:
276
+ instance.status!("Preparing...")
273
277
  # Prepare your service.
274
278
 
275
279
  Async do
276
280
  # Start your service.
281
+ instance.status!("Starting...")
277
282
 
278
- # Set up health checking, if a timeout was specified:
283
+ # Once ready, set up health checking:
279
284
  health_checker(instance, health_check_timeout) do
280
285
  instance.name = "#{self.name}: #{current_status}"
281
286
  end
@@ -284,6 +289,30 @@ def setup(container)
284
289
  end
285
290
  ```
286
291
 
292
+ #### Startup Timeout vs Health Check Timeout
293
+
294
+ The container supports two separate timeout mechanisms:
295
+
296
+ - **`startup_timeout`**: Maximum time a process can take to become ready (call `ready!`) before being terminated. This detects processes that hang during startup and never become ready.
297
+ - **`health_check_timeout`**: Maximum time between health check messages after the process has become ready. This detects processes that stop responding after they've started.
298
+
299
+ Both timeouts use a single clock that starts when the process starts. The clock resets when the process becomes ready, transitioning from startup timeout monitoring to health check timeout monitoring.
300
+
301
+ You can configure these timeouts via `container_options`:
302
+
303
+ ```ruby
304
+ module WebEnvironment
305
+ include Async::Service::ManagedEnvironment
306
+
307
+ def container_options
308
+ super.merge(
309
+ startup_timeout: 60, # Allow up to 60 seconds for startup.
310
+ health_check_timeout: 30 # Require health checks every 30 seconds after ready.
311
+ )
312
+ end
313
+ end
314
+ ```
315
+
287
316
  Note: `Async::Service::ManagedService` automatically handles health checking, container options, and process title formatting, so you typically don't need to set this up manually.
288
317
 
289
318
  ## How They Work Together
@@ -21,7 +21,12 @@ module Async
21
21
  configuration = self.new
22
22
 
23
23
  loader = Loader.new(configuration, root)
24
- loader.instance_eval(&block)
24
+
25
+ if block.arity == 0
26
+ loader.instance_eval(&block)
27
+ else
28
+ yield loader
29
+ end
25
30
 
26
31
  return configuration
27
32
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2024-2025, by Samuel Williams.
4
+ # Copyright, 2025-2026, by Samuel Williams.
5
5
 
6
6
  module Async
7
7
  module Service
@@ -28,10 +28,10 @@ module Async
28
28
  # We deliberately create a fiber here, to confirm that fiber creation is working.
29
29
  # If something has gone wrong with fiber allocation, we will crash here, and that's okay.
30
30
  Fiber.new do
31
- instance.ready!
31
+ instance.healthy!
32
32
  end.resume
33
33
 
34
- sleep(timeout / 2)
34
+ sleep(timeout / 2.0)
35
35
  end
36
36
  end
37
37
  else
@@ -39,7 +39,7 @@ module Async
39
39
  yield(instance)
40
40
  end
41
41
 
42
- instance.ready!
42
+ instance.healthy!
43
43
  end
44
44
  end
45
45
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2025, by Samuel Williams.
4
+ # Copyright, 2025-2026, by Samuel Williams.
5
5
 
6
6
  module Async
7
7
  module Service
@@ -16,6 +16,13 @@ module Async
16
16
  nil
17
17
  end
18
18
 
19
+ # The timeout duration for the startup. Set to `nil` to disable the startup timeout.
20
+ #
21
+ # @returns [Numeric | nil] The startup timeout in seconds.
22
+ def startup_timeout
23
+ nil
24
+ end
25
+
19
26
  # The timeout duration for the health check. Set to `nil` to disable the health check.
20
27
  #
21
28
  # @returns [Numeric | nil] The health check timeout in seconds.
@@ -30,6 +37,7 @@ module Async
30
37
  {
31
38
  restart: true,
32
39
  count: self.count,
40
+ startup_timeout: self.startup_timeout,
33
41
  health_check_timeout: self.health_check_timeout,
34
42
  }.compact
35
43
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2025, by Samuel Williams.
4
+ # Copyright, 2025-2026, by Samuel Williams.
5
5
 
6
6
  require_relative "generic_service"
7
7
  require_relative "health_checker"
@@ -46,18 +46,34 @@ module Async
46
46
  rescue StandardError, LoadError => error
47
47
  Console.warn(self, "Service preload failed!", error)
48
48
  end
49
-
49
+
50
50
  # Start the service, including preloading resources.
51
51
  def start
52
52
  preload!
53
53
 
54
54
  super
55
55
  end
56
-
57
- def emit_prepared(instance, start_time)
56
+
57
+ # Called after the service has been prepared but before it starts running.
58
+ #
59
+ # Override this method to emit metrics, logs, or perform other actions when the service preparation is complete.
60
+ #
61
+ # @parameter instance [Async::Container::Instance] The container instance.
62
+ # @parameter clock [Async::Clock] The monotonic start time from {Async::Clock.start}.
63
+ def emit_prepared(instance, clock)
64
+ # Override in subclasses as needed.
65
+ Console.info(self, "Prepared...", duration: clock.total)
58
66
  end
59
-
60
- def emit_running(instance, start_time)
67
+
68
+ # Called after the service has started running.
69
+ #
70
+ # Override this method to emit metrics, logs, or perform other actions when the service begins running.
71
+ #
72
+ # @parameter instance [Async::Container::Instance] The container instance.
73
+ # @parameter clock [Async::Clock] The monotonic start time from {Async::Clock.start}.
74
+ def emit_running(instance, clock)
75
+ # Override in subclasses as needed.
76
+ Console.info(self, "Running...", duration: clock.total)
61
77
  end
62
78
 
63
79
  # Set up the container with health checking and process title formatting.
@@ -69,22 +85,27 @@ module Async
69
85
  health_check_timeout = container_options[:health_check_timeout]
70
86
 
71
87
  container.run(**container_options) do |instance|
72
- start_time = Async::Clock.start
73
-
88
+ clock = Async::Clock.start
89
+
74
90
  Async do
75
91
  evaluator = self.environment.evaluator
92
+ server = nil
93
+
94
+ health_checker(instance, health_check_timeout) do
95
+ if server
96
+ instance.name = format_title(evaluator, server)
97
+ end
98
+ end
76
99
 
100
+ instance.status!("Preparing...")
77
101
  evaluator.prepare!(instance)
102
+ emit_prepared(instance, clock)
78
103
 
79
- emit_prepared(instance, start_time)
80
-
104
+ instance.status!("Running...")
81
105
  server = run(instance, evaluator)
106
+ emit_running(instance, clock)
82
107
 
83
- emit_running(instance, start_time)
84
-
85
- health_checker(instance) do
86
- instance.name = format_title(evaluator, server)
87
- end
108
+ instance.ready!
88
109
  end
89
110
  end
90
111
  end
@@ -5,6 +5,6 @@
5
5
 
6
6
  module Async
7
7
  module Service
8
- VERSION = "0.16.0"
8
+ VERSION = "0.18.0"
9
9
  end
10
10
  end
data/license.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # MIT License
2
2
 
3
- Copyright, 2024-2025, by Samuel Williams.
3
+ Copyright, 2024-2026, by Samuel Williams.
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/readme.md CHANGED
@@ -29,6 +29,17 @@ Please see the [project documentation](https://socketry.github.io/async-service/
29
29
 
30
30
  Please see the [project releases](https://socketry.github.io/async-service/releases/index) for all releases.
31
31
 
32
+ ### v0.18.0
33
+
34
+ - Start health checker earlier in the process. Use `#healthy!` message instead of `#ready!`.
35
+ - Emit prepared and running log messages with durations (e.g. how long it took to transition to prepared and running states).
36
+ - `Async::Service::Configuration.build{|loader|...}` can now take an argument for more flexible configuration construction.
37
+
38
+ ### v0.17.0
39
+
40
+ - `ManagedService` now sends `status!` messages during startup to prevent premature health check timeouts for slow-starting services.
41
+ - Support for `startup_timeout` option via `container_options` to detect processes that hang during startup and never become ready.
42
+
32
43
  ### v0.16.0
33
44
 
34
45
  - Renamed `Async::Service::Generic` -\> `Async::Service::GenericService`, added compatibilty alias.
@@ -67,15 +78,6 @@ Please see the [project releases](https://socketry.github.io/async-service/relea
67
78
 
68
79
  - Allow builder with argument for more flexible configuration construction.
69
80
 
70
- ### v0.10.0
71
-
72
- - Add `Environment::Evaluator#as_json` for JSON serialization support.
73
- - Allow constructing a configuration with existing environments.
74
-
75
- ### v0.9.0
76
-
77
- - Allow providing a list of modules to include in environments.
78
-
79
81
  ## Contributing
80
82
 
81
83
  We welcome contributions to this project.
data/releases.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Releases
2
2
 
3
+ ## v0.18.0
4
+
5
+ - Start health checker earlier in the process. Use `#healthy!` message instead of `#ready!`.
6
+ - Emit prepared and running log messages with durations (e.g. how long it took to transition to prepared and running states).
7
+ - `Async::Service::Configuration.build{|loader|...}` can now take an argument for more flexible configuration construction.
8
+
9
+ ## v0.17.0
10
+
11
+ - `ManagedService` now sends `status!` messages during startup to prevent premature health check timeouts for slow-starting services.
12
+ - Support for `startup_timeout` option via `container_options` to detect processes that hang during startup and never become ready.
13
+
3
14
  ## v0.16.0
4
15
 
5
16
  - Renamed `Async::Service::Generic` -\> `Async::Service::GenericService`, added compatibilty alias.
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: async-service
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.16.0
4
+ version: 0.18.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -58,14 +58,14 @@ dependencies:
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '0.16'
61
+ version: '0.29'
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '0.16'
68
+ version: '0.29'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: string-format
71
71
  requirement: !ruby/object:Gem::Requirement
metadata.gz.sig CHANGED
Binary file