@@ -7,7 +7,43 @@ const axios = require("axios");
77const unzipper = require ( "unzipper" ) ;
88const yaml = require ( "js-yaml" ) ;
99
10+ // Handle signals to ensure clean exit
11+ process . on ( 'SIGINT' , ( ) => {
12+ console . log ( 'Action wrapper received SIGINT, exiting...' ) ;
13+ process . exit ( 0 ) ;
14+ } ) ;
15+
16+ process . on ( 'SIGTERM' , ( ) => {
17+ console . log ( 'Action wrapper received SIGTERM, exiting...' ) ;
18+ process . exit ( 0 ) ;
19+ } ) ;
20+
21+ // Set an absolute maximum timeout for the entire action (30 minutes)
22+ const MAX_ACTION_DURATION_MS = 30 * 60 * 1000 ;
23+ const actionTimeoutId = setTimeout ( ( ) => {
24+ core . warning ( `Action timed out after ${ MAX_ACTION_DURATION_MS / 60000 } minutes. This is likely a bug in the action wrapper. Forcing exit.` ) ;
25+ process . exit ( 1 ) ;
26+ } , MAX_ACTION_DURATION_MS ) ;
27+
28+ // Make sure the timeout doesn't prevent the process from exiting naturally
29+ actionTimeoutId . unref ( ) ;
30+
1031async function run ( ) {
32+ // Track any child processes created
33+ const childProcesses = [ ] ;
34+
35+ // Register for process exits to ensure we clean up
36+ process . on ( 'beforeExit' , ( ) => {
37+ core . debug ( `Cleaning up any remaining child processes: ${ childProcesses . length } ` ) ;
38+ childProcesses . forEach ( pid => {
39+ try {
40+ process . kill ( pid , 'SIGTERM' ) ;
41+ } catch ( e ) {
42+ // Ignore errors when killing processes that might be gone
43+ }
44+ } ) ;
45+ } ) ;
46+
1147 try {
1248 // Get inputs
1349 const actionRef = core . getInput ( "action-ref" ) ;
@@ -196,11 +232,32 @@ async function processAction(actionDir, extraArgs) {
196232 core . info ( `Strace output will be saved to: ${ stracelLogFile } ` ) ;
197233 }
198234
199- // Use strace to wrap the node process
200- await exec . exec ( "strace" , [ ... straceOptionsList , "node" , entryFile , ... args ] , {
235+ // Use strace to wrap the node process with explicit exit handling
236+ const options = {
201237 cwd : actionDir ,
202- env : envVars
203- } ) ;
238+ env : envVars ,
239+ listeners : {
240+ stdout : ( data ) => {
241+ const output = data . toString ( ) ;
242+ // Just pass through stdout from the child process
243+ process . stdout . write ( output ) ;
244+ } ,
245+ stderr : ( data ) => {
246+ const output = data . toString ( ) ;
247+ // Just pass through stderr from the child process
248+ process . stderr . write ( output ) ;
249+ } ,
250+ debug : ( message ) => {
251+ core . debug ( message ) ;
252+ }
253+ } ,
254+ // This will provide access to the child process object
255+ ignoreReturnCode : false
256+ } ;
257+
258+ // Use the exec implementation that gives us access to the child process
259+ const cp = await exec . getExecOutput ( "strace" , [ ...straceOptionsList , "node" , entryFile , ...args ] , options ) ;
260+ core . debug ( `Strace process completed with exit code: ${ cp . exitCode } ` ) ;
204261
205262 // Export the strace log path as an output
206263 core . setOutput ( "strace-log" , stracelLogFile ) ;
@@ -209,18 +266,56 @@ async function processAction(actionDir, extraArgs) {
209266 // If strace is not available, fall back to running without it
210267 core . warning ( `Strace is not available: ${ error . message } ` ) ;
211268 core . info ( `Executing nested action without strace: node ${ entryFile } ${ args . join ( " " ) } ` ) ;
212- await exec . exec ( "node" , [ entryFile , ...args ] , {
269+
270+ const options = {
213271 cwd : actionDir ,
214- env : envVars
215- } ) ;
272+ env : envVars ,
273+ listeners : {
274+ stdout : ( data ) => {
275+ const output = data . toString ( ) ;
276+ process . stdout . write ( output ) ;
277+ } ,
278+ stderr : ( data ) => {
279+ const output = data . toString ( ) ;
280+ process . stderr . write ( output ) ;
281+ } ,
282+ debug : ( message ) => {
283+ core . debug ( message ) ;
284+ }
285+ } ,
286+ ignoreReturnCode : false
287+ } ;
288+
289+ // Use getExecOutput to get access to the child process
290+ const cp = await exec . getExecOutput ( "node" , [ entryFile , ...args ] , options ) ;
291+ core . debug ( `Node process completed with exit code: ${ cp . exitCode } ` ) ;
216292 }
217293 } else {
218294 // Run without strace
219295 core . info ( `Strace disabled. Executing nested action: node ${ entryFile } ${ args . join ( " " ) } ` ) ;
220- await exec . exec ( "node" , [ entryFile , ...args ] , {
296+
297+ const options = {
221298 cwd : actionDir ,
222- env : envVars
223- } ) ;
299+ env : envVars ,
300+ listeners : {
301+ stdout : ( data ) => {
302+ const output = data . toString ( ) ;
303+ process . stdout . write ( output ) ;
304+ } ,
305+ stderr : ( data ) => {
306+ const output = data . toString ( ) ;
307+ process . stderr . write ( output ) ;
308+ } ,
309+ debug : ( message ) => {
310+ core . debug ( message ) ;
311+ }
312+ } ,
313+ ignoreReturnCode : false
314+ } ;
315+
316+ // Use getExecOutput to get access to the child process
317+ const cp = await exec . getExecOutput ( "node" , [ entryFile , ...args ] , options ) ;
318+ core . debug ( `Node process completed with exit code: ${ cp . exitCode } ` ) ;
224319 }
225320}
226321
@@ -232,4 +327,20 @@ function parseActionRef(refString) {
232327 return parts ;
233328}
234329
235- run ( ) ;
330+ run ( )
331+ . then ( ( ) => {
332+ core . debug ( 'Action wrapper completed successfully' ) ;
333+ // Force exit to ensure we don't hang
334+ setTimeout ( ( ) => {
335+ core . debug ( 'Forcing process exit to prevent hanging' ) ;
336+ process . exit ( 0 ) ;
337+ } , 500 ) ;
338+ } )
339+ . catch ( error => {
340+ core . setFailed ( `Action wrapper failed: ${ error . message } ` ) ;
341+ // Force exit to ensure we don't hang
342+ setTimeout ( ( ) => {
343+ core . debug ( 'Forcing process exit to prevent hanging' ) ;
344+ process . exit ( 1 ) ;
345+ } , 500 ) ;
346+ } ) ;
0 commit comments