Skip to content

Commit 47eaf72

Browse files
committed
one more stab at a fix
Signed-off-by: Anupam Kumar <kyteinsky@gmail.com>
1 parent 36bcfb7 commit 47eaf72

File tree

1 file changed

+36
-1
lines changed

1 file changed

+36
-1
lines changed

context_chat_backend/utils.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
# SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
33
# SPDX-License-Identifier: AGPL-3.0-or-later
44
#
5+
import atexit
6+
import faulthandler
57
import io
68
import logging
79
import multiprocessing as mp
@@ -114,6 +116,28 @@ def _truncate_capture(text: str) -> tuple[str, bool]:
114116

115117

116118
def exception_wrap(fun: Callable | None, *args, resconn: Connection, stdconn: Connection, **kwargs):
119+
# --- diagnostic probes: write directly to the real stderr FD so they survive
120+
# Python's stdout/stderr redirection below and even os._exit() won't hide them
121+
# from the parent process's stderr stream.
122+
_diag_fd = os.dup(2) # dup before we capture sys.stderr
123+
124+
def _raw_diag(msg: str) -> None:
125+
with suppress(Exception):
126+
os.write(_diag_fd, (msg + '\n').encode())
127+
128+
# Enable faulthandler on the real FD so crash tracebacks (SIGSEGV etc.) appear.
129+
with suppress(Exception):
130+
faulthandler.enable(file=os.fdopen(os.dup(_diag_fd), 'w', closefd=True), all_threads=True)
131+
132+
# Atexit probe: if this message NEVER appears, it means os._exit() (C-level)
133+
# was called with Python's cleanup phase entirely skipped.
134+
_fun_name = getattr(fun, '__name__', str(fun))
135+
atexit.register(
136+
_raw_diag,
137+
f'[exception_wrap/atexit] pid={os.getpid()} target={_fun_name}'
138+
': Python atexit reached (normal Python exit)',
139+
)
140+
117141
stdout_capture = io.StringIO()
118142
stderr_capture = io.StringIO()
119143
orig_stdout = sys.stdout
@@ -124,10 +148,18 @@ def exception_wrap(fun: Callable | None, *args, resconn: Connection, stdconn: Co
124148
try:
125149
if fun is None:
126150
resconn.send({ 'value': None, 'error': None })
151+
_raw_diag(f'[exception_wrap/probe] pid={os.getpid()} target={_fun_name}: result sent (fun=None)')
127152
else:
128-
resconn.send({ 'value': fun(*args, **kwargs), 'error': None })
153+
result_value = fun(*args, **kwargs)
154+
_raw_diag(f'[exception_wrap/probe] pid={os.getpid()} target={_fun_name}: fun() returned, sending result')
155+
resconn.send({ 'value': result_value, 'error': None })
156+
_raw_diag(f'[exception_wrap/probe] pid={os.getpid()} target={_fun_name}: result pipe send complete')
129157
except BaseException as e:
130158
tb = traceback.format_exc()
159+
_raw_diag(
160+
f'[exception_wrap/probe] pid={os.getpid()} target={_fun_name}'
161+
f': caught {type(e).__name__}: {e}'
162+
)
131163
payload = {
132164
'value': None,
133165
'error': e,
@@ -162,6 +194,9 @@ def exception_wrap(fun: Callable | None, *args, resconn: Connection, stdconn: Co
162194
'stdout_truncated': stdout_truncated,
163195
'stderr_truncated': stderr_truncated,
164196
})
197+
_raw_diag(f'[exception_wrap/probe] pid={os.getpid()} target={_fun_name}: finally block complete')
198+
with suppress(Exception):
199+
os.close(_diag_fd)
165200

166201

167202
def exec_in_proc(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None): # noqa: B006

0 commit comments

Comments
 (0)