async-service 0.15.1 → 0.17.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: ce29cb5debee3f9a0b9dbdf80c0087fe52973e00c14c4d114a044fe8656d0920
4
- data.tar.gz: eec4b6a5117ea33293e942a68fc656707b59a434d89df4e604e5ab95a11c9ad3
3
+ metadata.gz: 6e9258424ee834d70b309984ba6f2d0c89f87ed9b2a16ec38fb298dfafb33fcd
4
+ data.tar.gz: e91b2129702c7c8ad7f3203063d3dd526b8e6e91d322bb84388afe873cf638f0
5
5
  SHA512:
6
- metadata.gz: a95d7e8bc23385c1d874368e0582156e574ed2b9fa9defbd4c2ffff75499e457c48421f0610a94bf8de1f8326245d55f77751a59b0507d31278ecc89c6af0dc2
7
- data.tar.gz: f79918dd27119524826711b5fa872bc37014c93fd460683b13358c14a98dfb4d96ed34dd08c19d4ac1fead8c5ad5acf045a2e006752bcdc05cc8276531b97bec
6
+ metadata.gz: edc78ce3ff189ef951f19b206d02bbef4f2c121b10e24f074b6a7eab702c6ffeceb5e47c7c29d330363c7389c721b371c4584f33b9145cde42b798ec08e47237
7
+ data.tar.gz: be8a666be488d9d428339edea966834c0a7d5e935bcf330b8669f914b58661ed11deff0782b4202327951b108063992e5ac6f4fb9427beca5d6c1daaf09f440a
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::Managed::Environment
64
+ include Async::Service::ManagedEnvironment
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::Managed::Service
90
+ class WebService < Async::Service::ManagedService
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,11 +114,11 @@ end
114
114
 
115
115
  ### Use `Managed::Environment` for Services
116
116
 
117
- Include {ruby Async::Service::Managed::Environment} for services that need robust lifecycle management using {ruby Async::Service::Managed::Service}:
117
+ Include {ruby Async::Service::ManagedEnvironment} for services that need robust lifecycle management using {ruby Async::Service::ManagedService}:
118
118
 
119
119
  ```ruby
120
120
  module WebEnvironment
121
- include Async::Service::Managed::Environment
121
+ include Async::Service::ManagedEnvironment
122
122
 
123
123
  def service_class
124
124
  WebService
@@ -201,13 +201,14 @@ end
201
201
 
202
202
  ### Use `Managed::Service` as Base Class
203
203
 
204
- Prefer `Async::Service::Managed::Service` over `Generic` for most services:
204
+ Prefer `Async::Service::ManagedService` over `GenericService` for most services:
205
205
 
206
206
  ```ruby
207
- class WebService < Async::Service::Managed::Service
207
+ 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::Managed::Service
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:
@@ -247,7 +270,7 @@ Try to keep process titles short and focused.
247
270
  Utilize the `start` and `stop` hooks to manage shared resources effectively:
248
271
 
249
272
  ```ruby
250
- class WebService < Async::Service::Managed::Service
273
+ class WebService < Async::Service::ManagedService
251
274
  def start
252
275
  # Bind to the endpoint in the container:
253
276
  @endpoint = @evaluator.endpoint.bind
@@ -0,0 +1,284 @@
1
+ # Deployment
2
+
3
+ This guide explains how to deploy `async-service` applications using systemd and Kubernetes. We'll use a simple example service to demonstrate deployment configurations.
4
+
5
+ ## Example Service
6
+
7
+ Let's start with a simple HTTP service that we'll deploy:
8
+
9
+ ```ruby
10
+ #!/usr/bin/env async-service
11
+ # frozen_string_literal: true
12
+
13
+ require "async/http"
14
+ require "async/service/managed_service"
15
+ require "async/service/managed_environment"
16
+
17
+ class WebService < Async::Service::ManagedService
18
+ def start
19
+ super
20
+ @endpoint = @evaluator.endpoint
21
+ @bound_endpoint = Sync{@endpoint.bound}
22
+ end
23
+
24
+ def stop
25
+ @endpoint = nil
26
+ @bound_endpoint&.close
27
+ super
28
+ end
29
+
30
+ def run(instance, evaluator)
31
+ Console.info(self){"Starting web server on #{@endpoint}"}
32
+
33
+ server = Async::HTTP::Server.for(@bound_endpoint, protocol: @endpoint.protocol, scheme: @endpoint.scheme) do |request|
34
+ Protocol::HTTP::Response[200, {}, ["Hello, World!"]]
35
+ end
36
+
37
+ instance.ready!
38
+ server.run
39
+ end
40
+ end
41
+
42
+ module WebEnvironment
43
+ include Async::Service::ManagedEnvironment
44
+
45
+ def endpoint
46
+ Async::HTTP::Endpoint.parse("http://0.0.0.0:3000")
47
+ end
48
+ end
49
+
50
+ service "web" do
51
+ service_class WebService
52
+ include WebEnvironment
53
+ end
54
+ ```
55
+
56
+ Save this as `web_service.rb` and make it executable:
57
+
58
+ ```bash
59
+ $ chmod +x web_service.rb
60
+ ```
61
+
62
+ ## Systemd Deployment
63
+
64
+ Systemd can manage your `async-service` application as a system service, providing automatic startup, restart on failure, and integration with system logging.
65
+
66
+ ### Service File
67
+
68
+ Create a systemd service file at `/etc/systemd/system/my-web-service.service`:
69
+
70
+ ```
71
+ [Unit]
72
+ Description=My Web Service
73
+ After=network.target
74
+
75
+ [Service]
76
+ Type=notify
77
+ ExecStart=/usr/local/bin/bundle exec /path/to/web_service.rb
78
+ WorkingDirectory=/path/to/application
79
+ User=www-data
80
+ Group=www-data
81
+ Restart=always
82
+ RestartSec=5
83
+ StandardOutput=journal
84
+ StandardError=journal
85
+
86
+ [Install]
87
+ WantedBy=multi-user.target
88
+ ```
89
+
90
+ ### Key Configuration Points
91
+
92
+ - **Type=notify**: This is essential for `async-service` to notify systemd when the service is ready. The service uses the `sd_notify` protocol via the `$NOTIFY_SOCKET` environment variable.
93
+ - **ExecStart**: Points to your service script. Use `bundle exec` if you're using Bundler.
94
+ - **WorkingDirectory**: Set to your application root directory.
95
+ - **User/Group**: Run the service as a non-privileged user.
96
+ - **Restart=always**: Automatically restart the service if it fails.
97
+
98
+ ### Installing and Managing the Service
99
+
100
+ ```bash
101
+ # Reload systemd to recognize the new service
102
+ $ sudo systemctl daemon-reload
103
+
104
+ # Enable the service to start on boot
105
+ $ sudo systemctl enable my-web-service
106
+
107
+ # Start the service
108
+ $ sudo systemctl start my-web-service
109
+
110
+ # Check service status
111
+ $ sudo systemctl status my-web-service
112
+
113
+ # View service logs
114
+ $ sudo journalctl -u my-web-service -f
115
+
116
+ # Stop the service
117
+ $ sudo systemctl stop my-web-service
118
+ ```
119
+
120
+ ### Verifying Readiness
121
+
122
+ The service will notify systemd when it's ready. You can verify this by checking the service status:
123
+
124
+ ```bash
125
+ $ sudo systemctl status my-web-service
126
+ ```
127
+
128
+ The service should show as "active (running)" once it has notified systemd of its readiness.
129
+
130
+ ## Kubernetes Deployment
131
+
132
+ Kubernetes can manage your `async-service` application as a containerized workload, providing scaling, health checks, and rolling updates.
133
+
134
+ ### Dockerfile
135
+
136
+ First, create a `Dockerfile` for your application:
137
+
138
+ ```dockerfile
139
+ FROM ruby:3.2
140
+
141
+ WORKDIR /app
142
+
143
+ # Install dependencies
144
+ COPY Gemfile Gemfile.lock ./
145
+ RUN bundle install --deployment --without development test
146
+
147
+ # Copy application files
148
+ COPY . .
149
+
150
+ # Expose the service port
151
+ EXPOSE 3000
152
+
153
+ # Set the notification log path
154
+ ENV NOTIFY_LOG=/tmp/notify.log
155
+
156
+ # Run the service
157
+ CMD ["bundle", "exec", "./web_service.rb"]
158
+ ```
159
+
160
+ ### Deployment Configuration
161
+
162
+ Create a Kubernetes deployment file `web-service-deployment.yaml`:
163
+
164
+ ```yaml
165
+ apiVersion: apps/v1
166
+ kind: Deployment
167
+ metadata:
168
+ name: web-service
169
+ spec:
170
+ replicas: 2
171
+ selector:
172
+ matchLabels:
173
+ app: web-service
174
+ template:
175
+ metadata:
176
+ labels:
177
+ app: web-service
178
+ spec:
179
+ containers:
180
+ - name: web-service
181
+ image: my-registry/web-service:latest
182
+ ports:
183
+ - containerPort: 3000
184
+ name: http
185
+ env:
186
+ - name: NOTIFY_LOG
187
+ value: "/tmp/notify.log"
188
+ readinessProbe:
189
+ exec:
190
+ command:
191
+ - bundle
192
+ - exec
193
+ - bake
194
+ - async:container:notify:log:ready?
195
+ initialDelaySeconds: 5
196
+ periodSeconds: 5
197
+ timeoutSeconds: 3
198
+ failureThreshold: 12
199
+ livenessProbe:
200
+ httpGet:
201
+ path: /
202
+ port: 3000
203
+ initialDelaySeconds: 30
204
+ periodSeconds: 10
205
+ timeoutSeconds: 5
206
+ failureThreshold: 3
207
+ resources:
208
+ requests:
209
+ memory: "128Mi"
210
+ cpu: "100m"
211
+ limits:
212
+ memory: "256Mi"
213
+ cpu: "500m"
214
+ ---
215
+ apiVersion: v1
216
+ kind: Service
217
+ metadata:
218
+ name: web-service
219
+ spec:
220
+ selector:
221
+ app: web-service
222
+ ports:
223
+ - protocol: TCP
224
+ port: 80
225
+ targetPort: 3000
226
+ type: LoadBalancer
227
+ ```
228
+
229
+ ### Key Configuration Points
230
+
231
+ - **readinessProbe**: Uses the `async:container:notify:log:ready?` bake task to check if the service is ready. This reads from the `NOTIFY_LOG` file.
232
+ - **livenessProbe**: HTTP health check to ensure the service is responding to requests.
233
+ - **NOTIFY_LOG**: Environment variable pointing to the notification log file path.
234
+ - **replicas**: Number of pod instances to run.
235
+
236
+ ### Deploying to Kubernetes
237
+
238
+ ```bash
239
+ # Build and push the Docker image
240
+ $ docker build -t my-registry/web-service:latest .
241
+ $ docker push my-registry/web-service:latest
242
+
243
+ # Apply the deployment
244
+ $ kubectl apply -f web-service-deployment.yaml
245
+
246
+ # Check deployment status
247
+ $ kubectl get deployments
248
+ $ kubectl get pods
249
+
250
+ # View pod logs
251
+ $ kubectl logs -f deployment/web-service
252
+
253
+ # Check service endpoints
254
+ $ kubectl get svc web-service
255
+
256
+ # Scale the deployment
257
+ $ kubectl scale deployment web-service --replicas=3
258
+
259
+ # Update the deployment (rolling update)
260
+ $ kubectl set image deployment/web-service web-service=my-registry/web-service:v2
261
+ ```
262
+
263
+ ### Verifying Readiness
264
+
265
+ Kubernetes will wait for the readiness probe to pass before routing traffic to the pod:
266
+
267
+ ```bash
268
+ # Check pod readiness
269
+ $ kubectl get pods -l app=web-service
270
+
271
+ # Describe pod to see readiness probe status
272
+ $ kubectl describe pod <pod-name>
273
+ ```
274
+
275
+ The pod will show as "Ready" once the readiness probe succeeds, indicating the service has notified that it's ready to accept traffic.
276
+
277
+ ## Notification Mechanism
278
+
279
+ Both systemd and Kubernetes deployments rely on the notification mechanism provided by `async-container`. The service uses `instance.ready!` to signal readiness:
280
+
281
+ - **Systemd**: Uses the `sd_notify` protocol via the `$NOTIFY_SOCKET` environment variable (automatically handled by `async-container`).
282
+ - **Kubernetes**: Uses a log file (`NOTIFY_LOG`) that the readiness probe checks using the `async:container:notify:log:ready?` bake task.
283
+
284
+ This ensures that your service is only considered ready when it has actually started and is prepared to handle requests, preventing premature traffic routing and improving reliability.
@@ -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::Generic} which represents the base class for implementing services.
17
+ - A {ruby Async::Service::GenericService} 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::Generic` 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::GenericService` 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::Generic
34
+ class HelloService < Async::Service::GenericService
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::Generic
76
+ class WebServerService < Async::Service::GenericService
77
77
  def setup(container)
78
78
  super
79
79
 
@@ -92,6 +92,8 @@ class WebServerService < Async::Service::Generic
92
92
  end
93
93
  ```
94
94
 
95
+ The evaluator is a memoized instance of the service's configuration, allowing for efficient access to configuration values throughout the service's lifecycle. If a service worker is restarted, it will create a new evaluator and a fresh environment.
96
+
95
97
  ### Multiple Services
96
98
 
97
99
  You can define multiple services in a single configuration file:
@@ -101,7 +103,7 @@ You can define multiple services in a single configuration file:
101
103
 
102
104
  require "async/service"
103
105
 
104
- class WebService < Async::Service::Generic
106
+ class WebService < Async::Service::GenericService
105
107
  def setup(container)
106
108
  super
107
109
 
@@ -113,7 +115,7 @@ class WebService < Async::Service::Generic
113
115
  end
114
116
  end
115
117
 
116
- class WorkerService < Async::Service::Generic
118
+ class WorkerService < Async::Service::GenericService
117
119
  def setup(container)
118
120
  super
119
121
 
@@ -151,40 +153,3 @@ end
151
153
 
152
154
  Async::Service::Controller.run(configuration)
153
155
  ```
154
-
155
- ### Accessing Configuration Values
156
-
157
- Services have access to their configuration through the environment and evaluator:
158
-
159
- ```ruby
160
- class ConfigurableService < Async::Service::Generic
161
- def setup(container)
162
- super
163
-
164
- container.run(count: 1, restart: true) do |instance|
165
- # Clone the evaluator for thread safety
166
- evaluator = self.environment.evaluator
167
- database_url = evaluator.database_url
168
- max_connections = evaluator.max_connections
169
- debug_mode = evaluator.debug_mode
170
-
171
- puts "Database URL: #{database_url}"
172
- puts "Max connections: #{max_connections}"
173
- puts "Debug mode: #{debug_mode}"
174
-
175
- instance.ready!
176
-
177
- # Your service implementation using these values
178
- end
179
- end
180
- end
181
-
182
- service "configurable" do
183
- service_class ConfigurableService
184
- database_url "postgresql://localhost/myapp"
185
- max_connections 10
186
- debug_mode true
187
- end
188
- ```
189
-
190
- The evaluator is a memoized instance of the service's configuration, allowing for efficient access to configuration values throughout the service's lifecycle.
data/context/index.yaml CHANGED
@@ -18,3 +18,8 @@ files:
18
18
  title: Best Practices
19
19
  description: This guide outlines recommended patterns and practices for building
20
20
  robust, maintainable services with `async-service`.
21
+ - path: deployment.md
22
+ title: Deployment
23
+ description: This guide explains how to deploy `async-service` applications using
24
+ systemd and Kubernetes. We'll use a simple example service to demonstrate deployment
25
+ configurations.
@@ -82,14 +82,14 @@ end
82
82
 
83
83
  ## Service
84
84
 
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.
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.
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::Generic
92
+ class WebService < Async::Service::GenericService
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::Generic
151
+ class MyService < Async::Service::GenericService
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,7 +262,9 @@ end
262
262
 
263
263
  ### Health Checking
264
264
 
265
- For services using `Async::Service::Managed::Service`, health checking is handled automatically. For services extending `Generic`, 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,7 +289,31 @@ def setup(container)
284
289
  end
285
290
  ```
286
291
 
287
- 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.
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
+
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
290
319
 
@@ -305,7 +334,7 @@ end
305
334
 
306
335
  ```ruby
307
336
  # Services are defined using environments:
308
- class WebService < Async::Service::Generic
337
+ class WebService < Async::Service::GenericService
309
338
  def setup(container)
310
339
  super
311
340
 
@@ -440,7 +469,7 @@ Create reusable configuration modules:
440
469
 
441
470
  ```ruby
442
471
  module ManagedEnvironment
443
- include Async::Service::Managed::Environment
472
+ include Async::Service::ManagedEnvironment
444
473
 
445
474
  def count
446
475
  4
@@ -464,7 +493,7 @@ end
464
493
  Build service hierarchies:
465
494
 
466
495
  ```ruby
467
- class BaseWebService < Async::Service::Generic
496
+ class BaseWebService < Async::Service::GenericService
468
497
  def setup(container)
469
498
  super
470
499
  # Common web service setup
@@ -4,7 +4,7 @@
4
4
  # Copyright, 2024-2025, by Samuel Williams.
5
5
 
6
6
  require_relative "loader"
7
- require_relative "generic"
7
+ require_relative "generic_service"
8
8
  require_relative "controller"
9
9
 
10
10
  module Async
@@ -70,7 +70,7 @@ module Async
70
70
 
71
71
  @environments.each do |environment|
72
72
  if implementing.nil? or environment.implements?(implementing)
73
- if service = Generic.wrap(environment)
73
+ if service = GenericService.wrap(environment)
74
74
  yield service
75
75
  end
76
76
  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(Generic)] The services to control.
45
+ # @parameter services [Array(GenericService)] 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(Generic)] The services to manage.
53
+ # @parameter services [Array(GenericService)] 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::Generic)]
62
+ # @attribute [Array(Async::Service::GenericService)]
63
63
  attr :services
64
64
 
65
65
  # Start all named services.