Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/hello/hello.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def setup(container)
sleep 1
end
end
end
end
end

service "sleep" do
Expand Down
9 changes: 9 additions & 0 deletions fixtures/async/service/good_interface.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module Async
module Service
module GoodInterface
def good_method
:good
end
end
end
end
36 changes: 36 additions & 0 deletions fixtures/async/service/sleep_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
require 'async/service/generic'

module Async
module Service
class SleepService < Async::Service::Generic
def setup(container)
super

container.run(count: 1, restart: true) do |instance|
# Use log level:
Console.logger.level = @environment.evaluator.log_level

if container.statistics.failed?
Console.debug(self, "Child process restarted #{container.statistics.restarts} times.")
else
Console.debug(self, "Child process started.")
end

instance.ready!

while true
sleep 1

Console.debug(self, "Work")

if rand < 0.1
Console.debug(self, "Should exit...")
sleep 0.5
exit(1)
end
end
end
end
end
end
end
8 changes: 5 additions & 3 deletions lib/async/service/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,11 @@ def services(implementing: nil)
return to_enum(:services, implementing: implementing) unless block_given?

@environments.each do |environment|
next if implementing and environment.implements?(implementing)

yield Generic.wrap(environment)
if implementing.nil? or environment.implements?(implementing)
if service = Generic.wrap(environment)
yield service
end
end
end
end

Expand Down
4 changes: 4 additions & 0 deletions lib/async/service/environment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ def method_missing(name, argument = nil, &block)
@facet.define_method(name){argument}
end
end

def respond_to_missing?(name, include_private = false)
true
end
end

def self.build(...)
Expand Down
7 changes: 5 additions & 2 deletions lib/async/service/generic.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ module Service
class Generic
# Convert the given environment into a service if possible.
# @parameter environment [Build::Environment] The environment to use to construct the service.
# @returns [Generic | Nil] The constructed service if the environment specifies a service class.
def self.wrap(environment)
evaluator = environment.evaluator

if service_class = evaluator.service_class || self
return service_class.new(environment, evaluator)
if evaluator.key?(:service_class)
if service_class = evaluator.service_class
return service_class.new(environment, evaluator)
end
end
end

Expand Down
11 changes: 7 additions & 4 deletions lib/async/service/loader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,14 @@ def environment(**initial, &block)
Environment.build(**initial, &block)
end

# Define a host with the specified name.
# Adds `root` and `authority` keys.
# Define a service with the specified name.
# Adds `root` and `name` keys.
# @parameter name [String] The name of the environment, usually a hostname.
def service(name, &block)
@configuration.add(self.environment(name: name, root: @root, &block))
def service(name = nil, **options, &block)
options[:name] = name
options[:root] ||= @root

@configuration.add(self.environment(**options, &block))
end
end
end
Expand Down
19 changes: 19 additions & 0 deletions test/async/service/.configurations/implementing.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/usr/bin/env async-service
# frozen_string_literal: trueb

# Released under the MIT License.
# Copyright, 2025, by Samuel Williams.

require "async/service/good_interface"
require "async/service/sleep_service"

# A service without a service class, e.g. a no-op.
service "good-service" do
include Async::Service::GoodInterface

service_class Async::Service::SleepService
end

service "not-so-good-service" do
service_class Async::Service::SleepService
end
9 changes: 9 additions & 0 deletions test/async/service/.configurations/null.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/usr/bin/env async-service
# frozen_string_literal: trueb

# Released under the MIT License.
# Copyright, 2025, by Samuel Williams.

# A service without a service class, e.g. a no-op.
service "null" do
end
34 changes: 2 additions & 32 deletions test/async/service/.configurations/sleep.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,45 +8,15 @@
log_level :info
end

# A test service:
class SleepService < Async::Service::Generic
def setup(container)
super

container.run(count: 1, restart: true) do |instance|
# Use log level:
Console.logger.level = @environment.evaluator.log_level

if container.statistics.failed?
Console.debug(self, "Child process restarted #{container.statistics.restarts} times.")
else
Console.debug(self, "Child process started.")
end

instance.ready!

while true
sleep 1

Console.debug(self, "Work")

if rand < 0.1
Console.debug(self, "Should exit...")
sleep 0.5
exit(1)
end
end
end
end
end
require "async/service/sleep_service"

service "sleep" do
include LogLevel

authority {self.name}
middleware {Object.new}

service_class SleepService
service_class Async::Service::SleepService
end

# A 2nd service:
Expand Down
37 changes: 37 additions & 0 deletions test/async/service/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# Copyright, 2024, by Samuel Williams.

require 'async/service/configuration'
require "async/service/good_interface"

describe Async::Service::Configuration do
with '.build' do
Expand All @@ -29,6 +30,42 @@
end
end

with "null serice configuration file" do
let(:configuration_path) {File.join(__dir__, '.configurations', 'null.rb')}

let(:configuration) do
subject.new.tap do |configuration|
configuration.load_file(configuration_path)
end
end

it 'can load configuration' do
expect(configuration).not.to be(:empty?)

environment = configuration.environments.first
evaluator = environment.evaluator
expect(evaluator.name).to be == 'null'

expect(configuration.services.to_a).to be(:empty?)
end
end

with "implementing service configuration file" do
let(:configuration_path) {File.join(__dir__, '.configurations', 'implementing.rb')}

let(:configuration) do
subject.new.tap do |configuration|
configuration.load_file(configuration_path)
end
end

it "can select services by implementing module" do
expect(configuration).not.to be(:empty?)

expect(configuration.services(implementing: Async::Service::GoodInterface).to_a).not.to be(:empty?)
end
end

with 'sleep service configuration file' do
let(:configuration_path) {File.join(__dir__, '.configurations', 'sleep.rb')}
let(:configuration_root) {File.join(File.realpath(__dir__), '.configurations')}
Expand Down
Loading