Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
a26c9ba
Rebuild container child lifecycle
samuel-williams-shopify Jul 3, 2026
5108143
Restore group running compatibility
samuel-williams-shopify Jul 5, 2026
e557e7c
Document child lifecycle APIs
samuel-williams-shopify Jul 5, 2026
5be1ef0
Fix child lifecycle timeout handling
samuel-williams-shopify Jul 5, 2026
fc599af
Stabilize uncooperative child fixture
samuel-williams-shopify Jul 5, 2026
3f6aeec
Use scoped pipes in child fixtures
samuel-williams-shopify Jul 5, 2026
845a32e
Preserve startup timeout deadline
samuel-williams-shopify Jul 5, 2026
6590487
Run child lifecycle management as async tasks
samuel-williams-shopify Jul 14, 2026
2d66ee1
Run hybrid child containers inside a scheduler
samuel-williams-shopify Jul 14, 2026
7c7e530
Wake threaded children after control exceptions
samuel-williams-shopify Jul 14, 2026
7a51d86
Keep controller cleanup inside Sync
samuel-williams-shopify Jul 14, 2026
30fe997
Outdent controller run interrupt handling
samuel-williams-shopify Jul 14, 2026
7906e15
Document container scheduler requirement
samuel-williams-shopify Jul 14, 2026
76218db
Stop child when lifecycle task exits
samuel-williams-shopify Jul 14, 2026
3d313a4
Support synchronous container lifecycle spawning
samuel-williams-shopify Jul 14, 2026
217d873
Cover controller startup with signal handlers
samuel-williams-shopify Jul 14, 2026
5035b86
Cover internal lifecycle thread
samuel-williams-shopify Jul 14, 2026
9c5ae72
Cover generic health check timeout
samuel-williams-shopify Jul 14, 2026
f953124
Cover threaded child status serialization
samuel-williams-shopify Jul 14, 2026
a5f4b02
Relax coverage threshold
samuel-williams-shopify Jul 14, 2026
88aca37
Stabilize lifecycle cancellation assertion
samuel-williams-shopify Jul 14, 2026
414655b
Wait for lifecycle child exit
samuel-williams-shopify Jul 14, 2026
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 .github/workflows/test-coverage.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,4 @@ jobs:

- name: Validate coverage
timeout-minutes: 5
run: bundle exec bake covered:validate --paths */.covered.db \;
run: bundle exec bake covered:validate --minimum 0.9 --paths */.covered.db \;
161 changes: 161 additions & 0 deletions fixtures/async/container/a_child.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
# frozen_string_literal: true

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

module Async
module Container
AChild = Sus::Shared("a child") do
def wait_for_child_started(input)
input.gets
end

def start_ready_child
subject.call(name: "test-child") do |instance|
instance.ready!(status: "ready")
end
end

def start_sleeping_child
::IO.pipe do |input, output|
child = subject.call(name: "test-child") do
output.puts "ready"
sleep
rescue Interrupt
# Graceful shutdown.
end

wait_for_child_started(input)

return child
end
end

def start_uncooperative_child
::IO.pipe do |input, output|
child = subject.call(name: "test-child") do
ready_sent = false

loop do
begin
unless ready_sent
output.puts "ready"
ready_sent = true
end

sleep
rescue Interrupt
# Ignore graceful shutdown.
end
end
end

wait_for_child_started(input)

return child
end
end

it "receives notifications and returns a successful status" do
child = start_ready_child

messages = []
status = child.wait do |message|
messages << message
end

expect(messages).to be == [{ready: true, status: "ready"}]
expect(status).to be(:success?)
end

it "tracks when the child last sent a message" do
child = start_ready_child
last_updated_at = child.last_updated_at

messages = []
child.wait do |message|
messages << message
end

expect(messages).to be == [{ready: true, status: "ready"}]
expect(child.last_updated_at).to be >= last_updated_at
end

it "can receive messages until the child is ready" do
child = subject.call(name: "test-child") do |instance|
instance.ready!(status: "ready")
sleep
rescue Interrupt
# Graceful shutdown.
end

result = child.receive do
break :ready if child.ready?
end

expect(result).to be == :ready
expect(child).to be(:ready?)

status = child.stop(true)

expect(status).to be(:success?)
end

it "returns a successful status for normal exit" do
child = subject.call(name: "test-child") do
# Exit normally.
end

status = child.wait

expect(status).to be(:success?)
end

it "returns nil when wait times out" do
child = start_sleeping_child
last_updated_at = child.last_updated_at

expect(child.wait(0.001)).to be_nil
expect(child.last_updated_at).to be == last_updated_at

status = child.stop(false)

expect(status).not.to be(:success?)
end

it "can stop gracefully" do
child = start_sleeping_child

status = child.stop(true)

expect(status).to be(:success?)
end

it "can kill immediately" do
child = start_sleeping_child

status = child.stop(false)

expect(status).not.to be(:success?)
end

it "kills the child when graceful shutdown times out" do
child = start_uncooperative_child

status = child.stop(0.001)

expect(status).not.to be(:success?)
end

it "returns a failure status when the child fails" do
child = subject.call(name: "test-child") do
raise "boom"
end

status = child.wait

expect(status).not.to be(:success?)
end
end
end
end
Loading
Loading