@@ -86,34 +86,41 @@ def log_file(pid):
86
86
87
87
#===============================================================================
88
88
89
- def _run_in_loop (func , args ):
89
+ def _run_in_loop (func , * args ):
90
+ #=============================
90
91
loop = uvloop .new_event_loop ()
91
- loop .run_until_complete (func (args ))
92
+ loop .run_until_complete (func (* args ))
92
93
93
- async def _make_map (params ):
94
- #===========================
94
+ async def _make_map (params , process_log_queue : multiprocessing . Queue ):
95
+ #=====================================================================
95
96
try :
96
- mapmaker = MapMaker (params )
97
+ mapmaker = MapMaker (params , process_log_queue = process_log_queue )
97
98
mapmaker .make ()
98
99
except Exception as e :
99
100
utils .log .exception (e , exc_info = True )
101
+ ## And now we need to send a CRITICAL failed message onto the msg_queue...
102
+ ## as any raised exception will end up here
103
+ ## e.g. ???
104
+ ## {"exc_info": true, "level": "error", "timestamp": "2025-08-18T08:01:06.842287Z", "msg": "GitCommandError(['git', 'checkout', 'staging'], 1, b\"error: pathspec 'staging' did not match any file(s) known to git\", b'')"}
100
105
sys .exit (1 )
101
106
102
107
#===============================================================================
103
108
104
109
class MakerProcess (multiprocessing .Process ):
105
- def __init__ (self , params : dict [str , Any ]):
110
+ def __init__ (self , params : dict [str , Any ], msg_queue : multiprocessing . Queue ):
106
111
id = str (uuid .uuid4 ())
107
112
self .__process_log_queue = multiprocessing .Queue ()
108
- params ['logQueue' ] = self .__process_log_queue
109
- super ().__init__ (target = _run_in_loop , args = (_make_map , params ), name = id )
113
+ super ().__init__ (target = _run_in_loop , args = (_make_map , params , self .__process_log_queue ), name = id )
110
114
self .__id = id
111
115
self .__process_id = None
112
116
self .__log_file = None
113
- self .__msg_queue = multiprocessing . Queue ()
117
+ self .__msg_queue = msg_queue
114
118
self .__status = 'queued'
115
119
self .__result = {}
116
120
121
+ def __str__ (self ):
122
+ return f'MakerProcess { self .__id } : { self .__status } , { self .is_alive ()} ({ self .pid } )'
123
+
117
124
@property
118
125
def completed (self ):
119
126
return self .__status in ['terminated' , 'aborted' ]
@@ -126,24 +133,17 @@ def log_file(self):
126
133
def id (self ):
127
134
return self .__id
128
135
129
- @property
130
- def msg_queue (self ):
131
- return self .__msg_queue
132
-
133
136
@property
134
137
def process_id (self ):
135
138
return self .__process_id
136
139
137
140
@property
138
- def result (self ):
141
+ def result (self ) -> dict :
139
142
return self .__result
140
143
141
144
@property
142
145
def status (self ) -> str :
143
146
return self .__status
144
- @status .setter
145
- def status (self , value : str ):
146
- self .__status = value
147
147
148
148
def close (self ):
149
149
#===============
@@ -205,6 +205,9 @@ def __init__(self):
205
205
os .makedirs (settings ['MAPMAKER_LOGS' ])
206
206
self .__map_dir = settings ['FLATMAP_ROOT' ]
207
207
208
+ self .__last_running_process_id : Optional [str ] = None
209
+ self .__last_running_process_status : str = 'terminated'
210
+ self .__process_msg_queue = multiprocessing .Queue ()
208
211
self .__running_process : Optional [MakerProcess ] = None
209
212
self .__terminate_event = asyncio .Event ()
210
213
self .__process_lock = asyncio .Lock ()
@@ -227,16 +230,25 @@ async def get_log(self, id, start_line=1):
227
230
return log_lines
228
231
return ''
229
232
233
+ def __flush_process_log (self ):
234
+ #=============================
235
+ while True :
236
+ try :
237
+ self .__process_msg_queue .get (block = False )
238
+ except queue .Empty :
239
+ return
240
+
230
241
async def get_process_log (self , id ):
231
242
#===================================
232
243
if self .__running_process is not None and id == self .__running_process .id :
233
244
while self .__running_process is not None and not self .__running_process .completed :
234
245
try :
235
- msg = self .__running_process . msg_queue .get (block = False )
246
+ msg = self .__process_msg_queue .get (block = False )
236
247
yield msg
237
248
except queue .Empty :
238
249
await asyncio .sleep (0.01 )
239
250
251
+
240
252
async def make (self , data : MakerData ) -> MakerStatus :
241
253
#====================================================
242
254
params = {key : value for (key , value ) in dataclasses .asdict (data ).items ()
@@ -249,7 +261,8 @@ async def make(self, data: MakerData) -> MakerStatus:
249
261
'logPath' : settings ['MAPMAKER_LOGS' ] # Logfile name is `PROCESS_ID.json.log`
250
262
})
251
263
if self .__running_process is None :
252
- process = MakerProcess (params )
264
+ self .__flush_process_log ()
265
+ process = MakerProcess (params , self .__process_msg_queue )
253
266
await self .__start_process (process )
254
267
return await self .status (process .id )
255
268
else :
@@ -267,15 +280,23 @@ async def _run(self):
267
280
process = self .__running_process
268
281
process .read_process_log_queue ()
269
282
if not process .is_alive ():
270
- process .close ()
271
- self .__running_process = None
272
- maker_result = process .result
273
- if len (maker_result ):
283
+ if not self .__process_msg_queue .empty ():
284
+ self .__log .error (f'Process log queue is not empty: { process .status } { process .result } ' )
285
+ while True :
286
+ try :
287
+ self .__log .warn (f'Queued: { self .__process_msg_queue .get (block = False )} ' )
288
+ except queue .Empty :
289
+ break
290
+ process .close () # This updates status
291
+ self .__last_running_process_id = process .id
292
+ self .__last_running_process_status = process .status
293
+ if len (process .result ):
274
294
info = ', ' .join ([ f'{ key } : { value } ' for key in MAKER_RESULT_KEYS
275
- if (value := maker_result .get (key )) is not None ])
295
+ if (value := process . result .get (key )) is not None ])
276
296
self .__log .info (f'Mapmaker succeeded: { process .name } , Map { info } ' )
277
297
else :
278
298
self .__log .error (f'Mapmaker FAILED: { process .name } ' )
299
+ self .__running_process = None
279
300
await asyncio .sleep (0.01 )
280
301
281
302
def terminate (self ):
@@ -288,6 +309,9 @@ async def status(self, id) -> MakerStatus:
288
309
if self .__running_process is not None and id == self .__running_process .id :
289
310
status = self .__running_process .status
290
311
pid = self .__running_process .process_id
312
+ elif id == self .__last_running_process_id :
313
+ status = self .__last_running_process_status
314
+ self .__last_running_process_id = None
291
315
else :
292
316
status = 'unknown'
293
317
return MakerStatus (status , id , pid )
@@ -297,6 +321,7 @@ async def __start_process(self, process: MakerProcess):
297
321
process .start ()
298
322
async with self .__process_lock :
299
323
self .__running_process = process
324
+ self .__last_running_process_id = None
300
325
self .__log .info (f'Started mapmaker process: { process .name } , PID: { process .process_id } ' )
301
326
302
327
#===============================================================================
0 commit comments