From 77a3aae5a4250db1642c060a01bfce50ee8505ae Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Sun, 23 Nov 2025 18:29:38 -0500 Subject: [PATCH 1/2] Attempt to fix cannot pickle 'TextIOWrapper' instances error for some distros. --- src/fenrirscreenreader/core/processManager.py | 82 ++++++++++++++++--- src/fenrirscreenreader/fenrirVersion.py | 2 +- 2 files changed, 73 insertions(+), 11 deletions(-) diff --git a/src/fenrirscreenreader/core/processManager.py b/src/fenrirscreenreader/core/processManager.py index 91c9325c..5ec18cdb 100644 --- a/src/fenrirscreenreader/core/processManager.py +++ b/src/fenrirscreenreader/core/processManager.py @@ -13,6 +13,64 @@ from fenrirscreenreader.core import debug from fenrirscreenreader.core.eventData import FenrirEventType +# Standalone worker functions for multiprocessing (cannot be instance methods) +def _custom_event_worker_process( + running, event_queue, function, pargs=None, run_once=False +): + """ + Standalone worker function for custom events in multiprocessing. + Cannot use instance methods due to pickle limitations with self.env. + """ + if not callable(function): + return + while running.value: + try: + if pargs: + function(running, event_queue, pargs) + else: + function(running, event_queue) + except Exception as e: + # Cannot use DebugManager in multiprocess context + print( + f"ProcessManager:_custom_event_worker_process:function(" + f"{function}):{e}" + ) + if run_once: + break + + +def _simple_event_worker_process( + running, event_queue, event, function, pargs=None, run_once=False +): + """ + Standalone worker function for simple events in multiprocessing. + Cannot use instance methods due to pickle limitations with self.env. + """ + if not isinstance(event, FenrirEventType): + return + if not callable(function): + return + while running.value: + data = None + try: + if pargs: + data = function(running, pargs) + else: + data = function(running) + except Exception as e: + # Cannot use DebugManager in multiprocess context + print( + f"ProcessManager:_simple_event_worker_process:function(" + f"{function}):{e}" + ) + try: + event_queue.put({"Type": event, "data": data}, timeout=0.1) + except Exception as e: + print(f"ProcessManager: Failed to put event to queue: {e}") + if run_once: + break + + class ProcessManager: def __init__(self): self._Processes = [] @@ -60,8 +118,8 @@ class ProcessManager: original_sigint_handler = signal.signal(signal.SIGINT, signal.SIG_IGN) if multiprocess: t = Process( - target=self.custom_event_worker_thread, - args=(event_queue, function, pargs, run_once), + target=_custom_event_worker_process, + args=(self.running, event_queue, function, pargs, run_once), ) self._Processes.append(t) else: # use thread instead of process @@ -78,9 +136,11 @@ class ProcessManager: ): original_sigint_handler = signal.signal(signal.SIGINT, signal.SIG_IGN) if multiprocess: + # Get event queue reference before creating process + event_queue = self.env["runtime"]["EventManager"].get_event_queue() t = Process( - target=self.simple_event_worker_thread, - args=(event, function, pargs, run_once), + target=_simple_event_worker_process, + args=(self.running, event_queue, event, function, pargs, run_once), ) self._Processes.append(t) else: @@ -106,12 +166,13 @@ class ProcessManager: else: function(self.running, event_queue) except Exception as e: - self.env["runtime"]["DebugManager"].write_debug_out( + # Cannot use DebugManager in multiprocess context due to + # pickle limitations with file handles + print( "ProcessManager:custom_event_worker_thread:function(" + str(function) + "):" - + str(e), - debug.DebugLevel.ERROR, + + str(e) ) if run_once: break @@ -131,12 +192,13 @@ class ProcessManager: else: data = function(self.running) except Exception as e: - self.env["runtime"]["DebugManager"].write_debug_out( + # Cannot use DebugManager in multiprocess context due to + # pickle limitations with file handles + print( "ProcessManager:simple_event_worker_thread:function(" + str(function) + "):" - + str(e), - debug.DebugLevel.ERROR, + + str(e) ) self.env["runtime"]["EventManager"].put_to_event_queue(event, data) if run_once: diff --git a/src/fenrirscreenreader/fenrirVersion.py b/src/fenrirscreenreader/fenrirVersion.py index b0cf2bc9..34a97ffd 100644 --- a/src/fenrirscreenreader/fenrirVersion.py +++ b/src/fenrirscreenreader/fenrirVersion.py @@ -4,5 +4,5 @@ # Fenrir TTY screen reader # By Chrys, Storm Dragon, and contributors. -version = "2025.10.17" +version = "2025.11.23" code_name = "testing" From 87553bdc385cdd781ae6a64bfd5ca6b219f2e81b Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Sun, 23 Nov 2025 18:37:21 -0500 Subject: [PATCH 2/2] more fixes for the pickle error. --- src/fenrirscreenreader/core/processManager.py | 16 ++++++++++++++-- src/fenrirscreenreader/remoteDriver/tcpDriver.py | 4 +++- .../remoteDriver/unixDriver.py | 4 +++- .../screenDriver/vcsaDriver.py | 4 +++- 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/fenrirscreenreader/core/processManager.py b/src/fenrirscreenreader/core/processManager.py index 5ec18cdb..d6835397 100644 --- a/src/fenrirscreenreader/core/processManager.py +++ b/src/fenrirscreenreader/core/processManager.py @@ -13,7 +13,19 @@ from fenrirscreenreader.core import debug from fenrirscreenreader.core.eventData import FenrirEventType -# Standalone worker functions for multiprocessing (cannot be instance methods) +# Standalone functions for multiprocessing (cannot be instance methods) +def _heart_beat_timer(running): + """ + Standalone heartbeat timer function for multiprocessing. + Returns current timestamp after a short sleep. + """ + try: + time.sleep(0.5) + except Exception as e: + print(f"ProcessManager _heart_beat_timer: Error during sleep: {e}") + return time.time() + + def _custom_event_worker_process( running, event_queue, function, pargs=None, run_once=False ): @@ -81,7 +93,7 @@ class ProcessManager: self.running = self.env["runtime"]["EventManager"].get_running() self.add_simple_event_thread( FenrirEventType.heart_beat, - self.heart_beat_timer, + _heart_beat_timer, multiprocess=True, ) diff --git a/src/fenrirscreenreader/remoteDriver/tcpDriver.py b/src/fenrirscreenreader/remoteDriver/tcpDriver.py index 149eda17..dd89ce4a 100644 --- a/src/fenrirscreenreader/remoteDriver/tcpDriver.py +++ b/src/fenrirscreenreader/remoteDriver/tcpDriver.py @@ -20,8 +20,10 @@ class driver(remoteDriver): def initialize(self, environment): self.env = environment + # Use threading instead of multiprocessing to avoid pickle issues + # with self.env (which contains unpicklable file handles) self.env["runtime"]["ProcessManager"].add_custom_event_thread( - self.watch_dog, multiprocess=True + self.watch_dog, multiprocess=False ) def watch_dog(self, active, event_queue): diff --git a/src/fenrirscreenreader/remoteDriver/unixDriver.py b/src/fenrirscreenreader/remoteDriver/unixDriver.py index d994ecfa..088c7339 100644 --- a/src/fenrirscreenreader/remoteDriver/unixDriver.py +++ b/src/fenrirscreenreader/remoteDriver/unixDriver.py @@ -20,8 +20,10 @@ class driver(remoteDriver): def initialize(self, environment): self.env = environment + # Use threading instead of multiprocessing to avoid pickle issues + # with self.env (which contains unpicklable file handles) self.env["runtime"]["ProcessManager"].add_custom_event_thread( - self.watch_dog, multiprocess=True + self.watch_dog, multiprocess=False ) def watch_dog(self, active, event_queue): diff --git a/src/fenrirscreenreader/screenDriver/vcsaDriver.py b/src/fenrirscreenreader/screenDriver/vcsaDriver.py index aef34b4e..a71af4a6 100644 --- a/src/fenrirscreenreader/screenDriver/vcsaDriver.py +++ b/src/fenrirscreenreader/screenDriver/vcsaDriver.py @@ -126,8 +126,10 @@ class driver(screenDriver): "default", # fontfamily ] ) # end attribute ) + # Use threading instead of multiprocessing to avoid pickle issues + # with self.env (which contains unpicklable file handles) self.env["runtime"]["ProcessManager"].add_custom_event_thread( - self.update_watchdog, multiprocess=True + self.update_watchdog, multiprocess=False ) def get_curr_screen(self):