asyncio_macos_fix.py
1 """ 2 Minimal asyncio fix for macOS subprocess creation issues. 3 4 This module provides a targeted fix for the NotImplementedError that occurs when 5 creating subprocesses with asyncio on macOS with Python 3.11+. 6 7 The issue occurs because the default event loop policy on macOS doesn't implement 8 get_child_watcher(), which is required for subprocess creation. 9 """ 10 11 import logging 12 import sys 13 14 log = logging.getLogger(__name__) 15 16 17 def apply_macos_asyncio_fix() -> bool: 18 """ 19 Apply minimal asyncio fix for macOS subprocess support. 20 21 This fix specifically addresses the NotImplementedError in 22 asyncio.events.get_child_watcher() by providing a ThreadedChildWatcher 23 implementation that works on macOS. 24 25 Returns: 26 bool: True if fix was applied or not needed, False if fix failed. 27 """ 28 if sys.platform != "darwin" and sys.platform != "linux": 29 log.debug("Not on macOS or linux, asyncio fix not needed.") 30 return True 31 32 import asyncio 33 34 log.info( 35 "On macOS. Applying asyncio child watcher fix for Python %s", 36 sys.version.split()[0], 37 ) 38 39 try: 40 import asyncio.events 41 from asyncio.unix_events import ThreadedChildWatcher 42 43 if not hasattr(asyncio.events, "_original_get_child_watcher_by_solace_fix"): 44 if hasattr(asyncio.events.get_child_watcher, "__name__") and ( 45 asyncio.events.get_child_watcher.__name__ == "get_child_watcher" 46 or asyncio.events.get_child_watcher.__name__ 47 == "patched_get_child_watcher" 48 ): 49 asyncio.events._original_get_child_watcher_by_solace_fix = ( 50 asyncio.events.get_child_watcher 51 ) 52 53 def patched_get_child_watcher(): 54 """Returns a ThreadedChildWatcher that works on macOS.""" 55 return ThreadedChildWatcher() 56 57 asyncio.events.get_child_watcher = patched_get_child_watcher 58 59 test_watcher = asyncio.events.get_child_watcher() 60 log.info( 61 "Successfully applied asyncio fix. get_child_watcher is now patched to use %s.", 62 type(test_watcher).__name__, 63 ) 64 return True 65 66 except ImportError as e_imp: 67 log.error( 68 "Failed to import necessary asyncio modules for macOS fix: %s. This is unexpected.", 69 e_imp, 70 ) 71 return False 72 except Exception as e: 73 log.error("Failed to apply unconditional asyncio fix for macOS: %s", e) 74 return False 75 76 77 def ensure_asyncio_compatibility(): 78 """ 79 Ensure asyncio compatibility for subprocess creation on macOS. 80 This function should be called as early as possible in the application. 81 82 Returns: 83 bool: True if compatibility is ensured, False otherwise. 84 """ 85 return apply_macos_asyncio_fix() 86 87 88 ensure_asyncio_compatibility()