6
6
@author: Morten
7
7
"""
8
8
9
+ import atexit
10
+ import collections
11
+ import copy
12
+ import ctypes
13
+ import logging
9
14
import os
10
- import io
15
+ import pathlib
16
+ import shelve
11
17
import sys
12
18
import time
13
- import copy
14
19
import types
15
- import ctypes
16
- import shelve
17
- import atexit
18
- import pathlib
19
- import logging
20
20
import warnings
21
- import collections
22
- from pathlib import Path
23
- from subprocess import Popen , TimeoutExpired
24
21
from contextlib import suppress
25
- from tempfile import NamedTemporaryFile
26
- from threading import Thread , RLock
22
+ from pathlib import Path
27
23
from queue import Queue
28
-
24
+ from subprocess import TimeoutExpired
25
+ from tempfile import NamedTemporaryFile
26
+ from threading import RLock , Thread
29
27
from typing import Generator , List
30
28
31
29
import numpy as np
32
30
from tqdm .auto import tqdm
33
31
32
+ from .macroutils import AnyMacro , MacroCommand
34
33
from .tools import (
34
+ BELOW_NORMAL_PRIORITY_CLASS ,
35
35
ON_WINDOWS ,
36
- make_hash ,
36
+ AnyPyProcessOutput ,
37
37
AnyPyProcessOutputList ,
38
- parse_anybodycon_output ,
39
- getsubdirs ,
38
+ case_preserving_replace ,
40
39
get_anybodycon_path ,
41
- BELOW_NORMAL_PRIORITY_CLASS ,
42
- AnyPyProcessOutput ,
43
- run_from_ipython ,
44
40
get_ncpu ,
45
- winepath ,
41
+ getsubdirs ,
42
+ make_hash ,
43
+ parse_anybodycon_output ,
46
44
silentremove ,
47
- case_preserving_replace ,
45
+ winepath ,
48
46
)
49
- from .macroutils import AnyMacro , MacroCommand
47
+
48
+ if ON_WINDOWS :
49
+ from .jobpopen import JobPopen as Popen
50
+ from subprocess import CREATE_NEW_PROCESS_GROUP
51
+ else :
52
+ from subprocess import Popen
50
53
51
54
logger = logging .getLogger ("abt.anypytools" )
52
55
60
63
class _SubProcessContainer (object ):
61
64
"""Class to hold a record of process pids from Popen.
62
65
63
- Properties
64
- ----------
65
- stop_all: boolean
66
- If set to True all process held by the object will be automatically
67
- killed
68
-
69
66
Methods
70
67
-------
68
+ stop_all():
69
+ Kill all process held by the object
71
70
add(pid):
72
71
Add process id to the record of process
73
-
74
72
remove(pid):
75
73
Remove process id from the record
76
74
77
75
"""
78
76
79
77
def __init__ (self ):
80
- self ._pids = set ()
81
- self ._stop_all = False
78
+ self ._pids : set = set ()
82
79
83
80
def add (self , pid ):
84
81
with _thread_lock :
85
82
self ._pids .add (pid )
86
- if self .stop_all :
87
- self ._kill_running_processes ()
88
83
89
84
def remove (self , pid ):
90
85
with _thread_lock :
91
- try :
92
- self ._pids .remove (pid )
93
- except KeyError :
94
- pass
86
+ self ._pids .pop (pid , None )
95
87
96
- @property
97
88
def stop_all (self ):
98
- return self ._stop_all
99
-
100
- @stop_all .setter
101
- def stop_all (self , value ):
102
- if value :
103
- self ._stop_all = True
104
- self ._kill_running_processes ()
105
- else :
106
- self ._stop_all = False
107
-
108
- def _kill_running_processes (self ):
109
89
"""Clean up and shut down any running processes."""
110
90
# Kill any rouge processes that are still running.
111
91
with _thread_lock :
112
- killed = []
113
92
for pid in self ._pids :
114
93
with suppress (Exception ):
115
94
os .kill (pid , _KILLED_BY_ANYPYTOOLS )
116
- killed .append (str (pid ))
117
95
self ._pids .clear ()
118
96
119
97
120
98
_subprocess_container = _SubProcessContainer ()
121
- atexit .register (_subprocess_container ._kill_running_processes )
99
+ atexit .register (_subprocess_container .stop_all )
122
100
123
101
124
102
def execute_anybodycon (
@@ -212,6 +190,7 @@ def execute_anybodycon(
212
190
ctypes .windll .kernel32 .SetErrorMode (SEM_NOGPFAULTERRORBOX )
213
191
subprocess_flags = 0x8000000 # win32con.CREATE_NO_WINDOW?
214
192
subprocess_flags |= priority
193
+ subprocess_flags |= CREATE_NEW_PROCESS_GROUP
215
194
extra_kwargs = {"creationflags" : subprocess_flags }
216
195
217
196
anybodycmd = [
@@ -275,6 +254,7 @@ def execute_anybodycon(
275
254
cwd = folder ,
276
255
)
277
256
257
+ retcode = None
278
258
_subprocess_container .add (proc .pid )
279
259
try :
280
260
proc .wait (timeout = timeout )
@@ -283,13 +263,16 @@ def execute_anybodycon(
283
263
proc .kill ()
284
264
proc .communicate ()
285
265
retcode = _TIMEDOUT_BY_ANYPYTOOLS
286
- except KeyboardInterrupt :
266
+ except KeyboardInterrupt as e :
287
267
proc .terminate ()
288
268
proc .communicate ()
289
269
retcode = _KILLED_BY_ANYPYTOOLS
290
- raise
270
+ raise e
291
271
finally :
292
- _subprocess_container .remove (proc .pid )
272
+ if not retcode :
273
+ proc .kill ()
274
+ else :
275
+ _subprocess_container .remove (proc .pid )
293
276
294
277
if retcode == _TIMEDOUT_BY_ANYPYTOOLS :
295
278
logfile .write (f"\n ERROR: AnyPyTools : Timeout after { int (timeout )} sec." )
@@ -844,20 +827,17 @@ def start_macro(
844
827
if hasattr (pbar , "container" ):
845
828
pbar .container .children [0 ].bar_style = "danger"
846
829
pbar .update ()
847
- except KeyboardInterrupt as e :
848
- _subprocess_container .stop_all = True
830
+ except KeyboardInterrupt :
849
831
tqdm .write ("KeyboardInterrupt: User aborted" )
850
- time .sleep (1 )
851
832
finally :
833
+ _subprocess_container .stop_all ()
852
834
if not self .silent :
853
835
tqdm .write (tasklist_summery (tasklist ))
854
836
855
837
self .cleanup_logfiles (tasklist )
856
838
# Cache the processed tasklist for restarting later
857
839
self .cached_tasklist = tasklist
858
- # self.summery.final_summery(process_time, tasklist)
859
- task_output = [task .get_output () for task in tasklist ]
860
- return AnyPyProcessOutputList (task_output )
840
+ return AnyPyProcessOutputList (t .get_output () for t in tasklist )
861
841
862
842
def _worker (self , task , task_queue ):
863
843
"""Handle processing of the tasks."""
@@ -935,7 +915,6 @@ def _worker(self, task, task_queue):
935
915
task_queue .put (task )
936
916
937
917
def _schedule_processes (self , tasklist ) -> Generator [_Task , None , None ]:
938
- _subprocess_container .stop_all = False
939
918
# Make a shallow copy of the task list,
940
919
# so we don't mess with the callers list.
941
920
tasklist = copy .copy (tasklist )
0 commit comments