@@ -29,6 +29,32 @@ def init(self, spec, no_connection=False):
2929serve.PluginCommand(BlockedInitPlugin("test", "v0.0.1")).run(["serve", "--address", sys.argv[1]])
3030"""
3131
32+ SERVE_PLUGIN_WITH_INIT_FLOODING_STDOUT = """
33+ import sys
34+ from cloudquery.sdk import serve
35+ from cloudquery.sdk.plugin import Plugin
36+
37+ class FloodingInitPlugin(Plugin):
38+ def init(self, spec, no_connection=False):
39+ while True:
40+ sys.stdout.write("x" * 1024)
41+
42+ serve.PluginCommand(FloodingInitPlugin("test", "v0.0.1")).run(["serve", "--address", sys.argv[1]])
43+ """
44+
45+ SERVE_PLUGIN_WITH_INIT_BLOCKED_ON_BUFFERED_STDOUT = """
46+ import sys, threading
47+ from cloudquery.sdk import serve
48+ from cloudquery.sdk.plugin import Plugin
49+
50+ class BufferedInitPlugin(Plugin):
51+ def init(self, spec, no_connection=False):
52+ sys.stdout.write("buffered and never flushed")
53+ threading.Event().wait()
54+
55+ serve.PluginCommand(BufferedInitPlugin("test", "v0.0.1")).run(["serve", "--address", sys.argv[1]])
56+ """
57+
3258SIGTERM_DURING_LOCK_RESTORE = """
3359import os, signal, sys, threading
3460from cloudquery.sdk import serve
@@ -94,9 +120,9 @@ def free_address():
94120 return "127.0.0.1:%d" % sock .getsockname ()[1 ]
95121
96122
97- def start_plugin_with_blocked_init (tmp_path ):
123+ def start_plugin_with_blocked_init (tmp_path , source = SERVE_PLUGIN_WITH_BLOCKED_INIT ):
98124 address = free_address ()
99- process = start_plugin (tmp_path , SERVE_PLUGIN_WITH_BLOCKED_INIT , address )
125+ process = start_plugin (tmp_path , source , address )
100126 wait_until_serving (process )
101127 channel = grpc .insecure_channel (address )
102128 init_call = plugin_pb2_grpc .PluginStub (channel ).Init .future (
@@ -130,3 +156,41 @@ def test_second_signal_with_blocked_handler_exits_zero_immediately(tmp_path):
130156 init_call .cancel ()
131157 channel .close ()
132158 process .kill ()
159+
160+
161+ def send_termination_signals (process , second_signal ):
162+ process .send_signal (signal .SIGTERM )
163+ if second_signal :
164+ time .sleep (0.5 )
165+ process .send_signal (signal .SIGINT )
166+ return 3
167+ return 20
168+
169+
170+ @pytest .mark .parametrize ("second_signal" , [False , True ])
171+ def test_forced_exit_with_full_stdout_pipe_exits_zero (tmp_path , second_signal ):
172+ process , channel , init_call = start_plugin_with_blocked_init (
173+ tmp_path , SERVE_PLUGIN_WITH_INIT_FLOODING_STDOUT
174+ )
175+ try :
176+ timeout = send_termination_signals (process , second_signal )
177+ assert process .wait (timeout = timeout ) == 0
178+ finally :
179+ init_call .cancel ()
180+ channel .close ()
181+ process .kill ()
182+
183+
184+ @pytest .mark .parametrize ("second_signal" , [False , True ])
185+ def test_forced_exit_with_closed_stdout_pipe_exits_zero (tmp_path , second_signal ):
186+ process , channel , init_call = start_plugin_with_blocked_init (
187+ tmp_path , SERVE_PLUGIN_WITH_INIT_BLOCKED_ON_BUFFERED_STDOUT
188+ )
189+ try :
190+ process .stdout .close ()
191+ timeout = send_termination_signals (process , second_signal )
192+ assert process .wait (timeout = timeout ) == 0
193+ finally :
194+ init_call .cancel ()
195+ channel .close ()
196+ process .kill ()
0 commit comments