3333:author: Toon Willems (Ghent University)
3434:author: Ward Poelmans (Ghent University)
3535"""
36+ import contextlib
3637import functools
3738import os
3839import re
@@ -315,21 +316,24 @@ def complete_cmd(proc, cmd, owd, start_time, cmd_log, log_ok=True, log_all=False
315316
316317 stdouterr = output
317318
318- ec = proc .poll ()
319- while ec is None :
320- # need to read from time to time.
321- # - otherwise the stdout/stderr buffer gets filled and it all stops working
322- output = get_output_from_process (proc , read_size = read_size )
323- if cmd_log :
324- cmd_log .write (output )
325- if stream_output :
326- sys .stdout .write (output )
327- stdouterr += output
319+ try :
328320 ec = proc .poll ()
321+ while ec is None :
322+ # need to read from time to time.
323+ # - otherwise the stdout/stderr buffer gets filled and it all stops working
324+ output = get_output_from_process (proc , read_size = read_size )
325+ if cmd_log :
326+ cmd_log .write (output )
327+ if stream_output :
328+ sys .stdout .write (output )
329+ stdouterr += output
330+ ec = proc .poll ()
331+
332+ # read remaining data (all of it)
333+ output = get_output_from_process (proc )
334+ finally :
335+ proc .stdout .close ()
329336
330- # read remaining data (all of it)
331- output = get_output_from_process (proc )
332- proc .stdout .close ()
333337 if cmd_log :
334338 cmd_log .write (output )
335339 cmd_log .close ()
@@ -397,6 +401,8 @@ def run_cmd_qa(cmd, qa, no_qa=None, log_ok=True, log_all=False, simple=False, re
397401 path = cwd
398402 dry_run_msg (" running interactive command \" %s\" " % cmd , silent = build_option ('silent' ))
399403 dry_run_msg (" (in %s)" % path , silent = build_option ('silent' ))
404+ if cmd_log :
405+ cmd_log .close ()
400406 if simple :
401407 return True
402408 else :
@@ -442,6 +448,8 @@ def check_answers_list(answers):
442448 if isinstance (answers , string_type ):
443449 answers = [answers ]
444450 elif not isinstance (answers , list ):
451+ if cmd_log :
452+ cmd_log .close ()
445453 raise EasyBuildError ("Invalid type for answer on %s, no string or list: %s (%s)" ,
446454 question , type (answers ), answers )
447455 # list is manipulated when answering matching question, so return a copy
@@ -479,47 +487,48 @@ def check_answers_list(answers):
479487 if cmd_log :
480488 cmd_log .write ("# output for interactive command: %s\n \n " % cmd )
481489
482- try :
483- proc = asyncprocess .Popen (cmd , shell = True , stdout = asyncprocess .PIPE , stderr = asyncprocess .STDOUT ,
484- stdin = asyncprocess .PIPE , close_fds = True , executable = '/bin/bash' )
485- except OSError as err :
486- raise EasyBuildError ("run_cmd_qa init cmd %s failed:%s" , cmd , err )
487-
488- ec = proc .poll ()
489- stdout_err = ''
490- old_len_out = - 1
491- hit_count = 0
492-
493- while ec is None :
494- # need to read from time to time.
495- # - otherwise the stdout/stderr buffer gets filled and it all stops working
490+ # Make sure we close the proc handles and the cmd_log file
491+ @contextlib .contextmanager
492+ def get_proc ():
496493 try :
497- out = get_output_from_process (proc , asynchronous = True )
498-
494+ proc = asyncprocess .Popen (cmd , shell = True , stdout = asyncprocess .PIPE , stderr = asyncprocess .STDOUT ,
495+ stdin = asyncprocess .PIPE , close_fds = True , executable = '/bin/bash' )
496+ except OSError as err :
499497 if cmd_log :
500- cmd_log .write (out )
501- stdout_err += out
502- # recv_some used by get_output_from_process for getting asynchronous output may throw exception
503- except (IOError , Exception ) as err :
504- _log .debug ("run_cmd_qa cmd %s: read failed: %s" , cmd , err )
505- out = None
506-
507- hit = False
508- for question , answers in new_qa .items ():
509- res = question .search (stdout_err )
510- if out and res :
511- fa = answers [0 ] % res .groupdict ()
512- # cycle through list of answers
513- last_answer = answers .pop (0 )
514- answers .append (last_answer )
515- _log .debug ("List of answers for question %s after cycling: %s" , question .pattern , answers )
516-
517- _log .debug ("run_cmd_qa answer %s question %s out %s" , fa , question .pattern , stdout_err [- 50 :])
518- asyncprocess .send_all (proc , fa )
519- hit = True
520- break
521- if not hit :
522- for question , answers in new_std_qa .items ():
498+ cmd_log .close ()
499+ raise EasyBuildError ("run_cmd_qa init cmd %s failed:%s" , cmd , err )
500+ try :
501+ yield proc
502+ finally :
503+ if proc .stdout :
504+ proc .stdout .close ()
505+ if proc .stdin :
506+ proc .stdin .close ()
507+ if cmd_log :
508+ cmd_log .close ()
509+
510+ with get_proc () as proc :
511+ ec = proc .poll ()
512+ stdout_err = ''
513+ old_len_out = - 1
514+ hit_count = 0
515+
516+ while ec is None :
517+ # need to read from time to time.
518+ # - otherwise the stdout/stderr buffer gets filled and it all stops working
519+ try :
520+ out = get_output_from_process (proc , asynchronous = True )
521+
522+ if cmd_log :
523+ cmd_log .write (out )
524+ stdout_err += out
525+ # recv_some used by get_output_from_process for getting asynchronous output may throw exception
526+ except (IOError , Exception ) as err :
527+ _log .debug ("run_cmd_qa cmd %s: read failed: %s" , cmd , err )
528+ out = None
529+
530+ hit = False
531+ for question , answers in new_qa .items ():
523532 res = question .search (stdout_err )
524533 if out and res :
525534 fa = answers [0 ] % res .groupdict ()
@@ -528,51 +537,65 @@ def check_answers_list(answers):
528537 answers .append (last_answer )
529538 _log .debug ("List of answers for question %s after cycling: %s" , question .pattern , answers )
530539
531- _log .debug ("run_cmd_qa answer %s std question %s out %s" , fa , question .pattern , stdout_err [- 50 :])
540+ _log .debug ("run_cmd_qa answer %s question %s out %s" , fa , question .pattern , stdout_err [- 50 :])
532541 asyncprocess .send_all (proc , fa )
533542 hit = True
534543 break
535544 if not hit :
536- if len (stdout_err ) > old_len_out :
537- old_len_out = len (stdout_err )
545+ for question , answers in new_std_qa .items ():
546+ res = question .search (stdout_err )
547+ if out and res :
548+ fa = answers [0 ] % res .groupdict ()
549+ # cycle through list of answers
550+ last_answer = answers .pop (0 )
551+ answers .append (last_answer )
552+ _log .debug ("List of answers for question %s after cycling: %s" , question .pattern , answers )
553+
554+ _log .debug ("run_cmd_qa answer %s std question %s out %s" ,
555+ fa , question .pattern , stdout_err [- 50 :])
556+ asyncprocess .send_all (proc , fa )
557+ hit = True
558+ break
559+ if not hit :
560+ if len (stdout_err ) > old_len_out :
561+ old_len_out = len (stdout_err )
562+ else :
563+ noqa = False
564+ for r in new_no_qa :
565+ if r .search (stdout_err ):
566+ _log .debug ("runqanda: noQandA found for out %s" , stdout_err [- 50 :])
567+ noqa = True
568+ if not noqa :
569+ hit_count += 1
538570 else :
539- noqa = False
540- for r in new_no_qa :
541- if r .search (stdout_err ):
542- _log .debug ("runqanda: noQandA found for out %s" , stdout_err [- 50 :])
543- noqa = True
544- if not noqa :
545- hit_count += 1
571+ hit_count = 0
546572 else :
547573 hit_count = 0
548- else :
549- hit_count = 0
550574
551- if hit_count > maxhits :
552- # explicitly kill the child process before exiting
553- try :
554- os .killpg (proc .pid , signal .SIGKILL )
555- os .kill (proc .pid , signal .SIGKILL )
556- except OSError as err :
557- _log .debug ("run_cmd_qa exception caught when killing child process: %s" , err )
558- _log .debug ("run_cmd_qa: full stdouterr: %s" , stdout_err )
559- raise EasyBuildError ("run_cmd_qa: cmd %s : Max nohits %s reached: end of output %s" ,
560- cmd , maxhits , stdout_err [- 500 :])
561-
562- # the sleep below is required to avoid exiting on unknown 'questions' too early (see above)
563- time .sleep (1 )
564- ec = proc .poll ()
565-
566- # Process stopped. Read all remaining data
567- try :
568- if proc .stdout :
569- out = get_output_from_process (proc )
570- stdout_err += out
571- if cmd_log :
572- cmd_log .write (out )
573- cmd_log .close ()
574- except IOError as err :
575- _log .debug ("runqanda cmd %s: remaining data read failed: %s" , cmd , err )
575+ if hit_count > maxhits :
576+ # explicitly kill the child process before exiting
577+ try :
578+ os .killpg (proc .pid , signal .SIGKILL )
579+ os .kill (proc .pid , signal .SIGKILL )
580+ except OSError as err :
581+ _log .debug ("run_cmd_qa exception caught when killing child process: %s" , err )
582+ _log .debug ("run_cmd_qa: full stdouterr: %s" , stdout_err )
583+ raise EasyBuildError ("run_cmd_qa: cmd %s : Max nohits %s reached: end of output %s" ,
584+ cmd , maxhits , stdout_err [- 500 :])
585+
586+ # the sleep below is required to avoid exiting on unknown 'questions' too early (see above)
587+ time .sleep (1 )
588+ ec = proc .poll ()
589+
590+ # Process stopped. Read all remaining data
591+ try :
592+ if proc .stdout :
593+ out = get_output_from_process (proc )
594+ stdout_err += out
595+ if cmd_log :
596+ cmd_log .write (out )
597+ except IOError as err :
598+ _log .debug ("runqanda cmd %s: remaining data read failed: %s" , cmd , err )
576599
577600 if trace :
578601 trace_msg ("interactive command completed: exit %s, ran in %s" % (ec , time_str_since (start_time )))
0 commit comments