Skip to content

Commit 768e600

Browse files
authored
Merge pull request #1870 from rstudio/fix/sigpipe-embedded-python
Fix embedded Python SIGPIPE handling
2 parents bc1b073 + c81e95e commit 768e600

File tree

3 files changed

+45
-0
lines changed

3 files changed

+45
-0
lines changed

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# reticulate (development version)
22

3+
- Fix spurious `Error: ignoring SIGPIPE signal` on Unix when embedded Python writes to a closed pipe (#1868).
4+
35
# reticulate 1.44.1
46

57
- The default Python version in `install_python()`

src/python.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2853,6 +2853,11 @@ static void interrupt_handler(int signum) {
28532853

28542854
PyOS_sighandler_t orig_interrupt_handler = NULL;
28552855

2856+
#ifndef _WIN32
2857+
static bool s_restore_sigpipe_handler = false;
2858+
static PyOS_sighandler_t s_orig_sigpipe_handler = SIG_DFL;
2859+
#endif
2860+
28562861
PyOS_sighandler_t install_interrupt_handlers_() {
28572862
// Installs a C handler and a Python handler
28582863
// Exported as an R symbol in case the user did some action that cleared the
@@ -3171,6 +3176,16 @@ void py_initialize(const std::string& python,
31713176
// initialize python
31723177
Py_InitializeEx(0); // 0 means "do not install signal handlers"
31733178
s_was_python_initialized_by_reticulate = true;
3179+
3180+
#ifndef _WIN32
3181+
// Python ignores SIGPIPE by default to avoid the process being
3182+
// interrupted during normal pipe / IPC usage (EPIPE is handled as an
3183+
// exception instead). Because reticulate uses Py_InitializeEx(0), we
3184+
// must explicitly mirror that behavior.
3185+
s_orig_sigpipe_handler = PyOS_setsig(SIGPIPE, SIG_IGN);
3186+
s_restore_sigpipe_handler = true;
3187+
#endif
3188+
31743189
const wchar_t *argv[1] = {s_python_v3.c_str()};
31753190
PySys_SetArgv_v3(1, const_cast<wchar_t**>(argv));
31763191

@@ -3262,6 +3277,14 @@ void py_finalize() {
32623277
Py_MakePendingCalls();
32633278
if (orig_interrupt_handler)
32643279
reticulate_setsig(SIGINT, orig_interrupt_handler);
3280+
3281+
#ifndef _WIN32
3282+
if (s_restore_sigpipe_handler) {
3283+
PyOS_setsig(SIGPIPE, s_orig_sigpipe_handler);
3284+
s_restore_sigpipe_handler = false;
3285+
}
3286+
#endif
3287+
32653288
is_py_finalized = true;
32663289
Py_Finalize();
32673290
}

tests/testthat/test-sigpipe.R

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
test_that("SIGPIPE does not interrupt embedded Python", {
2+
testthat::skip_on_os("windows")
3+
skip_if_no_python()
4+
5+
err <- tryCatch(
6+
{
7+
py_run_string("import os; r,w=os.pipe(); os.close(r); os.write(w,b'x')")
8+
NULL
9+
},
10+
error = function(e) e
11+
)
12+
13+
expect_true(inherits(err, "error"))
14+
expect_false(grepl(
15+
"ignoring SIGPIPE signal",
16+
conditionMessage(err),
17+
fixed = TRUE
18+
))
19+
expect_true(grepl("BrokenPipeError|\\[Errno 32\\]", conditionMessage(err)))
20+
})

0 commit comments

Comments
 (0)