1212import traceback
1313from pathlib import Path
1414from queue import Empty as EmptyQueue
15- from typing import TYPE_CHECKING , Any , Dict , Optional , Tuple , Type , Union
15+ from typing import Any , Dict , Optional , Tuple , Type , Union
1616
1717import psutil
1818from multiprocess import Process , Queue
1919from selenium .common .exceptions import WebDriverException
2020from tblib import Traceback , pickling_support
2121
22+ from .command_execution_context import CommandExecutionContext
2223from .command_sequence import CommandSequence
2324from .commands .browser_commands import FinalizeCommand
2425from .commands .profile_commands import dump_profile
2728from .config import BrowserParamsInternal , ManagerParamsInternal
2829from .deploy_browsers import deploy_firefox
2930from .errors import BrowserConfigError , BrowserCrashError , ProfileLoadError
31+ from .failure_tracker import CommandFailure
3032from .socket_interface import ClientSocket
3133from .storage .storage_providers import TableName
3234from .types import BrowserId , VisitId
3840
3941pickling_support .install ()
4042
41- if TYPE_CHECKING :
42- from .task_manager import TaskManager
43-
4443
4544class BrowserManagerHandle :
4645 """The BrowserManagerHandle class is responsible for holding all the
@@ -342,16 +341,23 @@ def close_browser_manager(self, force: bool = False) -> None:
342341
343342 def execute_command_sequence (
344343 self ,
345- # Quoting to break cyclic import, see https://stackoverflow.com/a/39757388
346- task_manager : "TaskManager" ,
344+ context : CommandExecutionContext ,
347345 command_sequence : CommandSequence ,
348346 ) -> None :
349347 """
350- Sends CommandSequence to the BrowserManager one command at a time
348+ Sends CommandSequence to the BrowserManager one command at a time.
349+
350+ Parameters
351+ ----------
352+ context : CommandExecutionContext
353+ Provides storage and failure tracking without requiring a
354+ direct reference to TaskManager.
355+ command_sequence : CommandSequence
356+ The sequence of commands to execute.
351357 """
352358 assert self .browser_id is not None
353359 assert self .curr_visit_id is not None
354- task_manager . sock .store_record (
360+ context .store_record (
355361 TableName ("site_visits" ),
356362 self .curr_visit_id ,
357363 {
@@ -411,11 +417,9 @@ def execute_command_sequence(
411417 "process while executing command %s. Setting failure "
412418 "status." % (self .browser_id , str (command ))
413419 )
414- task_manager .failure_status = {
415- "ErrorType" : "CriticalChildException" ,
416- "CommandSequence" : command_sequence ,
417- "Exception" : status [1 ],
418- }
420+ context .failure_tracker .set_critical_failure (
421+ "CriticalChildException" , command_sequence , exception = status [1 ]
422+ )
419423 error_text , tb = self ._unpack_pickled_error (status [1 ])
420424 elif status [0 ] == "FAILED" :
421425 command_status = "error"
@@ -435,7 +439,7 @@ def execute_command_sequence(
435439 else :
436440 raise ValueError ("Unknown browser status message %s" % status )
437441
438- task_manager . sock .store_record (
442+ context .store_record (
439443 TableName ("crawl_history" ),
440444 self .curr_visit_id ,
441445 {
@@ -454,39 +458,42 @@ def execute_command_sequence(
454458 )
455459
456460 if command_status == "critical" :
457- task_manager .sock .finalize_visit_id (
458- success = False ,
461+ context .finalize_visit_id (
459462 visit_id = self .curr_visit_id ,
463+ success = False ,
460464 )
461465 return
462466
463467 if command_status != "ok" :
464- with task_manager .threadlock :
465- task_manager .failure_count += 1
466- if task_manager .failure_count > task_manager .failure_limit :
468+ over_limit = context .failure_tracker .record_failure (
469+ CommandFailure (
470+ browser_id = self .browser_id ,
471+ command = repr (command ),
472+ command_status = command_status ,
473+ error = error_text ,
474+ traceback = tb ,
475+ )
476+ )
477+ if over_limit :
467478 self .logger .critical (
468479 "BROWSER %i: Command execution failure pushes failure "
469480 "count above the allowable limit. Setting "
470481 "failure_status." % self .browser_id
471482 )
472- task_manager .failure_status = {
473- "ErrorType" : "ExceedCommandFailureLimit" ,
474- "CommandSequence" : command_sequence ,
475- }
483+ context .failure_tracker .set_critical_failure (
484+ "ExceedCommandFailureLimit" , command_sequence
485+ )
476486 return
477487 self .restart_required = True
478488 self .logger .debug (
479489 "BROWSER %i: Browser restart required" % self .browser_id
480490 )
481491 # Reset failure_count at the end of each successful command sequence
482492 elif type (command ) is FinalizeCommand :
483- with task_manager .threadlock :
484- task_manager .failure_count = 0
493+ context .failure_tracker .reset ()
485494
486495 if self .restart_required :
487- task_manager .sock .finalize_visit_id (
488- success = False , visit_id = self .curr_visit_id
489- )
496+ context .finalize_visit_id (visit_id = self .curr_visit_id , success = False )
490497 break
491498
492499 self .logger .info (
@@ -499,7 +506,7 @@ def execute_command_sequence(
499506 # internal buffers to drain. Stopgap in support of #135
500507 time .sleep (2 )
501508
502- if task_manager .closing :
509+ if context .closing :
503510 return
504511
505512 # Allow StorageWatchdog to utilize built-in browser reset functionality
@@ -519,10 +526,9 @@ def execute_command_sequence(
519526 "BROWSER %i: Exceeded the maximum allowable consecutive "
520527 "browser launch failures. Setting failure_status." % self .browser_id
521528 )
522- task_manager .failure_status = {
523- "ErrorType" : "ExceedLaunchFailureLimit" ,
524- "CommandSequence" : command_sequence ,
525- }
529+ context .failure_tracker .set_critical_failure (
530+ "ExceedLaunchFailureLimit" , command_sequence
531+ )
526532 return
527533 self .restart_required = False
528534
0 commit comments