From e8b72d9b6d13668be1779bc43eab8a99e636e540 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Sat, 16 Nov 2024 15:34:15 +0800 Subject: [PATCH 1/9] Fix compilation on Windows --- src/cysignals/implementation.c | 83 ++++++++++++++++++++++++++++------ src/cysignals/signals.pyx | 4 ++ 2 files changed, 73 insertions(+), 14 deletions(-) diff --git a/src/cysignals/implementation.c b/src/cysignals/implementation.c index c3358a64..32187e3f 100644 --- a/src/cysignals/implementation.c +++ b/src/cysignals/implementation.c @@ -34,7 +34,6 @@ Interrupt and signal handling for Cython #include #include #include -#include #if HAVE_SYS_TYPES_H #include #endif @@ -87,6 +86,11 @@ void custom_set_pending_signal(int sig){ #if HAVE_WINDOWS_H #include +#else +#include +/* A trampoline to jump to after handling a signal. */ +static cyjmp_buf trampoline_setup; +static sigjmp_buf trampoline; #endif #include "struct_signals.h" @@ -108,10 +112,6 @@ static sigset_t default_sigmask; static sigset_t sigmask_with_sigint; #endif -/* A trampoline to jump to after handling a signal. */ -static cyjmp_buf trampoline_setup; -static sigjmp_buf trampoline; - static void setup_cysignals_handlers(void); static void cysigs_interrupt_handler(int sig); static void cysigs_signal_handler(int sig); @@ -194,15 +194,23 @@ static inline void print_stderr_ptr(void *ptr) /* Reset all signal handlers and the signal mask to their defaults. */ static inline void sig_reset_defaults(void) { +#ifdef SIGHUP signal(SIGHUP, SIG_DFL); +#endif signal(SIGINT, SIG_DFL); +#ifdef SIGQUIT signal(SIGQUIT, SIG_DFL); +#endif signal(SIGILL, SIG_DFL); signal(SIGABRT, SIG_DFL); signal(SIGFPE, SIG_DFL); +#ifdef SIGBUS signal(SIGBUS, SIG_DFL); +#endif signal(SIGSEGV, SIG_DFL); +#ifdef SIGALRM signal(SIGALRM, SIG_DFL); +#endif signal(SIGTERM, SIG_DFL); #if HAVE_SIGPROCMASK sigprocmask(SIG_SETMASK, &default_sigmask, NULL); @@ -228,10 +236,14 @@ static inline void sigdie_for_sig(int sig, int inside) sigdie(sig, "Unhandled SIGFPE during signal handling."); else if (sig == SIGSEGV) sigdie(sig, "Unhandled SIGSEGV during signal handling."); + #ifdef SIGBUS else if (sig == SIGBUS) sigdie(sig, "Unhandled SIGBUS during signal handling."); + #endif + #ifdef SIGQUIT else if (sig == SIGQUIT) sigdie(sig, NULL); + #endif else sigdie(sig, "Unknown signal during signal handling."); } @@ -244,10 +256,14 @@ static inline void sigdie_for_sig(int sig, int inside) sigdie(sig, "Unhandled SIGFPE: An unhandled floating point exception occurred."); else if (sig == SIGSEGV) sigdie(sig, "Unhandled SIGSEGV: A segmentation fault occurred."); + #ifdef SIGBUS else if (sig == SIGBUS) sigdie(sig, "Unhandled SIGBUS: A bus error occurred."); + #endif + #ifdef SIGQUIT else if (sig == SIGQUIT) sigdie(sig, NULL); + #endif else sigdie(sig, "Unknown signal received."); } @@ -259,8 +275,22 @@ static inline void sigdie_for_sig(int sig, int inside) #include "implementation_cygwin.c" #endif +void get_monotonic_time(struct timespec *ts) { +#ifdef _WIN32 + LARGE_INTEGER frequency; + LARGE_INTEGER counter; -/* Handler for SIGHUP, SIGINT, SIGALRM + QueryPerformanceFrequency(&frequency); + QueryPerformanceCounter(&counter); + + ts->tv_sec = counter.QuadPart / frequency.QuadPart; + ts->tv_nsec = (counter.QuadPart % frequency.QuadPart) * 1e9 / frequency.QuadPart; +#else + clock_gettime(CLOCK_MONOTONIC, ts); +#endif +} + +/* Handler for SIGHUP, SIGINT, SIGALRM, SIGTERM * * Inside sig_on() (i.e. when cysigs.sig_on_count is positive), this * raises an exception and jumps back to sig_on(). @@ -279,7 +309,7 @@ static void cysigs_interrupt_handler(int sig) if (cysigs.debug_level >= 3) print_backtrace(); /* Store time of this signal, unless there is already a * pending signal. */ - if (!cysigs.interrupt_received) clock_gettime(CLOCK_MONOTONIC, &sigtime); + if (!cysigs.interrupt_received) get_monotonic_time(&sigtime); } #endif @@ -290,8 +320,10 @@ static void cysigs_interrupt_handler(int sig) /* Raise an exception so Python can see it */ do_raise_exception(sig); +#if not HAVE_WINDOWS_H /* Jump back to sig_on() (the first one if there is a stack) */ siglongjmp(trampoline, sig); +#endif } } else @@ -305,7 +337,11 @@ static void cysigs_interrupt_handler(int sig) /* If we are here, we cannot handle the interrupt immediately, so * we store the signal number for later use. But make sure we * don't overwrite a SIGHUP or SIGTERM which we already received. */ - if (cysigs.interrupt_received != SIGHUP && cysigs.interrupt_received != SIGTERM) + if ( +#ifdef SIGHUP + cysigs.interrupt_received != SIGHUP && +#endif + cysigs.interrupt_received != SIGTERM) { cysigs.interrupt_received = sig; custom_set_pending_signal(sig); @@ -322,8 +358,11 @@ static void cysigs_signal_handler(int sig) int inside = cysigs.inside_signal_handler; cysigs.inside_signal_handler = 1; - if (inside == 0 && cysigs.sig_on_count > 0 && sig != SIGQUIT) - { + if (inside == 0 && cysigs.sig_on_count > 0 + #ifdef SIGQUIT + && sig != SIGQUIT + #endif + ) { /* We are inside sig_on(), so we can handle the signal! */ #if ENABLE_DEBUG_CYSIGNALS if (cysigs.debug_level >= 1) { @@ -331,15 +370,16 @@ static void cysigs_signal_handler(int sig) print_stderr_long(sig); print_stderr(" *** inside sig_on\n"); if (cysigs.debug_level >= 3) print_backtrace(); - clock_gettime(CLOCK_MONOTONIC, &sigtime); + get_monotonic_time(&sigtime); } #endif /* Raise an exception so Python can see it */ do_raise_exception(sig); - + #if not HAVE_WINDOWS_H /* Jump back to sig_on() (the first one if there is a stack) */ siglongjmp(trampoline, sig); + #endif } else { @@ -351,7 +391,7 @@ static void cysigs_signal_handler(int sig) } } - +#if not HAVE_WINDOWS_H /* A trampoline to jump to after handling a signal. * * The jump to sig_on() uses cylongjmp(), which does not restore the @@ -435,6 +475,7 @@ static void setup_trampoline(void) cylongjmp(trampoline_setup, 1); } } +#endif /* This calls sig_raise_exception() to actually raise the exception. */ @@ -443,7 +484,7 @@ static void do_raise_exception(int sig) #if ENABLE_DEBUG_CYSIGNALS struct timespec raisetime; if (cysigs.debug_level >= 2) { - clock_gettime(CLOCK_MONOTONIC, &raisetime); + get_monotonic_time(&raisetime); long delta_ms = (raisetime.tv_sec - sigtime.tv_sec)*1000L + (raisetime.tv_nsec - sigtime.tv_nsec)/1000000L; PyGILState_STATE gilstate = PyGILState_Ensure(); print_stderr("do_raise_exception(sig="); @@ -544,6 +585,11 @@ static void setup_alt_stack(void) static void setup_cysignals_handlers(void) { +#ifdef _WIN32 + signal(SIGINT, cysigs_interrupt_handler); + signal(SIGTERM, cysigs_interrupt_handler); + signal(SIGABRT, cysigs_signal_handler); +#else struct sigaction sa; memset(&sa, 0, sizeof(sa)); @@ -571,21 +617,30 @@ static void setup_cysignals_handlers(void) /* Handlers for interrupt-like signals */ sa.sa_handler = cysigs_interrupt_handler; sa.sa_flags = 0; +#ifdef SIGHUP if (sigaction(SIGHUP, &sa, NULL)) {perror("cysignals sigaction"); exit(1);} +#endif if (sigaction(SIGINT, &sa, NULL)) {perror("cysignals sigaction"); exit(1);} +#ifdef SIGALRM if (sigaction(SIGALRM, &sa, NULL)) {perror("cysignals sigaction"); exit(1);} +#endif /* Handlers for critical signals */ sa.sa_handler = cysigs_signal_handler; /* Allow signals during signal handling, we have code to deal with * this case. */ sa.sa_flags = SA_NODEFER | SA_ONSTACK; +#ifdef SIGQUIT if (sigaction(SIGQUIT, &sa, NULL)) {perror("cysignals sigaction"); exit(1);} +#endif if (sigaction(SIGILL, &sa, NULL)) {perror("cysignals sigaction"); exit(1);} if (sigaction(SIGABRT, &sa, NULL)) {perror("cysignals sigaction"); exit(1);} if (sigaction(SIGFPE, &sa, NULL)) {perror("cysignals sigaction"); exit(1);} +#ifdef SIGBUS if (sigaction(SIGBUS, &sa, NULL)) {perror("cysignals sigaction"); exit(1);} +#endif if (sigaction(SIGSEGV, &sa, NULL)) {perror("cysignals sigaction"); exit(1);} +#endif } diff --git a/src/cysignals/signals.pyx b/src/cysignals/signals.pyx index 5b841364..e320cbe7 100644 --- a/src/cysignals/signals.pyx +++ b/src/cysignals/signals.pyx @@ -34,6 +34,10 @@ cimport cython import sys from gc import collect +IF UNAME_SYSNAME == "Windows": + DEF SIGHUP = 1000 + DEF SIGALRM = 1000 + DEF SIGBUS = 1000 cdef extern from "implementation.c": cysigs_t cysigs From de4a617c52016b67c52a318d0515d600d1cfcdad Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Sat, 16 Nov 2024 16:14:18 +0800 Subject: [PATCH 2/9] Fix more tests on Windows --- src/cysignals/signals.pyx | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/cysignals/signals.pyx b/src/cysignals/signals.pyx index e320cbe7..5c76d32d 100644 --- a/src/cysignals/signals.pyx +++ b/src/cysignals/signals.pyx @@ -107,12 +107,15 @@ class AlarmInterrupt(KeyboardInterrupt): EXAMPLES:: + >>> import platform, pytest + >>> if platform.system() == 'Windows': + ... pytest.skip('this doctest does not work on Windows') >>> from cysignals import AlarmInterrupt >>> from signal import alarm + >>> from time import sleep >>> try: ... _ = alarm(1) - ... while True: - ... pass + ... sleep(2) ... except AlarmInterrupt: ... print("alarm!") alarm! @@ -210,7 +213,7 @@ def sig_print_exception(sig, msg=None): >>> import signal >>> sig_print_exception(signal.SIGFPE) FloatingPointError: Floating point exception - >>> sig_print_exception(signal.SIGBUS, "CUSTOM MESSAGE") + >>> sig_print_exception(signal.SIGSEGV, "CUSTOM MESSAGE") cysignals.signals.SignalError: CUSTOM MESSAGE >>> sig_print_exception(0) SystemError: unknown signal number 0 @@ -219,6 +222,9 @@ def sig_print_exception(sig, msg=None): >>> sig_print_exception(signal.SIGINT, "ignored") KeyboardInterrupt + >>> import platform, pytest + >>> if platform.system() == 'Windows': + ... pytest.skip('this doctest does not work on Windows') >>> sig_print_exception(signal.SIGALRM, "ignored") cysignals.signals.AlarmInterrupt @@ -315,6 +321,9 @@ def sig_on_reset(): EXAMPLES:: + >>> import platform, pytest + >>> if platform.system() == 'Windows': + ... pytest.skip('this doctest does not work on Windows') >>> from cysignals.signals import sig_on_reset >>> from cysignals.tests import _sig_on >>> _sig_on(); sig_on_reset() From 322d7285d3a476282f46f0ac0e6a1753f857cbb2 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Sun, 17 Nov 2024 05:28:40 +0000 Subject: [PATCH 3/9] Don't use conditional compilation to handle undefined windows signals --- src/cysignals/signals.pyx | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/cysignals/signals.pyx b/src/cysignals/signals.pyx index 5c76d32d..72f206e3 100644 --- a/src/cysignals/signals.pyx +++ b/src/cysignals/signals.pyx @@ -34,10 +34,19 @@ cimport cython import sys from gc import collect -IF UNAME_SYSNAME == "Windows": - DEF SIGHUP = 1000 - DEF SIGALRM = 1000 - DEF SIGBUS = 1000 +# On Windows, some signals are not pre-defined. +# We define them here with values that will never occur in practice +# (to avoid compilation errors and conditional compilation). +cdef extern from *: + """ + #if defined(_WIN32) || defined(WIN32) || defined(MS_WINDOWS) + #define NO_SUCH_SIGNAL 256 + #define SIGHUP NO_SUCH_SIGNAL + #define SIGALRM NO_SUCH_SIGNAL + #define SIGBUS NO_SUCH_SIGNAL + #endif + """ + pass cdef extern from "implementation.c": cysigs_t cysigs From 8ac3fe41c7d91b08d6861442ddaa0b62da21e985 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Mon, 18 Nov 2024 22:22:29 +0800 Subject: [PATCH 4/9] Run ci also on windows --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fa0b9657..98748ae9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,7 +51,7 @@ concurrency: cancel-in-progress: true jobs: - cygwin-without-sage: + cygwin: runs-on: windows-latest strategy: fail-fast: false @@ -88,7 +88,7 @@ jobs: strategy: fail-fast: false matrix: - os: ['macos-13', 'macos-latest', 'ubuntu-latest'] + os: ['macos-13', 'macos-latest', 'ubuntu-latest', 'windows-latest'] python-version: ['3.10', '3.11', '3.12', '3.13-dev'] steps: - name: Set up the repository From 405aa97603f6820ab5f848fd2f8abb5c5f749dc5 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Mon, 18 Nov 2024 22:34:25 +0800 Subject: [PATCH 5/9] fix compilation --- .github/workflows/ci.yml | 2 +- src/cysignals/cysignals_config.h.in | 43 ----------------------------- src/cysignals/implementation.c | 6 ++-- 3 files changed, 4 insertions(+), 47 deletions(-) delete mode 100644 src/cysignals/cysignals_config.h.in diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 98748ae9..3cc1413f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: Run Sage CI for Linux/Cygwin/macOS +name: CI ## This GitHub Actions workflow provides: ## diff --git a/src/cysignals/cysignals_config.h.in b/src/cysignals/cysignals_config.h.in deleted file mode 100644 index e7143de3..00000000 --- a/src/cysignals/cysignals_config.h.in +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Should cysignals be compiled with support for verbose debugging - * output? The debug level itself needs to be set using the C function - * _set_debug_level() or the Python function set_debug_level(). - * Enabling this will make the code slower, even if the debug level is - * set to 0. - */ -#ifndef ENABLE_DEBUG_CYSIGNALS -#undef ENABLE_DEBUG_CYSIGNALS -#endif - -/* - * Should sig_on() use sigsetjmp(env, 0) instead of setjmp(env)? This - * is needed on BSD and OS X because setjmp() saves the signal mask. - * With glibc, we also use sigsetjmp because it's faster. - */ -#ifndef CYSIGNALS_USE_SIGSETJMP -#undef CYSIGNALS_USE_SIGSETJMP -#endif - - -/* An implementation of atomic variables might not be allowed with OpenMP */ -#undef CYSIGNALS_C_ATOMIC_WITH_OPENMP -#undef CYSIGNALS_CXX_ATOMIC_WITH_OPENMP -#undef CYSIGNALS_STD_ATOMIC_WITH_OPENMP - -/* Which implementation of atomic variables (if any) to use? */ -#ifndef __cplusplus - -#if defined(CYSIGNALS_C_ATOMIC_WITH_OPENMP) || !defined(_OPENMP) -#undef CYSIGNALS_C_ATOMIC -#endif - -#else - -#if defined(CYSIGNALS_CXX_ATOMIC_WITH_OPENMP) || !defined(_OPENMP) -#undef CYSIGNALS_CXX_ATOMIC -#endif -#if defined(CYSIGNALS_STD_ATOMIC_WITH_OPENMP) || !defined(_OPENMP) -#undef CYSIGNALS_STD_ATOMIC -#endif - -#endif diff --git a/src/cysignals/implementation.c b/src/cysignals/implementation.c index 69fb4135..0477f8c3 100644 --- a/src/cysignals/implementation.c +++ b/src/cysignals/implementation.c @@ -391,7 +391,7 @@ static void cysigs_interrupt_handler(int sig) /* Raise an exception so Python can see it */ do_raise_exception(sig); -#if not HAVE_WINDOWS_H +#if !HAVE_WINDOWS_H /* Jump back to sig_on() (the first one if there is a stack) */ siglongjmp(trampoline, sig); #endif @@ -447,7 +447,7 @@ static void cysigs_signal_handler(int sig) /* Raise an exception so Python can see it */ do_raise_exception(sig); - #if not HAVE_WINDOWS_H + #if !HAVE_WINDOWS_H /* Jump back to sig_on() (the first one if there is a stack) */ siglongjmp(trampoline, sig); #endif @@ -462,7 +462,7 @@ static void cysigs_signal_handler(int sig) } } -#if not HAVE_WINDOWS_H +#if !HAVE_WINDOWS_H /* A trampoline to jump to after handling a signal. * * The jump to sig_on() uses cylongjmp(), which does not restore the From c82eaee91e2ee6588db81cd0e8bf5122dbdb3210 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Mon, 18 Nov 2024 22:51:30 +0800 Subject: [PATCH 6/9] Mingw has the fork header but doesn't provide the functionality on windows --- meson.build | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 7784fce6..d66145bc 100644 --- a/meson.build +++ b/meson.build @@ -14,6 +14,7 @@ cxx = meson.get_compiler('cpp') is_windows = host_machine.system() == 'windows' is_cygwin = host_machine.system() == 'cygwin' is_msvc = cc.get_id() == 'msvc' +is_mingw = cc.get_id()=='gcc' and host_machine.system()=='windows' # Set preprocessor macros # Disable .c line numbers in exception tracebacks @@ -45,7 +46,7 @@ config.set('HAVE_TIME_H', cc.has_header('time.h') ? 1 : 0) config.set('HAVE_SYS_WAIT_H', cc.has_header('sys/wait.h') ? 1 : 0) config.set('HAVE_WINDOWS_H', cc.has_header('windows.h') ? 1 : 0) -config.set('HAVE_FORK', cc.has_function('fork') ? 1 : 0) +config.set('HAVE_FORK', (cc.has_function('fork') and not is_mingw) ? 1 : 0) config.set('HAVE_KILL', cc.has_function('kill') ? 1 : 0) config.set('HAVE_SIGPROCMASK', cc.has_function('sigprocmask') ? 1 : 0) config.set('HAVE_SIGALTSTACK', cc.has_function('sigaltstack') ? 1 : 0) From bf7d381330ff44a761cebba2fd7acd61cb54686a Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Mon, 18 Nov 2024 22:59:35 +0800 Subject: [PATCH 7/9] Don't test missing files on windows --- src/conftest.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/conftest.py b/src/conftest.py index 693071c3..9ffb1be5 100755 --- a/src/conftest.py +++ b/src/conftest.py @@ -1,8 +1,17 @@ import pathlib +import platform from _pytest.nodes import Collector from _pytest.doctest import DoctestModule +if platform.system() == "Windows": + collect_ignore = [ + "cysignals/alarm.pyx", + "cysignals/pselect.pyx", + "cysignals/pysignals.pyx", + "cysignals/tests.pyx", + ] + def pytest_collect_file( file_path: pathlib.Path, From e7a151dfa6d3ce03d24337d5e90546c94369999f Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Mon, 18 Nov 2024 23:06:22 +0800 Subject: [PATCH 8/9] fix compilation on cygwin --- src/cysignals/implementation.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cysignals/implementation.c b/src/cysignals/implementation.c index 0477f8c3..cb45e263 100644 --- a/src/cysignals/implementation.c +++ b/src/cysignals/implementation.c @@ -462,7 +462,7 @@ static void cysigs_signal_handler(int sig) } } -#if !HAVE_WINDOWS_H +#if !_WIN32 /* A trampoline to jump to after handling a signal. * * The jump to sig_on() uses cylongjmp(), which does not restore the From 224b24cd302a2251acbb26df7db29dc920515b17 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Mon, 18 Nov 2024 23:59:20 +0800 Subject: [PATCH 9/9] Fix cygwin --- src/cysignals/implementation.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/cysignals/implementation.c b/src/cysignals/implementation.c index cb45e263..20a83d0f 100644 --- a/src/cysignals/implementation.c +++ b/src/cysignals/implementation.c @@ -86,11 +86,9 @@ void custom_set_pending_signal(int sig){ #if HAVE_WINDOWS_H #include -#else +#endif +#if !_WIN32 #include -/* A trampoline to jump to after handling a signal. */ -static cyjmp_buf trampoline_setup; -static sigjmp_buf trampoline; #endif #include "struct_signals.h" @@ -112,6 +110,12 @@ static sigset_t default_sigmask; static sigset_t sigmask_with_sigint; #endif +#if !_WIN32 +/* A trampoline to jump to after handling a signal. */ +static cyjmp_buf trampoline_setup; +static sigjmp_buf trampoline; +#endif + static void setup_cysignals_handlers(void); static void cysigs_interrupt_handler(int sig); static void cysigs_signal_handler(int sig); @@ -391,7 +395,7 @@ static void cysigs_interrupt_handler(int sig) /* Raise an exception so Python can see it */ do_raise_exception(sig); -#if !HAVE_WINDOWS_H +#if !_WIN32 /* Jump back to sig_on() (the first one if there is a stack) */ siglongjmp(trampoline, sig); #endif @@ -447,7 +451,7 @@ static void cysigs_signal_handler(int sig) /* Raise an exception so Python can see it */ do_raise_exception(sig); - #if !HAVE_WINDOWS_H + #if !_WIN32 /* Jump back to sig_on() (the first one if there is a stack) */ siglongjmp(trampoline, sig); #endif