# Copyright (C) 2019  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.

# cython: language_level=2

"""Module wcfs_test complements wcfs_test.py with things that are impossible to implement in Python"""

from posix.signal cimport sigaction, sigaction_t, siginfo_t, SA_SIGINFO
from libc.signal cimport SIGBUS
from libc.stdlib cimport abort
from libc.string cimport strlen
from posix.unistd cimport write, sleep

from cpython.exc cimport PyErr_SetFromErrno
from cpython.pystate cimport PyGILState_Ensure, PyGILState_Release, PyGILState_STATE

# XXX -> cpython.lifecycle? .run ?
cdef extern from "Python.h":
    int PyRun_SimpleString(const char *)

# read0_nogil accesses mem[0] with GIL released and returns first byte read from there.
def read0_nogil(const unsigned char[::1] mem not None) -> int:
    cdef unsigned char b
    with nogil:
        b = mem[0]
    return b


# ---- signal handling ----
# XXX -> golang.signal ?

# install_sigbus_trap installs SIGBUS handler that prints python-level
# traceback before aborting.
#
# Such handler is useful, because when wcfs.go bugs/panics while handling file
# access from wcfs.py, wcfs.py receives SIGBUS signal and by default aborts.
def install_sigbus_trap():
    cdef sigaction_t act
    act.sa_sigaction = on_sigbus
    act.sa_flags     = SA_SIGINFO

    cdef int err = sigaction(SIGBUS, &act, NULL)
    if err:
        PyErr_SetFromErrno(OSError)

cdef void on_sigbus(int sig, siginfo_t *si, void *_uc):
    # - wait a bit to give time for other threads to complete their exception dumps
    #   (e.g. getting "Transport endpoint is not connected" after wcfs.go dying)
    # - dump py-level traceback and abort.
    # TODO turn SIGBUS into python-level exception? (see sigpanic in Go how to do).

    writeerr("\nC: SIGBUS received; giving time to other threads" +
             "to dump their exceptions (if any) ...\n")
    cdef PyGILState_STATE gstate = PyGILState_Ensure()
    PyGILState_Release(gstate)
    sleep(1)

    writeerr("\nC: SIGBUS thread traceback:\n")
    PyGILState_Ensure()
    PyRun_SimpleString("import traceback; traceback.print_stack()")
    writeerr("-> SIGBUS\n");
    abort()

# writeerr writes msg to stderr without depending on stdio buffering and locking.
cdef void writeerr(const char *msg):
    cdef ssize_t n, left = strlen(msg)
    while left > 0:
        n = write(2, msg, left)
        if n == -1:
            return  # nothing we can do under crash?
        left -= n
        msg  += n