Commit e18adbab authored by Kirill Smelkov's avatar Kirill Smelkov

Nogil signals

Provide os/signal package that can be used to setup signal delivery to nogil
channels. This way for user code signal handling becomes regular handling of a
signalling channel instead of being something special or limited to only-main
python thread. The rationale for why we need it is explained below:

There are several problems with regular python's stdlib signal module:

1. Python2 does not call signal handler from under blocked lock.acquire.
   This means that if the main thread is blocked waiting on a semaphore,
   signal delivery will be delayed indefinitely, similarly to e.g. problem
   described in nxdtest!14 (comment 147527)
   where raising KeyboardInterrupt is delayed after SIGINT for many,
   potentially unbounded, seconds until ~semaphore wait finishes.

   Note that Python3 does not have this problem wrt stdlib locks and
   semaphores, but read below for the next point.

2. all pygolang communication operations (channels send/recv, sync.Mutex,
   sync.RWMutex, sync.Sema, sync.WaitGroup, sync.WorkGroup, ...) run with
   GIL released, but if blocked do not handle EINTR and do not schedule
   python signal handler to run (on main thread).

   Even if we could theoretically adjust this behaviour of pygolang at python
   level to match Python3, there are also C++ and pyx/nogil worlds. And we want gil
   and nogil worlds to interoperate (see https://pypi.org/project/pygolang/#cython-nogil-api),
   so that e.g. if completely nogil code happens to run on the main thread,
   signal handling is still possible, even if that signal handling was setup at
   python level.

With signals delivered to nogil channels both nogil world and python
world can setup signal handlers and to be notified of them irregardles
of whether main python thread is currently blocked in nogil wait or not.

/reviewed-on !17
parent ce507f4e
Pipeline #19469 passed with stage
in 0 seconds
...@@ -23,6 +23,8 @@ include golang/io.h ...@@ -23,6 +23,8 @@ include golang/io.h
include golang/io.cpp include golang/io.cpp
include golang/os.h include golang/os.h
include golang/os.cpp include golang/os.cpp
include golang/os/signal.h
include golang/os/signal.cpp
include golang/strings.h include golang/strings.h
include golang/strings.cpp include golang/strings.cpp
include golang/strings_test.cpp include golang/strings_test.cpp
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
/_golang.cpp /_golang.cpp
/_golang_test.cpp /_golang_test.cpp
/_io.cpp /_io.cpp
/_os.cpp
/_os_test.cpp /_os_test.cpp
/_strings_test.cpp /_strings_test.cpp
/_sync.cpp /_sync.cpp
......
...@@ -191,6 +191,7 @@ cpdef pypanic(arg) ...@@ -191,6 +191,7 @@ cpdef pypanic(arg)
# pychan is python wrapper over chan<object> or chan<structZ|bool|int|double|...> # pychan is python wrapper over chan<object> or chan<structZ|bool|int|double|...>
from cython cimport final from cython cimport final
from golang cimport os
# DType describes type of channel elements. # DType describes type of channel elements.
# TODO consider supporting NumPy dtypes too. # TODO consider supporting NumPy dtypes too.
...@@ -200,7 +201,8 @@ cdef enum DType: ...@@ -200,7 +201,8 @@ cdef enum DType:
DTYPE_BOOL = 2 # chan[bool] DTYPE_BOOL = 2 # chan[bool]
DTYPE_INT = 3 # chan[int] DTYPE_INT = 3 # chan[int]
DTYPE_DOUBLE = 4 # chan[double] DTYPE_DOUBLE = 4 # chan[double]
DTYPE_NTYPES = 5 DTYPE_OS_SIGNAL = 5 # chan[os::Signal] TODO register dynamically
DTYPE_NTYPES = 6
# pychan wraps a channel into python object. # pychan wraps a channel into python object.
# #
...@@ -226,6 +228,8 @@ cdef class pychan: ...@@ -226,6 +228,8 @@ cdef class pychan:
chan[cbool] chan_bool (pychan pych) chan[cbool] chan_bool (pychan pych)
chan[int] chan_int (pychan pych) chan[int] chan_int (pychan pych)
chan[double] chan_double (pychan pych) chan[double] chan_double (pychan pych)
# TODO move vvv out of pychan after dtypes are registered dynamically
chan[os.Signal] _chan_osSignal (pychan pych)
# pychan.from_chan_X returns pychan wrapping pyx/nogil-level chan[X]. # pychan.from_chan_X returns pychan wrapping pyx/nogil-level chan[X].
# X can be any C-level type, but not PyObject. # X can be any C-level type, but not PyObject.
...@@ -237,6 +241,9 @@ cdef class pychan: ...@@ -237,6 +241,9 @@ cdef class pychan:
cdef pychan from_chan_int (chan[int] ch) cdef pychan from_chan_int (chan[int] ch)
@staticmethod @staticmethod
cdef pychan from_chan_double (chan[double] ch) cdef pychan from_chan_double (chan[double] ch)
# TODO move vvv out of pychan after dtypes are registered dynamically
@staticmethod
cdef pychan _from_chan_osSignal (chan[os.Signal] ch)
# pyerror wraps an error into python object. # pyerror wraps an error into python object.
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
# cython: binding=False # cython: binding=False
# cython: c_string_type=str, c_string_encoding=utf8 # cython: c_string_type=str, c_string_encoding=utf8
# distutils: language = c++ # distutils: language = c++
# distutils: depends = libgolang.h # distutils: depends = libgolang.h os/signal.h
# #
# Copyright (C) 2018-2022 Nexedi SA and Contributors. # Copyright (C) 2018-2022 Nexedi SA and Contributors.
# Kirill Smelkov <kirr@nexedi.com> # Kirill Smelkov <kirr@nexedi.com>
...@@ -44,6 +44,8 @@ cdef extern from "Python.h": ...@@ -44,6 +44,8 @@ cdef extern from "Python.h":
from libcpp.vector cimport vector from libcpp.vector cimport vector
from cython cimport final from cython cimport final
from golang cimport os # TODO remove after dtypes are reworked to register dynamically
import sys import sys
# ---- panic ---- # ---- panic ----
...@@ -338,6 +340,10 @@ cdef class pychan: ...@@ -338,6 +340,10 @@ cdef class pychan:
pychan_asserttype(pych, DTYPE_DOUBLE) pychan_asserttype(pych, DTYPE_DOUBLE)
return _wrapchan[double] (pych._ch) return _wrapchan[double] (pych._ch)
chan[os.Signal] _chan_osSignal (pychan pych):
pychan_asserttype(pych, DTYPE_OS_SIGNAL)
return _wrapchan[os.Signal] (pych._ch)
# pychan <- chan[X] # pychan <- chan[X]
@staticmethod @staticmethod
cdef pychan from_chan_structZ (chan[structZ] ch): cdef pychan from_chan_structZ (chan[structZ] ch):
...@@ -355,6 +361,10 @@ cdef class pychan: ...@@ -355,6 +361,10 @@ cdef class pychan:
cdef pychan from_chan_double (chan[double] ch): cdef pychan from_chan_double (chan[double] ch):
return pychan_from_raw(ch._rawchan(), DTYPE_DOUBLE) return pychan_from_raw(ch._rawchan(), DTYPE_DOUBLE)
@staticmethod
cdef pychan _from_chan_osSignal (chan[os.Signal] ch):
return pychan_from_raw(ch._rawchan(), DTYPE_OS_SIGNAL)
cdef void pychan_asserttype(pychan pych, DType dtype) nogil: cdef void pychan_asserttype(pychan pych, DType dtype) nogil:
if pych.dtype != dtype: if pych.dtype != dtype:
panic("pychan: channel type mismatch") panic("pychan: channel type mismatch")
...@@ -741,6 +751,26 @@ cdef object double_c_to_py(const chanElemBuf *cfrom): ...@@ -741,6 +751,26 @@ cdef object double_c_to_py(const chanElemBuf *cfrom):
return (<double *>cfrom)[0] return (<double *>cfrom)[0]
# DTYPE_OS_SIGNAL
# TODO make dtype registration dynamic and move to _os.pyx
dtypeRegistry[<int>DTYPE_OS_SIGNAL] = DTypeInfo(
name = "C.os::Signal",
size = sizeof(os.Signal),
py_to_c = ossig_py_to_c,
c_to_py = ossig_c_to_py,
pynil = mkpynil(DTYPE_OS_SIGNAL),
)
cdef bint ossig_py_to_c(object obj, chanElemBuf *cto) except False:
if not isinstance(obj, os.PySignal):
raise TypeError("type mismatch: expect os.PySignal; got %r" % (obj,))
(<os.Signal*>cto)[0] = (<os.PySignal>obj).sig
cdef object ossig_c_to_py(const chanElemBuf *cfrom):
sig = (<os.Signal*>cfrom)[0]
return os.PySignal.from_sig(sig)
# verify at init time that sizeof(chanElemBuf) = max(_.size) # verify at init time that sizeof(chanElemBuf) = max(_.size)
cdef verify_chanElemBuf(): cdef verify_chanElemBuf():
cdef int size_max = 0 cdef int size_max = 0
...@@ -764,9 +794,16 @@ cdef DType parse_dtype(dtype) except <DType>-1: ...@@ -764,9 +794,16 @@ cdef DType parse_dtype(dtype) except <DType>-1:
return DTYPE_PYOBJECT return DTYPE_PYOBJECT
_ = name2dtype.get(dtype) _ = name2dtype.get(dtype)
if _ is None: if _ is not None:
raise TypeError("pychan: invalid dtype: %r" % (dtype,)) return _
return _
# accept classes with .dtype attribute
if hasattr(dtype, "dtype"):
_ = name2dtype.get(dtype.dtype)
if _ is not None:
return _
raise TypeError("pychan: invalid dtype: %r" % (dtype,))
# ---- strings ---- # ---- strings ----
......
# cython: language_level=2
# Copyright (C) 2021-2022 Nexedi SA and Contributors.
# Kirill Smelkov <kirr@nexedi.com>
#
# This program is free software: you can Use, Study, Modify and Redistribute
# it under the terms of the GNU General Public License version 3, or (at your
# option) any later version, as published by the Free Software Foundation.
#
# You can also Link and Combine this program with other software covered by
# the terms of any of the Free Software licenses or any of the Open Source
# Initiative approved licenses and Convey the resulting work. Corresponding
# source of such a combination shall include the source code for all other
# software used.
#
# This program is distributed WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# See COPYING file for full licensing terms.
# See https://www.nexedi.com/licensing for rationale and options.
"""Package os mirrors Go package os.
- `Signal` represents OS-level signal.
See also https://golang.org/pkg/os for Go os package documentation.
"""
#from golang cimport string # TODO restore after golang.pyx stops to import os.pyx
from libcpp.string cimport string # golang::string = std::string TODO remove after ^^^
cdef extern from "golang/os.h" namespace "golang::os" nogil:
struct Signal:
int signo
string String()
Signal _Signal_from_int(int signo)
# ---- python bits ----
from cython cimport final
@final
cdef class PySignal:
cdef Signal sig
# PySignal.from_sig returns PySignal wrapping py/nogil-level Signal sig
@staticmethod
cdef PySignal from_sig(Signal sig)
# -*- coding: utf-8 -*-
# cython: language_level=2
# Copyright (C) 2021-2022 Nexedi SA and Contributors.
# Kirill Smelkov <kirr@nexedi.com>
#
# This program is free software: you can Use, Study, Modify and Redistribute
# it under the terms of the GNU General Public License version 3, or (at your
# option) any later version, as published by the Free Software Foundation.
#
# You can also Link and Combine this program with other software covered by
# the terms of any of the Free Software licenses or any of the Open Source
# Initiative approved licenses and Convey the resulting work. Corresponding
# source of such a combination shall include the source code for all other
# software used.
#
# This program is distributed WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# See COPYING file for full licensing terms.
# See https://www.nexedi.com/licensing for rationale and options.
"""_os.pyx implements os.pyx - see _os.pxd for package overview."""
from __future__ import print_function, absolute_import
from golang cimport __pystr
from cython cimport final
# Signal represents an OS signal.
@final
cdef class PySignal:
dtype = "C.os::Signal"
@staticmethod
cdef PySignal from_sig(Signal sig):
cdef PySignal pysig = PySignal.__new__(PySignal)
pysig.sig = sig
return pysig
property signo:
def __get__(PySignal pysig):
return pysig.sig.signo
def __str__(PySignal pysig):
return __pystr(pysig.sig.String())
def __repr__(PySignal pysig):
return ("os.Signal(%d)" % pysig.sig.signo)
# PySignal == PySignal
def __hash__(PySignal pysig):
return <Py_hash_t>pysig.sig.signo
# NOTE __ne__ not needed: PySignal does not have base class and for that
# case cython automatically generates __ne__ based on __eq__.
def __eq__(PySignal a, object rhs):
if not isinstance(rhs, PySignal):
return False
cdef PySignal b = rhs
return (a.sig == b.sig)
def _PySignal_from_int(int signo): # -> PySignal
return PySignal.from_sig(_Signal_from_int(signo))
...@@ -31,6 +31,17 @@ ...@@ -31,6 +31,17 @@
#include <unistd.h> #include <unistd.h>
#include <string.h> #include <string.h>
#include <signal.h>
// GLIBC >= 2.32 provides sigdescr_np but not sys_siglist in its headers
// GLIBC < 2.32 provides sys_siglist but not sigdescr_np in its headers
// cut this short
// (on darwing sys_siglist declaration is normally provided)
#ifndef __APPLE__
extern "C" {
extern const char * const sys_siglist[];
}
#endif
using golang::internal::_runtime; using golang::internal::_runtime;
namespace sys = golang::internal::syscall; namespace sys = golang::internal::syscall;
...@@ -237,4 +248,24 @@ static error _pathError(const char *op, const string &path, error err) { ...@@ -237,4 +248,24 @@ static error _pathError(const char *op, const string &path, error err) {
return fmt::errorf("%s %s: %w", op, path.c_str(), err); return fmt::errorf("%s %s: %w", op, path.c_str(), err);
} }
string Signal::String() const {
const Signal& sig = *this;
const char *sigstr = nil;
if (0 <= sig.signo && sig.signo < NSIG)
sigstr = ::sys_siglist[sig.signo]; // might be nil as well
if (sigstr != nil)
return string(sigstr);
return fmt::sprintf("signal%d", sig.signo);
}
Signal _Signal_from_int(int signo) {
Signal sig;
sig.signo = signo;
return sig;
}
}} // golang::os:: }} // golang::os::
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
// - `Open` opens file @path. // - `Open` opens file @path.
// - `Pipe` creates new pipe. // - `Pipe` creates new pipe.
// - `NewFile` wraps OS-level file-descriptor into File. // - `NewFile` wraps OS-level file-descriptor into File.
// - `Signal` represents OS-level signal.
// //
// See also https://golang.org/pkg/os for Go os package documentation. // See also https://golang.org/pkg/os for Go os package documentation.
...@@ -104,6 +105,37 @@ LIBGOLANG_API std::tuple<File, error> NewFile(int sysfd, const string& name); ...@@ -104,6 +105,37 @@ LIBGOLANG_API std::tuple<File, error> NewFile(int sysfd, const string& name);
// Pipe creates connected pair of files. // Pipe creates connected pair of files.
LIBGOLANG_API std::tuple</*r*/File, /*w*/File, error> Pipe(); LIBGOLANG_API std::tuple</*r*/File, /*w*/File, error> Pipe();
// Signal represents an OS signal.
//
// NOTE in Go os.Signal is interface while in pygolang os::Signal is concrete structure.
struct Signal {
int signo;
// String returns human-readable signal text.
LIBGOLANG_API string String() const;
// Signal == Signal
inline bool operator==(const Signal& sig2) const { return (signo == sig2.signo); }
inline bool operator!=(const Signal& sig2) const { return (signo != sig2.signo); }
};
// _Signal_from_int creates Signal from integer, for example from SIGINT.
LIBGOLANG_API Signal _Signal_from_int(int signo);
}} // golang::os:: }} // golang::os::
// std::
namespace std {
// std::hash<Signal>
template<> struct hash<golang::os::Signal> {
std::size_t operator()(const golang::os::Signal& sig) const noexcept {
return hash<int>()(sig.signo);
}
};
} // std::
#endif // _NXD_LIBGOLANG_OS_H #endif // _NXD_LIBGOLANG_OS_H
# cython: language_level=2
# Copyright (C) 2021-2022 Nexedi SA and Contributors.
# Kirill Smelkov <kirr@nexedi.com>
#
# This program is free software: you can Use, Study, Modify and Redistribute
# it under the terms of the GNU General Public License version 3, or (at your
# option) any later version, as published by the Free Software Foundation.
#
# You can also Link and Combine this program with other software covered by
# the terms of any of the Free Software licenses or any of the Open Source
# Initiative approved licenses and Convey the resulting work. Corresponding
# source of such a combination shall include the source code for all other
# software used.
#
# This program is distributed WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# See COPYING file for full licensing terms.
# See https://www.nexedi.com/licensing for rationale and options.
"""Package os mirrors and amends Go package os.
See _os.pxd for package documentation.
"""
# redirect cimport: golang.os -> golang._os (see __init__.pxd for rationale)
from golang._os cimport *
# -*- coding: utf-8 -*-
# Copyright (C) 2021-2022 Nexedi SA and Contributors.
# Kirill Smelkov <kirr@nexedi.com>
#
# This program is free software: you can Use, Study, Modify and Redistribute
# it under the terms of the GNU General Public License version 3, or (at your
# option) any later version, as published by the Free Software Foundation.
#
# You can also Link and Combine this program with other software covered by
# the terms of any of the Free Software licenses or any of the Open Source
# Initiative approved licenses and Convey the resulting work. Corresponding
# source of such a combination shall include the source code for all other
# software used.
#
# This program is distributed WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# See COPYING file for full licensing terms.
# See https://www.nexedi.com/licensing for rationale and options.
"""Package os mirrors Go package os.
- `Signal` represents OS-level signal.
See also https://golang.org/pkg/os for Go os package documentation.
"""
from __future__ import print_function, absolute_import
from golang._os import \
PySignal as Signal
# cython: language_level=2
# Copyright (C) 2021-2022 Nexedi SA and Contributors.
# Kirill Smelkov <kirr@nexedi.com>
#
# This program is free software: you can Use, Study, Modify and Redistribute
# it under the terms of the GNU General Public License version 3, or (at your
# option) any later version, as published by the Free Software Foundation.
#
# You can also Link and Combine this program with other software covered by
# the terms of any of the Free Software licenses or any of the Open Source
# Initiative approved licenses and Convey the resulting work. Corresponding
# source of such a combination shall include the source code for all other
# software used.
#
# This program is distributed WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# See COPYING file for full licensing terms.
# See https://www.nexedi.com/licensing for rationale and options.
"""Package signal mirrors Go package signal.
- `Notify` arranges for signals to be delivered to channels.
- `Stop` unsubscribes a channel from signal delivery.
- `Ignore` requests signals to be ignored.
- `Reset` requests signals to be handled as by default.
See also https://golang.org/pkg/os/signal for Go signal package documentation.
"""
from golang cimport chan
from golang cimport os
from libcpp.vector cimport vector
cdef extern from "golang/os/signal.h" namespace "golang::os::signal" nogil:
void Notify(chan[os.Signal] ch, vector[os.Signal] sigv)
void Stop(chan[os.Signal] ch)
void Ignore(vector[os.Signal] sigv)
void Reset(vector[os.Signal] sigv)
# -*- coding: utf-8 -*-
# cython: language_level=2
# Copyright (C) 2021-2022 Nexedi SA and Contributors.
# Kirill Smelkov <kirr@nexedi.com>
#
# This program is free software: you can Use, Study, Modify and Redistribute
# it under the terms of the GNU General Public License version 3, or (at your
# option) any later version, as published by the Free Software Foundation.
#
# You can also Link and Combine this program with other software covered by
# the terms of any of the Free Software licenses or any of the Open Source
# Initiative approved licenses and Convey the resulting work. Corresponding
# source of such a combination shall include the source code for all other
# software used.
#
# This program is distributed WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# See COPYING file for full licensing terms.
# See https://www.nexedi.com/licensing for rationale and options.
"""_signal.pyx implements signal.pyx - see _signal.pxd for package overview."""
from __future__ import print_function, absolute_import
from golang cimport pychan, topyexc
from golang cimport os
from libc.signal cimport SIGINT
from libcpp.vector cimport vector
# adjust Python behaviour to not raise KeyboardInterrupt on SIGINT by default
# if any Notify(SIGINT) is currently active.
cdef set _pyint_notifying = set() # {pychan}
cdef _pydefault_int_handler(sig, frame):
# with gil:
if 1:
if len(_pyint_notifying) != 0:
return
raise KeyboardInterrupt
import signal as pysignal
if pysignal.getsignal(SIGINT) is pysignal.default_int_handler:
pysignal.default_int_handler = _pydefault_int_handler
pysignal.signal(SIGINT, _pydefault_int_handler)
def PyNotify(pychan pych, *pysigv):
cdef chan[os.Signal] ch = pychan_osSignal_pyexc(pych)
sigv, has_sigint = _unwrap_pysigv(pysigv)
# with gil:
if 1:
if has_sigint:
_pyint_notifying.add(pych)
_Notify_pyexc(ch, sigv)
def PyStop(pychan pych):
cdef chan[os.Signal] ch = pychan_osSignal_pyexc(pych)
# with gil:
if 1:
try:
_pyint_notifying.remove(pych)
except KeyError:
pass
_Stop_pyexc(ch)
def PyIgnore(*pysigv):
sigv, has_sigint = _unwrap_pysigv(pysigv)
# with gil:
if 1:
if has_sigint:
_pyint_notifying.clear()
_Ignore_pyexc(sigv)
def PyReset(*pysigv):
sigv, has_sigint = _unwrap_pysigv(pysigv)
# with gil:
if 1:
if has_sigint:
_pyint_notifying.clear()
_Reset_pyexc(sigv)
# _unwrap_pysigv converts pysigv to sigv.
cdef (vector[os.Signal], bint) _unwrap_pysigv(pysigv) except *: # (sigv, has_sigint)
cdef vector[os.Signal] sigv
cdef bint has_sigint = (len(pysigv) == 0) # if all signals
for xpysig in pysigv:
if not isinstance(xpysig, os.PySignal):
raise TypeError("expect os.Signal, got %r" % (xpysig,))
pysig = <os.PySignal>xpysig
sigv.push_back(pysig.sig)
if pysig.sig.signo == SIGINT:
has_sigint = True
return (sigv, has_sigint)
cdef:
chan[os.Signal] pychan_osSignal_pyexc(pychan pych) except +topyexc:
return pych._chan_osSignal()
void _Notify_pyexc(chan[os.Signal] ch, const vector[os.Signal]& sigv) except +topyexc:
Notify(ch, sigv)
void _Stop_pyexc(chan[os.Signal] ch) except +topyexc:
Stop(ch)
void _Ignore_pyexc(const vector[os.Signal]& sigv) except +topyexc:
Ignore(sigv)
void _Reset_pyexc(const vector[os.Signal]& sigv) except +topyexc:
Reset(sigv)
This diff is collapsed.
#ifndef _NXD_LIBGOLANG_OS_SIGNAL_H
#define _NXD_LIBGOLANG_OS_SIGNAL_H
//
// Copyright (C) 2021-2022 Nexedi SA and Contributors.
// Kirill Smelkov <kirr@nexedi.com>
//
// This program is free software: you can Use, Study, Modify and Redistribute
// it under the terms of the GNU General Public License version 3, or (at your
// option) any later version, as published by the Free Software Foundation.
//
// You can also Link and Combine this program with other software covered by
// the terms of any of the Free Software licenses or any of the Open Source
// Initiative approved licenses and Convey the resulting work. Corresponding
// source of such a combination shall include the source code for all other
// software used.
//
// This program is distributed WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//
// See COPYING file for full licensing terms.
// See https://www.nexedi.com/licensing for rationale and options.
// Package signal mirrors Go package signal.
//
// - `Notify` arranges for signals to be delivered to channels.
// - `Stop` unsubscribes a channel from signal delivery.
// - `Ignore` requests signals to be ignored.
// - `Reset` requests signals to be handled as by default.
//
// See also https://golang.org/pkg/os/signal for Go signal package documentation.
#include <golang/libgolang.h>
#include <golang/os.h>
#include <initializer_list>
#include <vector>
// golang::os::signal::
namespace golang {
namespace os {
namespace signal {
// Notify requests that specified signals, when received, are sent to channel ch.
//
// The sending will be done in non-blocking way. If, at the moment of signal
// reception, the channel is full and not being received-from, the signal won't
// be delivered.
//
// If the list of specified signals is empty, it means "all signals".
LIBGOLANG_API void Notify(chan<os::Signal> ch, const std::initializer_list<os::Signal>& sigv);
LIBGOLANG_API void Notify(chan<os::Signal> ch, const std::vector<os::Signal>& sigv);
// Stop undoes the effect of all previous calls to Notify with specified channel.
//
// After Stop completes, no more signals will be delivered to ch.
LIBGOLANG_API void Stop(chan<os::Signal> ch);
// Ignore requests specified signals to be ignored by the program.
//
// In particular it undoes the effect of all previous calls to Notify with
// specified signals.
//
// After Ignore completes specified signals won't be delivered to any channel.
//
// If the list of specified signals is empty, it means "all signals".
LIBGOLANG_API void Ignore(const std::initializer_list<os::Signal>& sigv);
LIBGOLANG_API void Ignore(const std::vector<os::Signal>& sigv);
// Reset resets specified signals to be handled as by default.
//
// In particular it undoes the effect of all previous calls to Notify with
// specified signals.
//
// After Reset completes specified signals won't be delivered to any channel.
//
// If the list of specified signals is empty, it means "all signals".
LIBGOLANG_API void Reset(const std::initializer_list<os::Signal>& sigv);
LIBGOLANG_API void Reset(const std::vector<os::Signal>& sigv);
}}} // golang::os::signal::
#endif // _NXD_LIBGOLANG_OS_SIGNAL_H
# cython: language_level=2
# Copyright (C) 2021-2022 Nexedi SA and Contributors.
# Kirill Smelkov <kirr@nexedi.com>
#
# This program is free software: you can Use, Study, Modify and Redistribute
# it under the terms of the GNU General Public License version 3, or (at your
# option) any later version, as published by the Free Software Foundation.
#
# You can also Link and Combine this program with other software covered by
# the terms of any of the Free Software licenses or any of the Open Source
# Initiative approved licenses and Convey the resulting work. Corresponding
# source of such a combination shall include the source code for all other
# software used.
#
# This program is distributed WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# See COPYING file for full licensing terms.
# See https://www.nexedi.com/licensing for rationale and options.
"""Package signal mirrors and Go package signal.
See _signal.pxd for package documentation.
"""
# redirect cimport: golang.os.signal -> golang.os._signal (see __init__.pxd for rationale)
from golang.os._signal cimport *
# -*- coding: utf-8 -*-
# Copyright (C) 2021-2022 Nexedi SA and Contributors.
# Kirill Smelkov <kirr@nexedi.com>
#
# This program is free software: you can Use, Study, Modify and Redistribute
# it under the terms of the GNU General Public License version 3, or (at your
# option) any later version, as published by the Free Software Foundation.
#
# You can also Link and Combine this program with other software covered by
# the terms of any of the Free Software licenses or any of the Open Source
# Initiative approved licenses and Convey the resulting work. Corresponding
# source of such a combination shall include the source code for all other
# software used.
#
# This program is distributed WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# See COPYING file for full licensing terms.
# See https://www.nexedi.com/licensing for rationale and options.
"""Package signal mirrors Go package signal.
- `Notify` arranges for signals to be delivered to channels.
- `Stop` unsubscribes a channel from signal delivery.
- `Ignore` requests signals to be ignored.
- `Reset` requests signals to be handled as by default.
See also https://golang.org/pkg/os/signal for Go signal package documentation.
"""
from __future__ import print_function, absolute_import
from golang.os._signal import \
PyNotify as Notify, \
PyStop as Stop, \
PyIgnore as Ignore, \
PyReset as Reset
This diff is collapsed.
#!/usr/bin/env python
# Copyright (C) 2021-2022 Nexedi SA and Contributors.
# Kirill Smelkov <kirr@nexedi.com>
#
# This program is free software: you can Use, Study, Modify and Redistribute
# it under the terms of the GNU General Public License version 3, or (at your
# option) any later version, as published by the Free Software Foundation.
#
# You can also Link and Combine this program with other software covered by
# the terms of any of the Free Software licenses or any of the Open Source
# Initiative approved licenses and Convey the resulting work. Corresponding
# source of such a combination shall include the source code for all other
# software used.
#
# This program is distributed WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# See COPYING file for full licensing terms.
# See https://www.nexedi.com/licensing for rationale and options.
"""This program verifies signal.Notify(), Ignore() and Reset() with 'all signals' argument."""
from __future__ import print_function, absolute_import
from golang import chan
from golang import os as gos, syscall, time
from golang.os import signal
import os, sys
def main():
# build "all signals" list
allsigv = []
for attr in dir(syscall):
if attr.startswith("SIG") and "_" not in attr:
sig = getattr(syscall, attr)
if sig not in allsigv: # avoid e.g. SIGCHLD/SIGCLD dups
allsigv.append(sig)
allsigv.sort(key=lambda sig: sig.signo)
allsigv.remove(syscall.SIGKILL) # SIGKILL/SIGSTOP cannot be caught
allsigv.remove(syscall.SIGSTOP)
allsigv.remove(syscall.SIGBUS) # AddressSanitizer crashes on SIGBUS/SIGFPE/SIGSEGV
allsigv.remove(syscall.SIGFPE)
allsigv.remove(syscall.SIGSEGV)
# Notify() -> kill * -> should be notified
ch = chan(10, dtype=gos.Signal)
signal.Notify(ch) # all signals
for sig in allsigv:
emit("-> %d %s" % (sig.signo, sig))
killme(sig)
xsig = ch.recv()
emit("<- %d %s" % (xsig.signo, xsig))
if xsig != sig:
raise AssertionError("got %s, expected %s" % (xsig, sig))
emit("ok (notify)")
# Ignore() -> kill * -> should not be notified
emit()
signal.Ignore() # all signals
assert len(ch) == 0
for sig in allsigv:
emit("-> %d %s" % (sig.signo, sig))
killme(sig)
assert len(ch) == 0
time.sleep(0.3)
assert len(ch) == 0
emit("ok (ignore)")
# Reset() -> kill * should be handled by OS by default
emit()
signal.Reset() # all signals
emit("terminating ...")
killme(syscall.SIGTERM)
raise AssertionError("not terminated")
# killme sends signal sig to own process.
def killme(sig):
mypid = os.getpid()
os.kill(mypid, sig.signo)
def emit(msg=''):
print(msg)
sys.stdout.flush()
if __name__ == '__main__':
main()
...@@ -185,6 +185,7 @@ def _with_build_defaults(kw): # -> (pygo, kw') ...@@ -185,6 +185,7 @@ def _with_build_defaults(kw): # -> (pygo, kw')
'sync.h', 'sync.h',
'time.h', 'time.h',
'os.h', 'os.h',
'os/signal.h',
'pyx/runtime.h', 'pyx/runtime.h',
'_testing.h', '_testing.h',
]]) ]])
...@@ -227,6 +228,10 @@ def Extension(name, sources, **kw): ...@@ -227,6 +228,10 @@ def Extension(name, sources, **kw):
'_sync.pxd', '_sync.pxd',
'time.pxd', 'time.pxd',
'_time.pxd', '_time.pxd',
'os.pxd',
'_os.pxd',
'os/signal.pxd',
'os/_signal.pxd',
'pyx/runtime.pxd', 'pyx/runtime.pxd',
]]) ]])
kw['depends'] = dependv kw['depends'] = dependv
......
...@@ -119,12 +119,14 @@ const _libgolang_runtime_ops *_runtime = nil; ...@@ -119,12 +119,14 @@ const _libgolang_runtime_ops *_runtime = nil;
using internal::_runtime; using internal::_runtime;
namespace internal { namespace atomic { extern void _init(); } } namespace internal { namespace atomic { extern void _init(); } }
namespace os { namespace signal { extern void _init(); } }
void _libgolang_init(const _libgolang_runtime_ops *runtime_ops) { void _libgolang_init(const _libgolang_runtime_ops *runtime_ops) {
if (_runtime != nil) // XXX better check atomically if (_runtime != nil) // XXX better check atomically
panic("libgolang: double init"); panic("libgolang: double init");
_runtime = runtime_ops; _runtime = runtime_ops;
internal::atomic::_init(); internal::atomic::_init();
os::signal::_init();
} }
void _taskgo(void (*f)(void *), void *arg) { void _taskgo(void (*f)(void *), void *arg) {
......
# -*- coding: utf-8 -*-
# Copyright (C) 2021-2022 Nexedi SA and Contributors.
# Kirill Smelkov <kirr@nexedi.com>
#
# This program is free software: you can Use, Study, Modify and Redistribute
# it under the terms of the GNU General Public License version 3, or (at your
# option) any later version, as published by the Free Software Foundation.
#
# You can also Link and Combine this program with other software covered by
# the terms of any of the Free Software licenses or any of the Open Source
# Initiative approved licenses and Convey the resulting work. Corresponding
# source of such a combination shall include the source code for all other
# software used.
#
# This program is distributed WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# See COPYING file for full licensing terms.
# See https://www.nexedi.com/licensing for rationale and options.
"""Package syscall mirrors Go package syscall.
- SIG* provide constants for signals.
See also https://golang.org/pkg/syscall for Go syscall package documentation.
"""
from __future__ import print_function, absolute_import
# create signal constants wrapped into os.Signal
# for example syscall.SIGTERM = _PySignal_from_int(SIGTERM)
def _():
from golang._os import _PySignal_from_int
import signal as _pysig
for attr in dir(_pysig):
if attr.startswith("SIG") and not attr.startswith("SIG_"):
signo = getattr(_pysig, attr)
sig = _PySignal_from_int(signo)
globals()[attr] = sig
_(); del _
...@@ -204,6 +204,7 @@ setup( ...@@ -204,6 +204,7 @@ setup(
'golang/fmt.cpp', 'golang/fmt.cpp',
'golang/io.cpp', 'golang/io.cpp',
'golang/os.cpp', 'golang/os.cpp',
'golang/os/signal.cpp',
'golang/strings.cpp', 'golang/strings.cpp',
'golang/sync.cpp', 'golang/sync.cpp',
'golang/time.cpp'], 'golang/time.cpp'],
...@@ -218,6 +219,7 @@ setup( ...@@ -218,6 +219,7 @@ setup(
'golang/fmt.h', 'golang/fmt.h',
'golang/io.h', 'golang/io.h',
'golang/os.h', 'golang/os.h',
'golang/os/signal.h',
'golang/strings.h', 'golang/strings.h',
'golang/sync.h', 'golang/sync.h',
'golang/time.h'], 'golang/time.h'],
...@@ -280,10 +282,15 @@ setup( ...@@ -280,10 +282,15 @@ setup(
Ext('golang._io', Ext('golang._io',
['golang/_io.pyx']), ['golang/_io.pyx']),
Ext('golang._os',
['golang/_os.pyx']),
Ext('golang._os_test', Ext('golang._os_test',
['golang/_os_test.pyx', ['golang/_os_test.pyx',
'golang/os_test.cpp']), 'golang/os_test.cpp']),
Ext('golang.os._signal',
['golang/os/_signal.pyx']),
Ext('golang._strings_test', Ext('golang._strings_test',
['golang/_strings_test.pyx', ['golang/_strings_test.pyx',
'golang/strings_test.cpp']), 'golang/strings_test.cpp']),
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment