_golang.pxd 8.42 KB
Newer Older
1
# cython: language_level=2
2 3
# Copyright (C) 2019-2020  Nexedi SA and Contributors.
#                          Kirill Smelkov <kirr@nexedi.com>
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
#
# 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 golang.pyx provides Go-like features for Cython/nogil and runtime for golang.py.

Cython/nogil API
----------------

Kirill Smelkov's avatar
Kirill Smelkov committed
25
- `go` spawns lightweight thread.
26
- `chan[T]`, `makechan[T]` and `select` provide C-level channels with Go semantic.
27
- `error` is the interface that represents errors.
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
- `panic` stops normal execution of current goroutine by throwing a C-level exception.

Everything in Cython/nogil API do not depend on Python runtime and in
particular can be used in nogil code.

See README for thorough overview.
See libgolang.h for API details.
See also package golang.py which provides similar functionality for Python.


Golang.py runtime
-----------------

In addition to Cython/nogil API, golang.pyx provides runtime for golang.py:

43
- Python-level channels are represented by pychan + pyselect.
44
- Python-level error is represented by pyerror.
45 46 47 48
- Python-level panic is represented by pypanic.
"""


49
from libcpp cimport nullptr_t as Nil, nullptr as nil # golang::nil = nullptr
50
from libcpp.utility cimport pair
51
from libc.stdint cimport uint64_t
52 53 54
cdef extern from *:
    ctypedef bint cbool "bool"

55 56 57 58 59 60 61 62 63 64
# nogil pyx-level golang API.
#
# NOTE even though many functions may panic (= throw C++ exception) nothing is
# annotated with `except +`. Reason: `f() except +` tells Cython to wrap every
# call to f with try/catch and convert C++ exception into Python one. And once
# you have a Python-level exception you are in Python world. However we want
# nogil golang.pyx API to be usable without Python at all.
#
# -> golang.pyx users need to add `except +topyexc` to their functions that are
# on the edge of Python/nogil world.
65
from libcpp.string cimport string  # golang::string = std::string
66 67 68 69
cdef extern from "golang/libgolang.h" namespace "golang" nogil:
    void panic(const char *)
    const char *recover()

Kirill Smelkov's avatar
Kirill Smelkov committed
70 71
    void go(...)    # typechecking is done by C

72 73 74 75 76
    struct _chan
    cppclass chan[T]:
        chan();

        # send/recv/close
77 78 79 80
        void send(const T&)                 const
        T recv()                            const
        pair[T, cbool] recv_()              const
        void close()                        const
81 82

        # send/recv in select
83 84 85 86
        _selcase sends(const T *ptx)        const
        _selcase recvs()                    const
        _selcase recvs(T* prx)              const
        _selcase recvs(T* prx, cbool *pok)  const
87 88

        # length/capacity
89 90
        unsigned len()                      const
        unsigned cap()                      const
91 92

        # compare wrt nil; =nil
93 94 95
        cbool operator==(Nil)               const
        cbool operator!=(Nil)               const
        void operator=(Nil)
96 97

        # for tests
98
        _chan *_rawchan()   const
99 100 101 102 103 104 105 106 107 108 109

    chan[T] makechan[T]()
    chan[T] makechan[T](unsigned size)

    struct structZ:
        pass

    enum _chanop:
        _CHANSEND
        _CHANRECV
        _DEFAULT
110 111
    enum _selflags:
        _INPLACE_DATA
112
    cppclass _selcase:
113 114 115
        _chan     *ch
        _chanop   op
        unsigned  flags
116
        unsigned  user
117 118 119
        void      *ptxrx
        uint64_t  itxrx
        cbool     *rxok
120 121 122 123

        const void *ptx() const
        void *prx() const

124 125 126 127
    const _selcase default "golang::_default"

    int select(_selcase casev[])

128

129 130 131
    # memory management of C++ nogil classes
    cppclass refptr[T]:
        # compare wrt nil; =nil
132 133 134
        cbool operator== (Nil)          const
        cbool operator!= (Nil)          const
        void  operator=  (Nil)          const
135

136 137 138 139 140 141 142 143 144
        # compare wrt refptr; =refptr
        # XXX workaround for https://github.com/cython/cython/issues/1357:
        #     compare by .eq() instead of ==
        #cbool operator== (refptr)       const
        #cbool operator!= (refptr)       const
        #cbool operator=  (refptr)       const
        cbool eq "operator==" (refptr)  const
        cbool ne "operator!=" (refptr)  const

145 146 147
        # get raw pointer
        T* _ptr()                       const

148 149 150 151 152 153 154 155
    refptr[T] adoptref[T](T *_obj)
    refptr[T] newref  [T](T *_obj)


    cppclass gobject "golang::object":
        cbool __decref()    # protected
        void  incref()
        int   refcnt() const
156

157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177

    # empty interface ~ interface{}
    cppclass _interface:
        void incref()
        void decref()

    cppclass interface (refptr[_interface]):
        # interface.X = interface->X in C++
        void incref "_ptr()->incref" ()
        void decref "_ptr()->decref" ()


    # error interface
    cppclass _error (_interface):
        string Error()

    cppclass error (refptr[_error]):
        # error.X = error->X in C++
        string Error    "_ptr()->Error" ()


178 179 180 181 182 183 184 185 186
    # error wrapper interface
    cppclass _errorWrapper (_error):
        error Unwrap()

    cppclass errorWrapper (refptr[_errorWrapper]):
        # errorWrapper.X = errorWrapper->X in C++
        error Unwrap    "_ptr()->Unwrap" ()


187 188 189 190
# ---- python bits ----

cdef void topyexc() except *
cpdef pypanic(arg)
191

192
# pychan is python wrapper over chan<object> or chan<structZ|bool|int|double|...>
193 194
from cython cimport final

195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
# DType describes type of channel elements.
# TODO consider supporting NumPy dtypes too.
cdef enum DType:
    DTYPE_PYOBJECT   = 0    # chan[object]
    DTYPE_STRUCTZ    = 1    # chan[structZ]
    DTYPE_BOOL       = 2    # chan[bool]
    DTYPE_INT        = 3    # chan[int]
    DTYPE_DOUBLE     = 4    # chan[double]
    DTYPE_NTYPES     = 5

# pychan wraps a channel into python object.
#
# Type of channel can be either channel of python objects, or channel of
# C-level objects. If channel elements are C-level objects, the channel - even
# via pychan wrapper - can be used to interact with nogil world.
#
# There can be multiple pychan(s) wrapping a particular raw channel.
212 213
@final
cdef class pychan:
214
    cdef _chan  *_ch
215 216 217 218 219 220 221 222 223 224 225 226 227 228
    cdef DType  dtype # type of channel elements

    # pychan.nil(X) creates new nil pychan with element type X.
    @staticmethod                  # XXX needs to be `cpdef nil()` but cython:
    cdef pychan _nil(object dtype) #  "static cpdef methods not yet supported"

    # chan_X returns ._ch wrapped into typesafe pyx/nogil-level chan[X].
    # chan_X panics if channel type != X.
    # X can be any C-level type, but not PyObject.
    cdef nogil:
        chan[structZ]   chan_structZ    (pychan pych)
        chan[cbool]     chan_bool       (pychan pych)
        chan[int]       chan_int        (pychan pych)
        chan[double]    chan_double     (pychan pych)
229 230 231 232 233 234 235 236 237 238 239

    # pychan.from_chan_X returns pychan wrapping pyx/nogil-level chan[X].
    # X can be any C-level type, but not PyObject.
    @staticmethod
    cdef pychan from_chan_structZ   (chan[structZ] ch)
    @staticmethod
    cdef pychan from_chan_bool      (chan[cbool] ch)
    @staticmethod
    cdef pychan from_chan_int       (chan[int] ch)
    @staticmethod
    cdef pychan from_chan_double    (chan[double] ch)
240 241 242 243 244 245


# pyerror wraps an error into python object.
#
# There can be multiple pyerror(s) wrapping a particular raw error object.
# Nil C-level error corresponds to None at Python-level.
246 247 248 249 250
#
# Pyerror can be also used as base class for Python-level exception types:
#
#  - objects with type being exact pyerror are treated as wrappers around C-level error.
#  - objects with other types inherited from pyerror are treated as Python-level error.
251
cdef class pyerror(Exception):
252 253
    cdef error    err   # raw error object; nil for Python-level case
    cdef readonly args  # .args for Python-level case
254 255 256 257 258

    # pyerror.from_error returns pyerror wrapping pyx/nogil-level error.
    # from_error(nil) -> returns None.
    @staticmethod
    cdef object from_error (error err) # -> pyerror | None