Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cysignals for windows #76

Closed
wants to merge 1 commit into from
Closed

Conversation

vinklein
Copy link

@vinklein vinklein commented Mar 15, 2018

This PR is a proposal for issue #63. For feedback and advices.

The core of this PR is based on cypari fork of cysignals.

Continuous integration is working in four version python versions Python27, Python27-64bits, Python34 and Python34-64bits (build #186 for example).

Some notes / remaining questions :

  • I don't have managed yet to have working with python35 and python36.
  • test_helper.c : signal_pid_after_delay use fork. There is no real equivalent to "fork" in windows. I have not find what the best way to manage this for windows.
  • alarm.pyx: Not translated because SIGALRM doesn't exist on Windows.
  • Error with python 35
  • macro.h : I don't clearly understand the role of win32ctrlc
  • tests.pyx CYSIGNALS_CRASH_QUIET doesn't work yet on windows. Fixed

@jdemeyer jdemeyer changed the title Cysignals for windows. Proposal for issue #63 Cysignals for windows Mar 15, 2018
@jdemeyer
Copy link
Collaborator

One immediate major comment: I don't like code of the form

/* Posix version */
#if !defined(__MINGW32__) && !defined(_WIN32)
....
#else /* Windows version */
....
#endif

where the "POSIX version" and "Windows version" are mostly the same but not really. You really should avoid that.

setup.py Outdated
]

scripts = []

if os.name is not 'nt':
Copy link
Collaborator

@jdemeyer jdemeyer Mar 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very bad. Compare strings with !=

rundoctests.py Outdated
# side effects from doctests.
p = Process(target=testfile, args=(f,))
p.start()
p.join(200)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe to be safer, increase the timeout to 10 minutes or so. And better be explicit and use join(timeout=600). You also need to handle the case of timeout.

void setup_cysignals_handlers() nogil
void print_backtrace() nogil
void _sig_on_interrupt_received() nogil
void _sig_on_recover() nogil
void _sig_off_warning(const char*, int) nogil

IF UNAME_SYSNAME != 'Windows':
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for that. I doesn't hurt to declare functions which do not exist.

elif sig == SIGSEGV:
if msg is NULL:
msg = "Segmentation fault"
PyErr_SetString(SignalError, msg)
else:
raise SystemError(f"unknown signal number {sig}")
IF UNAME_SYSNAME != 'Windows':
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for that.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please be more explicit on this one ?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am just saying that you should try to replace the code

IF UNAME_SYSNAME == 'Windows':
    X
ELSE:
    Y

by

X
Y

(i.e. remove the conditions)

@@ -193,22 +205,23 @@ def init_cysignals():
import signal
old = signal.signal(signal.SIGINT, python_check_interrupt)

setup_alt_stack()
IF UNAME_SYSNAME != 'Windows':
setup_alt_stack()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for that.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one cause an undefined reference to 'setup_alt_stack' if removed

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one cause an undefined reference to 'setup_alt_stack' if removed

Yes of course. But then you just implement setup_alt_stack to do nothing on Windows. This is related to the "major comment" that you should try to implement things as much as possible the same for POSIX and Windows.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok i get the idea. Thx.

feature...
"""
setup_alt_stack()
IF UNAME_SYSNAME != 'Windows':
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for that.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

undefined reference to 'setup_alt_stack' if removed

rundoctests.py Outdated
flag_skip_posix = OPTIONFLAGS_BY_NAME["SKIP_POSIX"]


class WindowsSkipDocTestParser(DocTestParser):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why Windows in the name here? You handle SKIP_WINDOWS as well as SKIP_POSIX.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right. I should have rename after adding SKIP_POSIX.

@jdemeyer
Copy link
Collaborator

I don't have managed yet to have working with python35 and python36.

Can you at least enable tests such that we can follow up on the errors?

@jdemeyer
Copy link
Collaborator

Can you explain why you need all the patches in appveyorutil?

example/setup.py Outdated
# Option "-D_hypot=hypot" is mandatory for mingw64
extensions = [Extension('cysignals_example',
['cysignals_example.pyx'],
extra_compile_args=['-D_hypot=hypot'])]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's up with hypot? The example code doesn't involve hypot at all, so why is this needed?

Copy link
Author

@vinklein vinklein Mar 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

short story :

In file included from c:/mingw-w64/x86_64-7.2.0-posix-seh-rt_v5-rev1/mingw64/lib/gcc/x86_64-w64-mingw32/7.2.0/include/c++/math.h:36:0,
                 from c:\Python27-x64\include/pyport.h:325,
                 from c:\Python27-x64\include/Python.h:58,
                 from cysignals_example.cpp:24:
c:/mingw-w64/x86_64-7.2.0-posix-seh-rt_v5-rev1/mingw64/lib/gcc/x86_64-w64-mingw32/7.2.0/include/c++/cmath:1136:11: error: '::hypot' has not been declared
   using ::hypot;

Long story :
https://ci.appveyor.com/project/vinklein/cysignals/build/1.0.194

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is likely a bug in mingw64.

Can you send me the pre-processed source file (created with -save-temps)?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And maybe also the file c:/mingw-w64/x86_64-7.2.0-posix-seh-rt_v5-rev1/mingw64/lib/gcc/x86_64-w64-mingw32/7.2.0/include/c++/cmath

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's actually a Python bug: python/cpython#880

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In master, I pushed a commit to stop using <math.h> in the example. Hopefully the issue will be gone then.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok nice

Copy link
Author

@vinklein vinklein Mar 20, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know why but it look like <math.h> is still called (https://ci.appveyor.com/project/vinklein/cysignals/build/job/no6rwrud4sojkauv).

@@ -4,7 +4,8 @@ Interrupt and signal handling for Cython

/*****************************************************************************
* Copyright (C) 2006 William Stein <wstein@gmail.com>
* 2006-2016 Martin Albrecht <martinralbrecht+cysignals@gmail.com>
* 2006-2016 Martin Albrecht <martinralbrecht+cysignals@gmail.com
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't change this line

@@ -4,7 +4,8 @@ Interrupt and signal handling for Cython

/*****************************************************************************
* Copyright (C) 2006 William Stein <wstein@gmail.com>
* 2006-2016 Martin Albrecht <martinralbrecht+cysignals@gmail.com>
* 2006-2016 Martin Albrecht <martinralbrecht+cysignals@gmail.com
* 2016 Marc Culler and Nathan Dunfield
* 2010-2018 Jeroen Demeyer <J.Demeyer@UGent.be>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel free to add your own name here

@@ -21,6 +22,23 @@ Interrupt and signal handling for Cython
* along with cysignals. If not, see <http://www.gnu.org/licenses/>.
*
****************************************************************************/
#define ENABLE_DEBUG_CYSIGNALS 0
#if ENABLE_DEBUG_CYSIGNALS
#define DEBUG(...) fprintf(stderr, __VA_ARGS__); fflush(stderr);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't do this. Use cysigs.debug_level instead.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If i understand correctly the different debug_levels are here to indicate how much details we want to be printed rather than a trace category like WARNING, INFO DEBUG ?

Copy link
Collaborator

@jdemeyer jdemeyer Mar 19, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rather than a trace category like WARNING, INFO DEBUG ?

I consider it as various levels of debug information. It's more like DEBUG_A_LITTLE, DEBUG_SOME_MORE and DEBUG_A_LOT.

}

__PYX_EXTERN_C DL_EXPORT(int) sig_raise_exception(int, char const *);
//extern int sig_raise_exception(int sig, const char* msg);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's wrong with this?

@@ -92,8 +92,11 @@ extern "C" {
* if the first returns 0).
*/

#define _sig_on_(message) ( unlikely(_sig_on_prejmp(message, __FILE__, __LINE__)) || _sig_on_postjmp(cysetjmp(cysigs.env)) )

#if defined(__MINGW32__) || defined(_WIN32)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for this. Just #define cysetjmp appropriately.

@jdemeyer
Copy link
Collaborator

Am I understanding correctly that you are still using the same configure script on Windows? That would be good because you could add tests to configure for certain features which are missing on Windows, such as sigaction and sigaltstack. That way, you could use #if HAVE_SIGACTION instead of checking for things like __MINGW32__.

@jdemeyer
Copy link
Collaborator

It would be good to (eventually) port alarm.pyx and pysignals.pyx also to Windows. I think that should not be too hard. But I understand that you don't want to do this right now.

For now, the most important thing is to clean up and reorganize the code. Feel free to write wrapper functions where needed, for example to hide the difference between signal and sigaction (similar to the use of cysetjmp to hide the difference between setjmp and sigsetjmp).

@vinklein
Copy link
Author

Can you explain why you need all the patches in appveyorutil?

You mean in a documentation or here ?
The patchs are for ditsutils to know what compiler to use. Microsoft compiler would be used be default without this.

@jdemeyer
Copy link
Collaborator

The patchs are for ditsutils to know what compiler to use. Microsoft compiler would be used be default without this.

Where did you get those patches from? Is it possible to configure distutils to use the mingw compiler without patching?

The problem with patching on appveyor is that cysignals won't work on real Windows machines, it will only work on appveyor.

@vinklein
Copy link
Author

vinklein commented Mar 16, 2018

I didn't get them form some place i written them based after finding this stackoverflow Python 3.4: compile cython module for 64-bit windows. They are just git diff with vanilla versions of distutils.

The problem with patching on appveyor is that cysignals won't work on real Windows machines, it will only work on appveyor.

This is not really a problem as the patchs are not appveyor specific. The wiki page How-to-install-cysignals-on-Windows is not up to date but the idea is here.

@jdemeyer
Copy link
Collaborator

I didn't get them form some place

I seems to me that your patches come from https://bugs.python.org/issue11723

@jdemeyer
Copy link
Collaborator

It also seems like you should be able to monkey-patch distutils in setup.py to add those compilers. That would be much better than patching distutils itself.

@vinklein
Copy link
Author

I seems to me that your patches come from https://bugs.python.org/issue11723

Yes via the stackoverflow mentioned above. I don't mean i invented the solution.

@vinklein
Copy link
Author

It also seems like you should be able to monkey-patch distutils in setup.py to add those compilers. That would be much better than patching distutils itself.

I will look at it.

# This was discovered at https://github.com/sagemath/cysignals/issues/71
# but I don't know a good analysis nor solution.
DOCTEST = ulimit -s 1024 && $(PYTHON) -B rundoctests.py
ifeq ($(OS),Windows_NT)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just pushed a commit to master which hopefully makes these changes to Makefile unneeded.

Copy link
Author

@vinklein vinklein Mar 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It works for removing LN_SITE_PACKAGE.
But ulimit don't exist on windows.
I will push the rebase after some others tests

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But ulimit don't exist on windows.

OK, so you get an error but the tests continue, right?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will push the rebase after some others tests

Maybe squash your commits. It's not really helpful to have a lot of commits, especially when some just revert earlier commits.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, so you get an error but the tests continue, right?

Yes ulimit don't look to be blocking, but i have another error crashing distcheck with my current rebase.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe instead of doing these kind of hacks, we could just say that one should run make distcheck on Windows. I think that it's fine if we run make distcheck on at least one platform, but not all.

@@ -276,10 +304,21 @@ static inline void sig_error(void)
{
fprintf(stderr, "sig_error() without sig_on()\n");
}
#if defined(__MINGW32__) || defined(_WIN32)
/*
* The Windows abort function will terminate the process no
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the abort() function terminate the process or does the SIGABRT signal abort the process? If only abort() really aborts, then you could use raise(SIGABRT) instead.

fprintf(stderr, "Incremented win32ctrlc to %d\n", win32ctrlc);
}
#endif
return;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't you raise(SIGFPE) here?

sig_atomic_t inside = cysigs.inside_signal_handler;
cysigs.inside_signal_handler = 1;

if (inside == 0 && cysigs.sig_on_count > 0 && sig != SIGQUIT)
#else
int inside = 0; // not used for windows
if(cysigs.sig_on_count > 0)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a particular reason that you are not using inside_signal_handler on Windows?

if(cysigs.debug_level >=1)
fprintf(stderr, "Mapped from %d\n", cysigs.sig_mapped_to_FPE);
#endif
int mapped_sig = cysigs.sig_mapped_to_FPE;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simplify this as sig = cysigs.sig_mapped_to_FPE;

@jdemeyer
Copy link
Collaborator

Looking again at some Windows documentation, I'm not convinced that the sig_mapped_to_FPE machinary is really needed for any signal besides SIGINT. The documentation at https://docs.microsoft.com/nl-nl/cpp/c-runtime-library/reference/signal?view=vs-2017 says

SIGINT is not supported for any Win32 application. When a CTRL+C interrupt occurs, Win32 operating systems generate a new thread to specifically handle that interrupt. This can cause a single-thread application, such as one in UNIX, to become multithreaded and cause unexpected behavior.

but it only says that for SIGINT. And it seems to me that it's not so much about the signal number (SIGINT or SIGFPE or whatever) but more about how it was raised (by pressing CTRL-C, by calling raise(), by an actual floating-point exception occuring).

So maybe you are overcomplicating things with the sig_mapped_to_FPE. You might even be able to just raise(SIGINT) from the SIGINT-handling thread.

@jdemeyer
Copy link
Collaborator

That page also says

Do not use longjmp unless the interrupt is caused by a floating-point exception (that is, sig is SIGFPE).

but it doesn't really say why. For SIGINT, it's clear because another thread is involved. But for the other signals, we don't know why it shouldn't work.

@vinklein
Copy link
Author

vinklein commented Dec 21, 2018

OK, new attempt. Does this work:

diff --git a/example/cysignals_example.pyx b/example/cysignals_example.pyx
index ef3a3b5..414b7a1 100644
--- a/example/cysignals_example.pyx
+++ b/example/cysignals_example.pyx
@@ -1,5 +1,8 @@
 # distutils: language = c++
 # cython: language_level = 3
+#
+# The next line works around https://github.com/python/cpython/pull/880
+# distutils: define_macros = _hypot=hypot
 
 from cysignals.signals cimport sig_check
 from cysignals.memory cimport check_allocarray

I'm asking because this looks like a simpler solution than changing setup.py.

Yes it does work !

@vinklein
Copy link
Author

It seems to me that you are disabling almost all tests on Windows. That's not acceptable. I certainly don't mind to disable the more advanced tests, but basic things like handling SIGINT should still be tested.

The most of disabled tests seems to be that way because of signal_after_delay or signals_after_delay usage. Since i now have an implementation for signal_after_delay, i will try to enable the one using this function.

@vinklein
Copy link
Author

vinklein commented Jan 7, 2019

I suggest this to deal with the sigsetjmp compatibility:

diff --git a/src/cysignals/implementation.c b/src/cysignals/implementation.c
index a422f62..be904f9 100644
--- a/src/cysignals/implementation.c
+++ b/src/cysignals/implementation.c
@@ -83,6 +83,11 @@ static sigset_t default_sigmask;
 
 /* default_sigmask with SIGHUP, SIGINT, SIGALRM added. */
 static sigset_t sigmask_with_sigint;
+#else
+/* If we don't have sigprocmask(), assume that we don't have siglongjmp() either */
+#define sigjmp_buf jmp_buf
+#define sigsetjmp(env, savesigs) setjmp(env)
+#define siglongjmp longjmp
 #endif
 
 /* A trampoline to jump to after handling a signal. */

Currently (in windows_dev branch) siglongjmp and sigjmp_buf are defined in struct_signals.h do you think it is more consistent to have that in implementation.c ?

@jdemeyer
Copy link
Collaborator

jdemeyer commented Jan 7, 2019

Currently (in windows_dev branch) siglongjmp and sigjmp_buf are defined in struct_signals.h do you think it is more consistent to have that in implementation.c ?

I would rather not have it in struct_signals.h since that's an installed header. So it would affect everything using cysignals.

@vinklein
Copy link
Author

vinklein commented Jan 7, 2019

General comment: is there a particular reason that you are not using the trampoline jump? I can understand that this is not really required on Windows, but for consistency it would be good to use it.

setup_trampoline use pthread_attr_setstack which doesn't exist on windows system, i am looking for a workaround (https://stackoverflow.com/questions/35962507/can-i-create-a-thread-with-its-stack-at-a-specific-address).

@jdemeyer
Copy link
Collaborator

jdemeyer commented Jan 9, 2019

setup_trampoline use pthread_attr_setstack which doesn't exist on windows system, i am looking for a workaround (https://stackoverflow.com/questions/35962507/can-i-create-a-thread-with-its-stack-at-a-specific-address).

Just commenting here: the reason that I'm using pthread_attr_setstack is because the thread stack should not be deallocated when the thread exists. If there is any way in Windows to keep the thread stack alive after the thread exists, that would be fine too.

@vinklein
Copy link
Author

Rebase on 582dbf6 and many modifications :

Some remaining issues:

  • Having a working signal_after_delay implementation on windows. I am still trying to do that. The firsts implementation tested is using thread to raise the signal. For now i have inconsistent results :
    raise(SIGINT) by a test program do what is expected but with signal_after_delay it's like if nothing is called.
    Concerning SIGABRT
>>> from cysignals.tests import *
>>> test_abort

works on windows but running test_signal_abrt() will stop the python process. In theory raise(SIGABRT) is called in both cases.

  • siglongjmp and trampoline usage instead of reset_CPU/longjmp (pthread_attr_setstack issue).
  • win32ctrlc usefulness ?
  • Try to run make distcheck on at least one platform but not all in order to remove the ulimit -s 1024 trick

@vinklein
Copy link
Author

Does Windows have anything like the kill or pthread_kill function? Is there a way to send a signal to a different process or different thread? I am trying to write a portable version of signal_pid_after_delay but I need to know what Windows supports.

For what i know there is no clean way to do that (look at terminateprocess and terminatethread), the existing functions can cause resources to not be released.

@vinklein
Copy link
Author

  • Rebase on 50ba45a (Wich resolve the signal_after_delay issue)
  • Map SIGHUP, SIGBUS, SIGALRM, SIGQUIT with existing signals.
  • Remove +SKIP_WINDOWS for tests using signal_after_delay with windows compatible signals
  • Remove kill_thread_group declaration

@vinklein
Copy link
Author

One point to clarify : running test_sig_retry_and_signal currently provoke a timeout on windows.

@@ -165,19 +165,19 @@ def interrupt_after_delay(ms_delay=500):
"""
signal_after_delay(SIGINT, ms_delay)

IF UNAME_SYSNAME != 'Windows':
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not be needed anymore

@@ -639,7 +639,14 @@ def unguarded_abort():
We run Python in a subprocess and make it call abort()::

>>> from cysignals.tests import subpython_err
>>> subpython_err('from cysignals.tests import *; unguarded_abort()')
>>> subpython_err('from cysignals.tests import *; unguarded_abort()') # doctest: +SKIP_WINDOWS
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this test duplicated?

@jdemeyer
Copy link
Collaborator

* Fix indentation and whitespace errors.

You are still introducing various needless whitespace changes, please have a look at the diff.

@vinklein
Copy link
Author

vinklein commented Jan 16, 2019

* Fix indentation and whitespace errors.

You are still introducing various needless whitespace changes, please have a look at the diff.

Yes. Some have already been fixed under vinklein:appveyor_test branch

Modify cysignals for windows compatibility
@jdemeyer
Copy link
Collaborator

For easier testing, I copied this to a branch windows in cysignals. See #104.

@jdemeyer jdemeyer closed this Feb 12, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants