Skip to content

Commit a26c9ba

Browse files
Rebuild container child lifecycle
Assisted-By: devx/a01026a8-dd63-4b91-bae4-aec7ee8f7fa3
1 parent 2e9234b commit a26c9ba

23 files changed

Lines changed: 1562 additions & 1913 deletions

File tree

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
# frozen_string_literal: true
2+
3+
# Released under the MIT License.
4+
# Copyright, 2026, by Samuel Williams.
5+
6+
module Async
7+
module Container
8+
AChild = Sus::Shared("a child") do
9+
def wait_until_ready(input)
10+
input.gets
11+
end
12+
13+
def start_ready_child
14+
subject.call(name: "test-child") do |instance|
15+
instance.ready!(status: "ready")
16+
end
17+
end
18+
19+
def start_sleeping_child
20+
ready = ::IO.pipe
21+
22+
child = subject.call(name: "test-child") do
23+
ready.last.puts "ready"
24+
sleep
25+
rescue Interrupt
26+
# Graceful shutdown.
27+
end
28+
29+
wait_until_ready(ready.first)
30+
31+
return child
32+
end
33+
34+
def start_uncooperative_child
35+
ready = ::IO.pipe
36+
37+
child = subject.call(name: "test-child") do
38+
ready.last.puts "ready"
39+
40+
loop do
41+
begin
42+
sleep
43+
rescue Interrupt
44+
# Ignore graceful shutdown.
45+
end
46+
end
47+
end
48+
49+
wait_until_ready(ready.first)
50+
51+
return child
52+
end
53+
54+
it "receives notifications and returns a successful status" do
55+
child = start_ready_child
56+
57+
messages = []
58+
status = child.wait do |message|
59+
messages << message
60+
end
61+
62+
expect(messages).to be == [{ready: true, status: "ready"}]
63+
expect(status).to be(:success?)
64+
end
65+
66+
it "tracks when the child last sent a message" do
67+
child = start_ready_child
68+
last_updated_at = child.last_updated_at
69+
70+
messages = []
71+
child.wait do |message|
72+
messages << message
73+
end
74+
75+
expect(messages).to be == [{ready: true, status: "ready"}]
76+
expect(child.last_updated_at).to be >= last_updated_at
77+
end
78+
79+
it "can receive messages until the child is ready" do
80+
child = subject.call(name: "test-child") do |instance|
81+
instance.ready!(status: "ready")
82+
sleep
83+
rescue Interrupt
84+
# Graceful shutdown.
85+
end
86+
87+
result = child.receive do
88+
break :ready if child.ready?
89+
end
90+
91+
expect(result).to be == :ready
92+
expect(child).to be(:ready?)
93+
94+
status = child.stop(true)
95+
96+
expect(status).to be(:success?)
97+
end
98+
99+
it "returns a successful status for normal exit" do
100+
child = subject.call(name: "test-child") do
101+
# Exit normally.
102+
end
103+
104+
status = child.wait
105+
106+
expect(status).to be(:success?)
107+
end
108+
109+
it "returns nil when wait times out" do
110+
child = start_sleeping_child
111+
last_updated_at = child.last_updated_at
112+
113+
expect(child.wait(0.001)).to be_nil
114+
expect(child.last_updated_at).to be == last_updated_at
115+
116+
status = child.stop(false)
117+
118+
expect(status).not.to be(:success?)
119+
end
120+
121+
it "can stop gracefully" do
122+
child = start_sleeping_child
123+
124+
status = child.stop(true)
125+
126+
expect(status).to be(:success?)
127+
end
128+
129+
it "can kill immediately" do
130+
child = start_sleeping_child
131+
132+
status = child.stop(false)
133+
134+
expect(status).not.to be(:success?)
135+
end
136+
137+
it "kills the child when graceful shutdown times out" do
138+
child = start_uncooperative_child
139+
140+
status = child.stop(0.001)
141+
142+
expect(status).not.to be(:success?)
143+
end
144+
145+
it "returns a failure status when the child fails" do
146+
child = subject.call(name: "test-child") do
147+
raise "boom"
148+
end
149+
150+
status = child.wait
151+
152+
expect(status).not.to be(:success?)
153+
end
154+
end
155+
end
156+
end

0 commit comments

Comments
 (0)