async-service 0.18.1 → 0.19.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: 9a133d45fe4dd738fa104a0df7d8ff74279856b7f9eeb1567b22e19eab58e4c0
4
- data.tar.gz: 5c655ba303639dbb3040cc84b211ebf117b7bf45af044471ee9e3ab91397b36c
3
+ metadata.gz: 9b31f83d5dbdb36dfdf3e6be399d8ed560caedb465854a537a7c40741d121632
4
+ data.tar.gz: edae5fada2d9ebcd50b89a495df5901cd78a1c6eee94b270986592cf3ca79cfb
5
5
  SHA512:
6
- metadata.gz: 136b018820c0227c4755803b82ad6857e059b48cf70e10b7fca4bfd793a16f8586ee0750ba314f18e8b3fb2b719998ca22d04fb110823a1b65c11c253e638ee5
7
- data.tar.gz: 537237974adbfc334426e998a289f7ce48af50d82a9c8a443790207b801f068938374bde1683b4666daac94dd3c37b9feff9aa890a768e8e77c3bddc3ec70250
6
+ metadata.gz: 5a0fe39a4f3a4bba63ad3bfefcafaf068191e3db7e710d0a3a7eaf9a5f92601035431aa088a39163a70426ad31dd03302c609f3a25996593290df33aac2b46a9
7
+ data.tar.gz: 37fc1659dd3e2cc747dc01fa85915694b7952eda09d361c1e72977f07804471221be796a0339fc61a98180acdc8042cca7fe0f195afdb5337a2f6ffae3ac8522
checksums.yaml.gz.sig CHANGED
Binary file
@@ -61,7 +61,7 @@ Place environments in `lib/my_library/environment/`:
61
61
  module MyLibrary
62
62
  module Environment
63
63
  module WebEnvironment
64
- include Async::Service::ManagedEnvironment
64
+ include Async::Service::Managed::Environment
65
65
 
66
66
  def service_class
67
67
  MyLibrary::Service::WebService
@@ -87,7 +87,7 @@ Place services in `lib/my_library/service/`:
87
87
  # lib/my_library/service/web_service.rb
88
88
  module MyLibrary
89
89
  module Service
90
- class WebService < Async::Service::ManagedService
90
+ class WebService < Async::Service::Managed::Service
91
91
  private def format_title(evaluator, server)
92
92
  if server&.respond_to?(:connection_count)
93
93
  "#{self.name} [#{evaluator.host}:#{evaluator.port}] (#{server.connection_count} connections)"
@@ -114,7 +114,7 @@ end
114
114
 
115
115
  ### Use `Managed::Environment` for Services
116
116
 
117
- Include {ruby Async::Service::ManagedEnvironment} for services that need robust lifecycle management using {ruby Async::Service::ManagedService}:
117
+ Include {ruby Async::Service::Managed::Environment} for services that need robust lifecycle management using {ruby Async::Service::Managed::Service}:
118
118
 
119
119
  ```ruby
120
120
  module WebEnvironment
@@ -201,7 +201,7 @@ end
201
201
 
202
202
  ### Use `Managed::Service` as Base Class
203
203
 
204
- Prefer `Async::Service::ManagedService` over `GenericService` for most services:
204
+ Prefer `Async::Service::Managed::Service` over `Generic` for most services:
205
205
 
206
206
  ```ruby
207
207
  class WebService < Async::Service::ManagedService
@@ -243,7 +243,7 @@ module WebEnvironment
243
243
  end
244
244
  ```
245
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.
246
+ The `startup_timeout` ensures processes that hang during startup are detected, while `health_check_timeout` monitors processes after they've become ready. `Managed::Service` automatically sends `status!` messages during startup to keep the health check clock resetting until the service is ready.
247
247
 
248
248
  ### Implement Meaningful Process Titles
249
249
 
@@ -14,7 +14,7 @@ require "async/http"
14
14
  require "async/service/managed_service"
15
15
  require "async/service/managed_environment"
16
16
 
17
- class WebService < Async::Service::ManagedService
17
+ class WebService < Async::Service::Managed::Service
18
18
  def start
19
19
  super
20
20
  @endpoint = @evaluator.endpoint
@@ -40,7 +40,7 @@ class WebService < Async::Service::ManagedService
40
40
  end
41
41
 
42
42
  module WebEnvironment
43
- include Async::Service::ManagedEnvironment
43
+ include Async::Service::Managed::Environment
44
44
 
45
45
  def endpoint
46
46
  Async::HTTP::Endpoint.parse("http://0.0.0.0:3000")
@@ -14,13 +14,13 @@ $ bundle add async-service
14
14
 
15
15
  `async-service` has several core concepts:
16
16
 
17
- - A {ruby Async::Service::GenericService} which represents the base class for implementing services.
17
+ - A {ruby Async::Service::Generic} which represents the base class for implementing services.
18
18
  - A {ruby Async::Service::Configuration} which manages service configurations and environments.
19
19
  - A {ruby Async::Service::Controller} which handles starting, stopping, and managing services.
20
20
 
21
21
  ## Usage
22
22
 
23
- Services are long-running processes that can be managed as a group. Each service extends `Async::Service::GenericService` and implements a `setup` method that defines how the service runs.
23
+ Services are long-running processes that can be managed as a group. Each service extends `Async::Service::Generic` and implements a `setup` method that defines how the service runs.
24
24
 
25
25
  ### Basic Service
26
26
 
@@ -31,7 +31,7 @@ Create a simple service that runs continuously:
31
31
 
32
32
  require "async/service"
33
33
 
34
- class HelloService < Async::Service::GenericService
34
+ class HelloService < Async::Service::Generic
35
35
  def setup(container)
36
36
  super
37
37
 
@@ -73,7 +73,7 @@ end
73
73
  In your service implementation, you can access these values through the environment and evaluator:
74
74
 
75
75
  ```ruby
76
- class WebServerService < Async::Service::GenericService
76
+ class WebServerService < Async::Service::Generic
77
77
  def setup(container)
78
78
  super
79
79
 
@@ -103,7 +103,7 @@ You can define multiple services in a single configuration file:
103
103
 
104
104
  require "async/service"
105
105
 
106
- class WebService < Async::Service::GenericService
106
+ class WebService < Async::Service::Generic
107
107
  def setup(container)
108
108
  super
109
109
 
@@ -115,7 +115,7 @@ class WebService < Async::Service::GenericService
115
115
  end
116
116
  end
117
117
 
118
- class WorkerService < Async::Service::GenericService
118
+ class WorkerService < Async::Service::Generic
119
119
  def setup(container)
120
120
  super
121
121
 
@@ -82,14 +82,14 @@ end
82
82
 
83
83
  ## Service
84
84
 
85
- The {ruby Async::Service::GenericService} represents the service implementation layer. It handles the actual business logic of your services, provides access to configuration through environment evaluators, and manages the service lifecycle including startup, execution, and shutdown phases.
85
+ The {ruby Async::Service::Generic} represents the service implementation layer. It handles the actual business logic of your services, provides access to configuration through environment evaluators, and manages the service lifecycle including startup, execution, and shutdown phases.
86
86
 
87
87
  ### Business Logic
88
88
 
89
89
  Services contain the actual implementation of what your service does:
90
90
 
91
91
  ```ruby
92
- class WebService < Async::Service::GenericService
92
+ class WebService < Async::Service::Generic
93
93
  def setup(container)
94
94
  super
95
95
 
@@ -148,7 +148,7 @@ By evaluating the log_path in the child process, you ensure that each instance h
148
148
  Services define their startup, running, and shutdown behavior:
149
149
 
150
150
  ```ruby
151
- class MyService < Async::Service::GenericService
151
+ class MyService < Async::Service::Generic
152
152
  def start
153
153
  super
154
154
  # Service-specific startup logic including pre-loading libraries and binding to network interfaces before forking.
@@ -262,9 +262,9 @@ end
262
262
 
263
263
  ### Health Checking
264
264
 
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.
265
+ For services using `Async::Service::Managed::Service`, 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
266
 
267
- For services extending `GenericService`, you can set up health checking manually:
267
+ For services extending `Generic`, you can set up health checking manually:
268
268
 
269
269
  ```ruby
270
270
  def setup(container)
@@ -302,7 +302,7 @@ You can configure these timeouts via `container_options`:
302
302
 
303
303
  ```ruby
304
304
  module WebEnvironment
305
- include Async::Service::ManagedEnvironment
305
+ include Async::Service::Managed::Environment
306
306
 
307
307
  def container_options
308
308
  super.merge(
@@ -313,7 +313,7 @@ module WebEnvironment
313
313
  end
314
314
  ```
315
315
 
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.
316
+ Note: `Async::Service::Managed::Service` automatically handles health checking, container options, and process title formatting, so you typically don't need to set this up manually.
317
317
 
318
318
  ## How They Work Together
319
319
 
@@ -334,7 +334,7 @@ end
334
334
 
335
335
  ```ruby
336
336
  # Services are defined using environments:
337
- class WebService < Async::Service::GenericService
337
+ class WebService < Async::Service::Generic
338
338
  def setup(container)
339
339
  super
340
340
 
@@ -469,7 +469,7 @@ Create reusable configuration modules:
469
469
 
470
470
  ```ruby
471
471
  module ManagedEnvironment
472
- include Async::Service::ManagedEnvironment
472
+ include Async::Service::Managed::Environment
473
473
 
474
474
  def count
475
475
  4
@@ -493,7 +493,7 @@ end
493
493
  Build service hierarchies:
494
494
 
495
495
  ```ruby
496
- class BaseWebService < Async::Service::GenericService
496
+ class BaseWebService < Async::Service::Generic
497
497
  def setup(container)
498
498
  super
499
499
  # Common web service setup
@@ -75,7 +75,7 @@ module Async
75
75
 
76
76
  @environments.each do |environment|
77
77
  if implementing.nil? or environment.implements?(implementing)
78
- if service = GenericService.wrap(environment)
78
+ if service = Generic.wrap(environment)
79
79
  yield service
80
80
  end
81
81
  end
@@ -42,7 +42,7 @@ module Async
42
42
  end
43
43
 
44
44
  # Create a controller for the given services.
45
- # @parameter services [Array(GenericService)] The services to control.
45
+ # @parameter services [Array(Generic)] The services to control.
46
46
  # @parameter options [Hash] Additional options for the controller.
47
47
  # @returns [Controller] A new controller instance.
48
48
  def self.for(*services, **options)
@@ -50,7 +50,7 @@ module Async
50
50
  end
51
51
 
52
52
  # Initialize a new controller with services.
53
- # @parameter services [Array(GenericService)] The services to manage.
53
+ # @parameter services [Array(Generic)] The services to manage.
54
54
  # @parameter options [Hash] Options passed to the parent controller.
55
55
  def initialize(services, **options)
56
56
  super(**options)
@@ -59,7 +59,7 @@ module Async
59
59
  end
60
60
 
61
61
  # All the services associated with this controller.
62
- # @attribute [Array(Async::Service::GenericService)]
62
+ # @attribute [Array(Async::Service::Generic)]
63
63
  attr :services
64
64
 
65
65
  # Start all named services.
@@ -1,15 +1,65 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2024-2025, by Samuel Williams.
5
-
6
- # Compatibility shim for Async::Service::Generic
7
- # Use Async::Service::GenericService instead
8
- require_relative "generic_service"
4
+ # Copyright, 2025-2026, by Samuel Williams.
9
5
 
10
6
  module Async
11
7
  module Service
12
- # @deprecated Use {GenericService} instead.
13
- Generic = GenericService
8
+ # Captures the stateful behaviour of a specific service.
9
+ # Specifies the interfaces required by derived classes.
10
+ #
11
+ # Designed to be invoked within an {Async::Controller::Container}.
12
+ class Generic
13
+ # Convert the given environment into a service if possible.
14
+ # @parameter environment [Environment] The environment to use to construct the service.
15
+ # @returns [Generic | Nil] The constructed service if the environment specifies a service class.
16
+ def self.wrap(environment)
17
+ evaluator = environment.evaluator
18
+
19
+ if evaluator.key?(:service_class)
20
+ if service_class = evaluator.service_class
21
+ return service_class.new(environment, evaluator)
22
+ end
23
+ end
24
+ end
25
+
26
+ # Initialize the service from the given environment.
27
+ # @parameter environment [Environment]
28
+ def initialize(environment, evaluator = environment.evaluator)
29
+ @environment = environment
30
+ @evaluator = evaluator
31
+ end
32
+
33
+ # @attribute [Environment] The environment which is used to configure the service.
34
+ attr :environment
35
+
36
+ # Convert the service evaluator to a hash.
37
+ # @returns [Hash] A hash representation of the evaluator.
38
+ def to_h
39
+ @evaluator.to_h
40
+ end
41
+
42
+ # The name of the service - used for informational purposes like logging.
43
+ # e.g. `myapp.com`.
44
+ def name
45
+ @evaluator.name
46
+ end
47
+
48
+ # Start the service. Called before the container setup.
49
+ def start
50
+ Console.debug(self){"Starting service #{self.name}..."}
51
+ end
52
+
53
+ # Setup the service into the specified container.
54
+ # @parameter container [Async::Container::Generic]
55
+ def setup(container)
56
+ Console.debug(self){"Setting up service #{self.name}..."}
57
+ end
58
+
59
+ # Stop the service. Called after the container is stopped.
60
+ def stop(graceful = true)
61
+ Console.debug(self){"Stopping service #{self.name}..."}
62
+ end
63
+ end
14
64
  end
15
65
  end
@@ -1,65 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2025-2026, by Samuel Williams.
4
+ # Copyright, 2024-2025, by Samuel Williams.
5
+
6
+ # Compatibility shim for Async::Service::GenericService
7
+ # Use Async::Service::Generic instead
8
+ require_relative "generic"
5
9
 
6
10
  module Async
7
11
  module Service
8
- # Captures the stateful behaviour of a specific service.
9
- # Specifies the interfaces required by derived classes.
10
- #
11
- # Designed to be invoked within an {Async::Controller::Container}.
12
- class GenericService
13
- # Convert the given environment into a service if possible.
14
- # @parameter environment [Environment] The environment to use to construct the service.
15
- # @returns [GenericService | Nil] The constructed service if the environment specifies a service class.
16
- def self.wrap(environment)
17
- evaluator = environment.evaluator
18
-
19
- if evaluator.key?(:service_class)
20
- if service_class = evaluator.service_class
21
- return service_class.new(environment, evaluator)
22
- end
23
- end
24
- end
25
-
26
- # Initialize the service from the given environment.
27
- # @parameter environment [Environment]
28
- def initialize(environment, evaluator = environment.evaluator)
29
- @environment = environment
30
- @evaluator = evaluator
31
- end
32
-
33
- # @attribute [Environment] The environment which is used to configure the service.
34
- attr :environment
35
-
36
- # Convert the service evaluator to a hash.
37
- # @returns [Hash] A hash representation of the evaluator.
38
- def to_h
39
- @evaluator.to_h
40
- end
41
-
42
- # The name of the service - used for informational purposes like logging.
43
- # e.g. `myapp.com`.
44
- def name
45
- @evaluator.name
46
- end
47
-
48
- # Start the service. Called before the container setup.
49
- def start
50
- Console.debug(self){"Starting service #{self.name}..."}
51
- end
52
-
53
- # Setup the service into the specified container.
54
- # @parameter container [Async::Container::Generic]
55
- def setup(container)
56
- Console.debug(self){"Setting up service #{self.name}..."}
57
- end
58
-
59
- # Stop the service. Called after the container is stopped.
60
- def stop(graceful = true)
61
- Console.debug(self){"Stopping service #{self.name}..."}
62
- end
63
- end
12
+ # @deprecated Use {Generic} instead.
13
+ GenericService = Generic
64
14
  end
65
15
  end
@@ -1,48 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2025, by Samuel Williams.
4
+ # Copyright, 2024-2025, by Samuel Williams.
5
+
6
+ # Compatibility shim for Async::Service::HealthChecker
7
+ # Use Async::Service::Managed::HealthChecker instead
8
+ require_relative "managed/health_checker"
5
9
 
6
10
  module Async
7
11
  module Service
8
- # A health checker for managed services.
9
- module HealthChecker
10
- # Start the health checker.
11
- #
12
- # If a timeout is specified, a transient child task will be scheduled, which will yield the instance if a block is given, then mark the instance as ready, and finally sleep for half the health check duration (so that we guarantee that the health check runs in time).
13
- #
14
- # If a timeout is not specified, the health checker will yield the instance immediately and then mark the instance as ready.
15
- #
16
- # @parameter instance [Object] The service instance to check.
17
- # @parameter timeout [Numeric] The timeout duration for the health check.
18
- # @parameter parent [Async::Task] The parent task to run the health checker in.
19
- # @yields {|instance| ...} If a block is given, it will be called with the service instance at least once.
20
- def health_checker(instance, timeout = @evaluator.health_check_timeout, parent: Async::Task.current, &block)
21
- if timeout
22
- parent.async(transient: true) do
23
- while true
24
- if block_given?
25
- yield(instance)
26
- end
27
-
28
- # We deliberately create a fiber here, to confirm that fiber creation is working.
29
- # If something has gone wrong with fiber allocation, we will crash here, and that's okay.
30
- Fiber.new do
31
- instance.healthy!
32
- end.resume
33
-
34
- sleep(timeout / 2.0)
35
- end
36
- end
37
- else
38
- if block_given?
39
- yield(instance)
40
- end
41
-
42
- instance.healthy!
43
- end
44
- end
45
- end
12
+ # @deprecated Use {Managed::HealthChecker} instead.
13
+ HealthChecker = Managed::HealthChecker
46
14
  end
47
15
  end
48
16
 
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2025-2026, by Samuel Williams.
5
+
6
+ module Async
7
+ module Service
8
+ module Managed
9
+ # Default configuration for managed services.
10
+ #
11
+ # This is provided not because it is required, but to offer a sensible default for production services, and to expose a consistent interface for service configuration.
12
+ module Environment
13
+ # Number of instances to start. By default, when `nil`, uses `Etc.nprocessors`.
14
+ #
15
+ # @returns [Integer | nil] The number of instances to start, or `nil` to use the default.
16
+ def count
17
+ nil
18
+ end
19
+
20
+ # The timeout duration for the startup. Set to `nil` to disable the startup timeout.
21
+ #
22
+ # @returns [Numeric | nil] The startup timeout in seconds.
23
+ def startup_timeout
24
+ nil
25
+ end
26
+
27
+ # The timeout duration for the health check. Set to `nil` to disable the health check.
28
+ #
29
+ # @returns [Numeric | nil] The health check timeout in seconds.
30
+ def health_check_timeout
31
+ 30
32
+ end
33
+
34
+ # Options to use when creating the container, including `restart`, `count`, and `health_check_timeout`.
35
+ #
36
+ # @returns [Hash] The options for the container.
37
+ def container_options
38
+ {
39
+ restart: true,
40
+ count: self.count,
41
+ startup_timeout: self.startup_timeout,
42
+ health_check_timeout: self.health_check_timeout,
43
+ }.compact
44
+ end
45
+
46
+ # Any scripts to preload before starting the service.
47
+ #
48
+ # @returns [Array(String)] The list of scripts to preload.
49
+ def preload
50
+ []
51
+ end
52
+
53
+ # General tags for metrics, traces and logging.
54
+ #
55
+ # @returns [Array(String)] The tags for the service.
56
+ def tags
57
+ []
58
+ end
59
+
60
+ # Prepare the instance for running the service.
61
+ #
62
+ # This is called before {Async::Service::Managed::Service#run}.
63
+ #
64
+ # @parameter instance [Object] The container instance.
65
+ def prepare!(instance)
66
+ # No preparation required by default.
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2025-2026, by Samuel Williams.
5
+
6
+ module Async
7
+ module Service
8
+ module Managed
9
+ # A health checker for managed services.
10
+ module HealthChecker
11
+ # Start the health checker.
12
+ #
13
+ # If a timeout is specified, a transient child task will be scheduled, which will yield the instance if a block is given, then mark the instance as ready, and finally sleep for half the health check duration (so that we guarantee that the health check runs in time).
14
+ #
15
+ # If a timeout is not specified, the health checker will yield the instance immediately and then mark the instance as ready.
16
+ #
17
+ # @parameter instance [Object] The service instance to check.
18
+ # @parameter timeout [Numeric] The timeout duration for the health check.
19
+ # @parameter parent [Async::Task] The parent task to run the health checker in.
20
+ # @yields {|instance| ...} If a block is given, it will be called with the service instance at least once.
21
+ def health_checker(instance, timeout = @evaluator.health_check_timeout, parent: Async::Task.current, &block)
22
+ if timeout
23
+ parent.async(transient: true) do
24
+ while true
25
+ if block_given?
26
+ yield(instance)
27
+ end
28
+
29
+ # We deliberately create a fiber here, to confirm that fiber creation is working.
30
+ # If something has gone wrong with fiber allocation, we will crash here, and that's okay.
31
+ Fiber.new do
32
+ instance.healthy!
33
+ end.resume
34
+
35
+ sleep(timeout / 2.0)
36
+ end
37
+ end
38
+ else
39
+ if block_given?
40
+ yield(instance)
41
+ end
42
+
43
+ instance.healthy!
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2025-2026, by Samuel Williams.
5
+
6
+ require_relative "../generic"
7
+ require_relative "health_checker"
8
+
9
+ module Async
10
+ module Service
11
+ # @namespace
12
+ module Managed
13
+ # A managed service with built-in health checking, restart policies, and process title formatting.
14
+ #
15
+ # This is the recommended base class for most services that need robust lifecycle management.
16
+ class Service < Generic
17
+ include HealthChecker
18
+
19
+ private def format_title(evaluator, server)
20
+ "#{evaluator.name} #{server.to_s}"
21
+ end
22
+
23
+ # Run the service logic.
24
+ #
25
+ # Override this method to implement your service. Return an object that represents the running service (e.g., a server, task, or worker pool) for health checking.
26
+ #
27
+ # @parameter instance [Object] The container instance.
28
+ # @parameter evaluator [Environment::Evaluator] The environment evaluator.
29
+ # @returns [Object] The service object (server, task, etc.)
30
+ def run(instance, evaluator)
31
+ Async do
32
+ sleep
33
+ end
34
+ end
35
+
36
+ # Preload any resources specified by the environment.
37
+ def preload!
38
+ if scripts = @evaluator.preload
39
+ root = @evaluator.root
40
+ scripts = Array(scripts)
41
+
42
+ scripts.each do |path|
43
+ Console.info(self){"Preloading #{path}..."}
44
+ full_path = File.expand_path(path, root)
45
+ require(full_path)
46
+ end
47
+ end
48
+ rescue StandardError, LoadError => error
49
+ Console.warn(self, "Service preload failed!", error)
50
+ end
51
+
52
+ # Start the service, including preloading resources.
53
+ def start
54
+ preload!
55
+
56
+ super
57
+ end
58
+
59
+ # Called after the service has been prepared but before it starts running.
60
+ #
61
+ # Override this method to emit metrics, logs, or perform other actions when the service preparation is complete.
62
+ #
63
+ # @parameter instance [Async::Container::Instance] The container instance.
64
+ # @parameter clock [Async::Clock] The monotonic start time from {Async::Clock.start}.
65
+ def emit_prepared(instance, clock)
66
+ # Override in subclasses as needed.
67
+ # Console.info(self, "Prepared...", duration: clock.total)
68
+ end
69
+
70
+ # Called after the service has started running.
71
+ #
72
+ # Override this method to emit metrics, logs, or perform other actions when the service begins running.
73
+ #
74
+ # @parameter instance [Async::Container::Instance] The container instance.
75
+ # @parameter clock [Async::Clock] The monotonic start time from {Async::Clock.start}.
76
+ def emit_running(instance, clock)
77
+ # Override in subclasses as needed.
78
+ # Console.info(self, "Running...", duration: clock.total)
79
+ end
80
+
81
+ # Set up the container with health checking and process title formatting.
82
+ # @parameter container [Async::Container] The container to configure.
83
+ def setup(container)
84
+ super
85
+
86
+ container_options = @evaluator.container_options
87
+ health_check_timeout = container_options[:health_check_timeout]
88
+
89
+ container.run(**container_options) do |instance|
90
+ clock = Async::Clock.start
91
+
92
+ Async do
93
+ evaluator = self.environment.evaluator
94
+ server = nil
95
+
96
+ health_checker(instance, health_check_timeout) do
97
+ if server
98
+ instance.name = format_title(evaluator, server)
99
+ end
100
+ end
101
+
102
+ instance.status!("Preparing...")
103
+ evaluator.prepare!(instance)
104
+ emit_prepared(instance, clock)
105
+
106
+ instance.status!("Running...")
107
+ server = run(instance, evaluator)
108
+ emit_running(instance, clock)
109
+
110
+ instance.ready!
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -1,70 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2025-2026, by Samuel Williams.
4
+ # Copyright, 2024-2025, by Samuel Williams.
5
+
6
+ # Compatibility shim for Async::Service::ManagedEnvironment
7
+ # Use Async::Service::Managed::Environment instead
8
+ require_relative "managed/environment"
5
9
 
6
10
  module Async
7
11
  module Service
8
- # Default configuration for managed services.
9
- #
10
- # This is provided not because it is required, but to offer a sensible default for production services, and to expose a consistent interface for service configuration.
11
- module ManagedEnvironment
12
- # Number of instances to start. By default, when `nil`, uses `Etc.nprocessors`.
13
- #
14
- # @returns [Integer | nil] The number of instances to start, or `nil` to use the default.
15
- def count
16
- nil
17
- end
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
-
26
- # The timeout duration for the health check. Set to `nil` to disable the health check.
27
- #
28
- # @returns [Numeric | nil] The health check timeout in seconds.
29
- def health_check_timeout
30
- 30
31
- end
32
-
33
- # Options to use when creating the container, including `restart`, `count`, and `health_check_timeout`.
34
- #
35
- # @returns [Hash] The options for the container.
36
- def container_options
37
- {
38
- restart: true,
39
- count: self.count,
40
- startup_timeout: self.startup_timeout,
41
- health_check_timeout: self.health_check_timeout,
42
- }.compact
43
- end
44
-
45
- # Any scripts to preload before starting the service.
46
- #
47
- # @returns [Array(String)] The list of scripts to preload.
48
- def preload
49
- []
50
- end
51
-
52
- # General tags for metrics, traces and logging.
53
- #
54
- # @returns [Array(String)] The tags for the service.
55
- def tags
56
- []
57
- end
58
-
59
- # Prepare the instance for running the service.
60
- #
61
- # This is called before {Async::Service::ManagedService#run}.
62
- #
63
- # @parameter instance [Object] The container instance.
64
- def prepare!(instance)
65
- # No preparation required by default.
66
- end
67
- end
12
+ # @deprecated Use {Managed::Environment} instead.
13
+ ManagedEnvironment = Managed::Environment
68
14
  end
69
15
  end
70
16
 
@@ -1,115 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2025-2026, by Samuel Williams.
4
+ # Copyright, 2024-2025, by Samuel Williams.
5
5
 
6
- require_relative "generic_service"
7
- require_relative "health_checker"
6
+ # Compatibility shim for Async::Service::ManagedService
7
+ # Use Async::Service::Managed::Service instead
8
+ require_relative "managed/service"
8
9
 
9
10
  module Async
10
11
  module Service
11
- # A managed service with built-in health checking, restart policies, and process title formatting.
12
- #
13
- # This is the recommended base class for most services that need robust lifecycle management.
14
- class ManagedService < GenericService
15
- include HealthChecker
16
-
17
- private def format_title(evaluator, server)
18
- "#{evaluator.name} #{server.to_s}"
19
- end
20
-
21
- # Run the service logic.
22
- #
23
- # Override this method to implement your service. Return an object that represents the running service (e.g., a server, task, or worker pool) for health checking.
24
- #
25
- # @parameter instance [Object] The container instance.
26
- # @parameter evaluator [Environment::Evaluator] The environment evaluator.
27
- # @returns [Object] The service object (server, task, etc.)
28
- def run(instance, evaluator)
29
- Async do
30
- sleep
31
- end
32
- end
33
-
34
- # Preload any resources specified by the environment.
35
- def preload!
36
- if scripts = @evaluator.preload
37
- root = @evaluator.root
38
- scripts = Array(scripts)
39
-
40
- scripts.each do |path|
41
- Console.info(self){"Preloading #{path}..."}
42
- full_path = File.expand_path(path, root)
43
- require(full_path)
44
- end
45
- end
46
- rescue StandardError, LoadError => error
47
- Console.warn(self, "Service preload failed!", error)
48
- end
49
-
50
- # Start the service, including preloading resources.
51
- def start
52
- preload!
53
-
54
- super
55
- end
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)
66
- end
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)
77
- end
78
-
79
- # Set up the container with health checking and process title formatting.
80
- # @parameter container [Async::Container] The container to configure.
81
- def setup(container)
82
- super
83
-
84
- container_options = @evaluator.container_options
85
- health_check_timeout = container_options[:health_check_timeout]
86
-
87
- container.run(**container_options) do |instance|
88
- clock = Async::Clock.start
89
-
90
- Async do
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
99
-
100
- instance.status!("Preparing...")
101
- evaluator.prepare!(instance)
102
- emit_prepared(instance, clock)
103
-
104
- instance.status!("Running...")
105
- server = run(instance, evaluator)
106
- emit_running(instance, clock)
107
-
108
- instance.ready!
109
- end
110
- end
111
- end
112
- end
12
+ # @deprecated Use {Managed::Service} instead.
13
+ ManagedService = Managed::Service
113
14
  end
114
15
  end
115
16
 
@@ -3,8 +3,10 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2024-2025, by Samuel Williams.
5
5
 
6
+ # @namespace
6
7
  module Async
8
+ # @namespace
7
9
  module Service
8
- VERSION = "0.18.1"
10
+ VERSION = "0.19.0"
9
11
  end
10
12
  end
data/lib/async/service.rb CHANGED
@@ -6,10 +6,3 @@
6
6
  require_relative "service/configuration"
7
7
  require_relative "service/controller"
8
8
  require_relative "service/version"
9
-
10
- # @namespace
11
- module Async
12
- # @namespace
13
- module Service
14
- end
15
- end
data/readme.md CHANGED
@@ -29,6 +29,13 @@ 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.19.0
33
+
34
+ - Renamed `Async::Service::GenericService` -\> `Async::Service::Generic`, added compatibility alias for `GenericService`.
35
+ - Renamed `Async::Service::ManagedService` -\> `Async::Service::Managed::Service`, added compatibility alias for `ManagedService`.
36
+ - Renamed `Async::Service::ManagedEnvironment` -\> `Async::Service::Managed::Environment`, added compatibility alias for `ManagedEnvironment`.
37
+ - Renamed `Async::Service::HealthChecker` -\> `Async::Service::Managed::HealthChecker`, added compatibility alias for `HealthChecker`.
38
+
32
39
  ### v0.18.1
33
40
 
34
41
  - Remove prepared and running log messages - not as useful as I imagined, and quite noisy.
@@ -74,10 +81,6 @@ Please see the [project releases](https://socketry.github.io/async-service/relea
74
81
  - Make service name optional and improve code comments.
75
82
  - Add `respond_to_missing?` for completeness.
76
83
 
77
- ### v0.12.0
78
-
79
- - Add convenient `Configuration.build{...}` method for constructing inline configurations.
80
-
81
84
  ## Contributing
82
85
 
83
86
  We welcome contributions to this project.
data/releases.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Releases
2
2
 
3
+ ## v0.19.0
4
+
5
+ - Renamed `Async::Service::GenericService` -\> `Async::Service::Generic`, added compatibility alias for `GenericService`.
6
+ - Renamed `Async::Service::ManagedService` -\> `Async::Service::Managed::Service`, added compatibility alias for `ManagedService`.
7
+ - Renamed `Async::Service::ManagedEnvironment` -\> `Async::Service::Managed::Environment`, added compatibility alias for `ManagedEnvironment`.
8
+ - Renamed `Async::Service::HealthChecker` -\> `Async::Service::Managed::HealthChecker`, added compatibility alias for `HealthChecker`.
9
+
3
10
  ## v0.18.1
4
11
 
5
12
  - Remove prepared and running log messages - not as useful as I imagined, and quite noisy.
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.18.1
4
+ version: 0.19.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -100,6 +100,9 @@ files:
100
100
  - lib/async/service/generic_service.rb
101
101
  - lib/async/service/health_checker.rb
102
102
  - lib/async/service/loader.rb
103
+ - lib/async/service/managed/environment.rb
104
+ - lib/async/service/managed/health_checker.rb
105
+ - lib/async/service/managed/service.rb
103
106
  - lib/async/service/managed_environment.rb
104
107
  - lib/async/service/managed_service.rb
105
108
  - lib/async/service/version.rb
@@ -126,7 +129,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
126
129
  - !ruby/object:Gem::Version
127
130
  version: '0'
128
131
  requirements: []
129
- rubygems_version: 3.6.9
132
+ rubygems_version: 4.0.3
130
133
  specification_version: 4
131
134
  summary: A service layer for Async.
132
135
  test_files: []
metadata.gz.sig CHANGED
Binary file