12
12
import signal
13
13
import sys
14
14
from queue import Queue
15
- from threading import Thread , active_count , Lock
15
+ from threading import Thread , active_count , Lock , Event
16
16
from time import sleep
17
17
from typing import Optional , Any , Mapping , Tuple , Sequence
18
18
55
55
from .sota .os_factory import SotaOsFactory
56
56
from .sota .sota import SOTA
57
57
from .sota .sota_error import SotaError
58
+ from .sota .cancel import cancel_thread
58
59
from .workload_orchestration import WorkloadOrchestration
59
60
from inbm_lib .xmlhandler import *
60
61
from inbm_lib .version import get_friendly_inbm_version_commit
@@ -94,6 +95,8 @@ def __init__(self, args: list[str], broker: DispatcherBroker, install_check_serv
94
95
# Initialize update_queue with a capacity of 1 to ensure serialized handling of updates.
95
96
self .update_queue : Queue [Tuple [str , str , Optional [str ]]] = Queue (1 )
96
97
self ._thread_count = 1
98
+ self ._thread_list : list [Thread ] = []
99
+ self ._active_thread_manifest : Optional [str ] = None
97
100
self ._sota_repos = None
98
101
self .sota_mode = None
99
102
self ._package_list : str = ""
@@ -115,6 +118,7 @@ def __init__(self, args: list[str], broker: DispatcherBroker, install_check_serv
115
118
116
119
self .sqlite_mgr = SqliteManager ()
117
120
self .ap_scheduler = APScheduler (sqlite_mgr = self .sqlite_mgr )
121
+ self ._cancel_event = Event ()
118
122
119
123
def stop (self ) -> None :
120
124
self .RUNNING = False
@@ -186,7 +190,13 @@ def _sig_handler(signo, frame) -> None:
186
190
if active_count () - active_start_count < self ._thread_count :
187
191
worker = Thread (target = handle_updates , args = (self ,))
188
192
worker .setDaemon (True )
193
+ self ._thread_list .append (worker )
189
194
worker .start ()
195
+
196
+ # Periodically check if processes have finished. If process finished, remove it from the list.
197
+ for thread in self ._thread_list [:]:
198
+ if not thread .is_alive ():
199
+ self ._thread_list .remove (thread )
190
200
sleep (1 )
191
201
192
202
self ._dispatcher_broker .mqtt_publish (f'{ AGENT } /state' , 'dead' , retain = True )
@@ -310,6 +320,8 @@ def do_install(self, xml: str, schema_location: Optional[str] = None, job_id: st
310
320
result : Result = Result ()
311
321
logger .debug ("do_install" )
312
322
parsed_head = None
323
+ # Assumption is that there is only one active OTA thread at a time
324
+ self ._active_thread_manifest = xml
313
325
try : # TODO: Split into multiple try/except blocks
314
326
type_of_manifest , parsed_head = \
315
327
_check_type_validate_manifest (xml , schema_location = schema_location )
@@ -411,7 +423,8 @@ def _do_ota_update(self, ota_type: str, repo_type: str, resource: dict,
411
423
self ._sota_repos ,
412
424
self ._install_check_service ,
413
425
self ._update_logger ,
414
- self .config_dbs )
426
+ self .config_dbs ,
427
+ self ._cancel_event )
415
428
416
429
p = factory .create_parser ()
417
430
# NOTE: p.parse can raise one of the *otaError exceptions
@@ -440,7 +453,8 @@ def _validate_pota_manifest(self, repo_type: str,
440
453
self ._sota_repos ,
441
454
self ._install_check_service ,
442
455
self ._update_logger ,
443
- self .config_dbs )
456
+ self .config_dbs ,
457
+ self ._cancel_event )
444
458
p = factory .create_parser ()
445
459
# NOTE: p.parse can raise one of the *otaError exceptions
446
460
parsed_manifest = p .parse (ota_list [ota ], kwargs , parsed_head )
@@ -497,7 +511,32 @@ def _on_cloud_request(self, topic: str, payload: str, qos: int) -> None:
497
511
request_type = topic .split ('/' )[2 ]
498
512
request_id = topic .split ('/' )[3 ] if len (topic .split ('/' )) > 3 else None
499
513
manifest = payload
500
- self ._add_request_to_queue (request_type , manifest , request_id )
514
+ if not self ._handle_cancel_request (request_type , manifest ):
515
+ self ._add_request_to_queue (request_type , manifest , request_id )
516
+
517
+ def _handle_cancel_request (self , request_type : str , manifest : str ) -> bool :
518
+ """
519
+ Check if it is a SOTA cancel request. If it is, send the terminate signal to current process.
520
+
521
+ @param request_type: type of the request
522
+ @param manifest: manifest to be processed
523
+ @return: True if the request has been processed; False if no request has been handled.
524
+ """
525
+ if request_type == "install" :
526
+ type_of_manifest , parsed_head = \
527
+ _check_type_validate_manifest (manifest )
528
+ type_of_active_manifest = active_thread_parsed_head = None
529
+ if self ._active_thread_manifest :
530
+ type_of_active_manifest , active_thread_parsed_head = \
531
+ _check_type_validate_manifest (self ._active_thread_manifest )
532
+ result = cancel_thread (type_of_manifest , parsed_head , self ._thread_list ,
533
+ type_of_active_manifest , active_thread_parsed_head ,
534
+ self ._dispatcher_broker , self ._cancel_event )
535
+ if result :
536
+ logger .debug (f"Request cancel complete." )
537
+ self ._send_result (str (Result (CODE_OK , "Request complete." )))
538
+ return True
539
+ return False
501
540
502
541
def _on_message (self , topic : str , payload : Any , qos : int ) -> None :
503
542
"""Called when a message is received from _telemetry-agent
@@ -619,6 +658,7 @@ def invoke_sota(self, snapshot: Optional[Any] = None, action: Optional[Any] = No
619
658
self ._update_logger ,
620
659
self ._sota_repos ,
621
660
self ._install_check_service ,
661
+ self ._cancel_event ,
622
662
snapshot , action )
623
663
624
664
sota_instance .execute (self .proceed_without_rollback )
0 commit comments