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 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
3941
4042pickling_support .install ()
4143
42- if TYPE_CHECKING :
43- from .task_manager import TaskManager
44-
4544
4645class BrowserManagerHandle :
4746 """The BrowserManagerHandle class is responsible for holding all the
@@ -343,16 +342,23 @@ def close_browser_manager(self, force: bool = False) -> None:
343342
344343 def execute_command_sequence (
345344 self ,
346- # Quoting to break cyclic import, see https://stackoverflow.com/a/39757388
347- task_manager : "TaskManager" ,
345+ context : CommandExecutionContext ,
348346 command_sequence : CommandSequence ,
349347 ) -> None :
350348 """
351- Sends CommandSequence to the BrowserManager one command at a time
349+ Sends CommandSequence to the BrowserManager one command at a time.
350+
351+ Parameters
352+ ----------
353+ context : CommandExecutionContext
354+ Provides storage and failure tracking without requiring a
355+ direct reference to TaskManager.
356+ command_sequence : CommandSequence
357+ The sequence of commands to execute.
352358 """
353359 assert self .browser_id is not None
354360 assert self .curr_visit_id is not None
355- task_manager . sock .store_record (
361+ context .store_record (
356362 TableName ("site_visits" ),
357363 self .curr_visit_id ,
358364 {
@@ -412,11 +418,9 @@ def execute_command_sequence(
412418 "process while executing command %s. Setting failure "
413419 "status." % (self .browser_id , str (command ))
414420 )
415- task_manager .failure_status = {
416- "ErrorType" : "CriticalChildException" ,
417- "CommandSequence" : command_sequence ,
418- "Exception" : status [1 ],
419- }
421+ context .failure_tracker .set_critical_failure (
422+ "CriticalChildException" , command_sequence , exception = status [1 ]
423+ )
420424 error_text , tb = self ._unpack_pickled_error (status [1 ])
421425 elif status [0 ] == "FAILED" :
422426 command_status = "error"
@@ -436,7 +440,7 @@ def execute_command_sequence(
436440 else :
437441 raise ValueError ("Unknown browser status message %s" % status )
438442
439- task_manager . sock .store_record (
443+ context .store_record (
440444 TableName ("crawl_history" ),
441445 self .curr_visit_id ,
442446 {
@@ -455,39 +459,42 @@ def execute_command_sequence(
455459 )
456460
457461 if command_status == "critical" :
458- task_manager .sock .finalize_visit_id (
459- success = False ,
462+ context .finalize_visit_id (
460463 visit_id = self .curr_visit_id ,
464+ success = False ,
461465 )
462466 return
463467
464468 if command_status != "ok" :
465- with task_manager .threadlock :
466- task_manager .failure_count += 1
467- if task_manager .failure_count > task_manager .failure_limit :
469+ over_limit = context .failure_tracker .record_failure (
470+ CommandFailure (
471+ browser_id = self .browser_id ,
472+ command = repr (command ),
473+ command_status = command_status ,
474+ error = error_text ,
475+ traceback = tb ,
476+ )
477+ )
478+ if over_limit :
468479 self .logger .critical (
469480 "BROWSER %i: Command execution failure pushes failure "
470481 "count above the allowable limit. Setting "
471482 "failure_status." % self .browser_id
472483 )
473- task_manager .failure_status = {
474- "ErrorType" : "ExceedCommandFailureLimit" ,
475- "CommandSequence" : command_sequence ,
476- }
484+ context .failure_tracker .set_critical_failure (
485+ "ExceedCommandFailureLimit" , command_sequence
486+ )
477487 return
478488 self .restart_required = True
479489 self .logger .debug (
480490 "BROWSER %i: Browser restart required" % self .browser_id
481491 )
482492 # Reset failure_count at the end of each successful command sequence
483493 elif type (command ) is FinalizeCommand :
484- with task_manager .threadlock :
485- task_manager .failure_count = 0
494+ context .failure_tracker .reset ()
486495
487496 if self .restart_required :
488- task_manager .sock .finalize_visit_id (
489- success = False , visit_id = self .curr_visit_id
490- )
497+ context .finalize_visit_id (visit_id = self .curr_visit_id , success = False )
491498 break
492499
493500 self .logger .info (
@@ -500,7 +507,7 @@ def execute_command_sequence(
500507 # internal buffers to drain. Stopgap in support of #135
501508 time .sleep (2 )
502509
503- if task_manager .closing :
510+ if context .closing :
504511 return
505512
506513 # Allow StorageWatchdog to utilize built-in browser reset functionality
@@ -520,10 +527,9 @@ def execute_command_sequence(
520527 "BROWSER %i: Exceeded the maximum allowable consecutive "
521528 "browser launch failures. Setting failure_status." % self .browser_id
522529 )
523- task_manager .failure_status = {
524- "ErrorType" : "ExceedLaunchFailureLimit" ,
525- "CommandSequence" : command_sequence ,
526- }
530+ context .failure_tracker .set_critical_failure (
531+ "ExceedLaunchFailureLimit" , command_sequence
532+ )
527533 return
528534 self .restart_required = False
529535
0 commit comments