Commit 0fb53e33 authored by Kirill Smelkov's avatar Kirill Smelkov

sync: Move/Port Once to C++/Pyx nogil

Provide sync.Once that can be used directly from C++ and Pyx/nogil worlds.
Python-level sync.Once becomes small wrapper around pyx/nogil sync.Once .
parent 1d153a45
...@@ -178,6 +178,7 @@ cdef extern from * nogil: ...@@ -178,6 +178,7 @@ cdef extern from * nogil:
extern void _test_select_win_while_queue(); extern void _test_select_win_while_queue();
extern void _test_select_inplace(); extern void _test_select_inplace();
extern void _test_defer(); extern void _test_defer();
extern void _test_sync_once_cpp();
""" """
void _test_chan_cpp_refcount() except +topyexc void _test_chan_cpp_refcount() except +topyexc
void _test_chan_cpp() except +topyexc void _test_chan_cpp() except +topyexc
...@@ -188,6 +189,7 @@ cdef extern from * nogil: ...@@ -188,6 +189,7 @@ cdef extern from * nogil:
void _test_select_win_while_queue() except +topyexc void _test_select_win_while_queue() except +topyexc
void _test_select_inplace() except +topyexc void _test_select_inplace() except +topyexc
void _test_defer() except +topyexc void _test_defer() except +topyexc
void _test_sync_once_cpp() except +topyexc
def test_chan_cpp_refcount(): def test_chan_cpp_refcount():
with nogil: with nogil:
_test_chan_cpp_refcount() _test_chan_cpp_refcount()
...@@ -215,6 +217,9 @@ def test_select_inplace(): ...@@ -215,6 +217,9 @@ def test_select_inplace():
def test_defer(): def test_defer():
with nogil: with nogil:
_test_defer() _test_defer()
def test_sync_once_cpp(): # TODO move -> _sync_test.pyx
with nogil:
_test_sync_once_cpp()
# helpers for pychan(dtype=X) py <-> c tests. # helpers for pychan(dtype=X) py <-> c tests.
......
...@@ -33,3 +33,6 @@ cdef extern from "golang/libgolang.h" namespace "golang::sync" nogil: ...@@ -33,3 +33,6 @@ cdef extern from "golang/libgolang.h" namespace "golang::sync" nogil:
cppclass Mutex: cppclass Mutex:
void lock() void lock()
void unlock() void unlock()
cppclass Once:
void do "do_" (...) # ... = std::function
...@@ -21,7 +21,9 @@ ...@@ -21,7 +21,9 @@
from __future__ import print_function, absolute_import from __future__ import print_function, absolute_import
from cython cimport final from cython cimport final
from cpython cimport PyObject
from golang cimport topyexc
@final @final
cdef class PySema: cdef class PySema:
...@@ -63,9 +65,45 @@ cdef class PyMutex: ...@@ -63,9 +65,45 @@ cdef class PyMutex:
pymu.unlock() pymu.unlock()
# ---- misc ---- @final
cdef class PyOnce:
"""Once allows to execute an action only once.
For example:
once = Once()
...
once.do(doSomething)
"""
cdef Once once
from golang cimport topyexc # FIXME cannot catch/pyreraise panic of .once ctor
# https://github.com/cython/cython/issues/3165
def do(PyOnce pyonce, object f):
with nogil:
_once_pydo(&pyonce.once, <PyObject *>f)
cdef void _once_pydo(Once *once, PyObject *f) nogil except *:
__once_pydo(once, f)
cdef extern from * nogil:
"""
static void __pyx_f_6golang_5_sync__pycall_fromnogil(PyObject *);
static void __once_pydo(golang::sync::Once *once, PyObject *f) {
once->do_([&]() {
__pyx_f_6golang_5_sync__pycall_fromnogil(f);
});
}
"""
void __once_pydo(Once *once, PyObject *f) except +topyexc
cdef void _pycall_fromnogil(PyObject *f) nogil except *:
with gil:
(<object>f)()
# ---- misc ----
cdef nogil: cdef nogil:
......
...@@ -147,3 +147,29 @@ cdef nogil: ...@@ -147,3 +147,29 @@ cdef nogil:
def test_sema_wakeup(): def test_sema_wakeup():
with nogil: with nogil:
_test_sema_wakeup() _test_sema_wakeup()
# verify Once
cdef nogil:
int _once_ncall = 0
void _once_call():
global _once_ncall
_once_ncall += 1
cdef void _test_once() nogil except +topyexc:
cdef sync.Once once
if not (_once_ncall == 0):
panic("once @0: ncall != 0")
once.do(_once_call);
if not (_once_ncall == 1):
panic("once @1: ncall != 1")
once.do(_once_call);
if not (_once_ncall == 1):
panic("once @2: ncall != 1")
once.do(_once_call);
if not (_once_ncall == 1):
panic("once @3: ncall != 1")
def test_once():
with nogil:
_test_once()
...@@ -509,6 +509,26 @@ private: ...@@ -509,6 +509,26 @@ private:
Mutex(Mutex&&); // don't move Mutex(Mutex&&); // don't move
}; };
// Once allows to execute an action only once.
//
// For example:
//
// sync::Once once;
// ...
// once.do_(doSomething);
class Once {
Mutex _mu;
bool _done;
public:
LIBGOLANG_API Once();
LIBGOLANG_API ~Once();
LIBGOLANG_API void do_(const std::function<void(void)> &f);
private:
Once(const Once&); // don't copy
Once(Once&&); // don't move
};
} // golang::sync:: } // golang::sync::
......
...@@ -157,7 +157,7 @@ void _semarelease(_sema *sema) { ...@@ -157,7 +157,7 @@ void _semarelease(_sema *sema) {
_runtime->sema_release((_libgolang_sema *)sema); _runtime->sema_release((_libgolang_sema *)sema);
} }
// golang::sync:: // golang::sync:: (only Sema and Mutex)
namespace sync { namespace sync {
Sema::Sema() { Sema::Sema() {
...@@ -1234,6 +1234,33 @@ double now() { ...@@ -1234,6 +1234,33 @@ double now() {
}} // golang::time:: }} // golang::time::
// ---- golang::sync:: (except Sema and Mutex) ----
namespace golang {
namespace sync {
Once::Once() {
Once *once = this;
once->_done = false;
}
Once::~Once() {}
void Once::do_(const std::function<void(void)> &f) {
Once *once = this;
once->_mu.lock();
defer([&]() {
once->_mu.unlock();
});
if (!once->_done) {
once->_done = true;
f();
}
}
}} // golang::sync::
// ---- misc ---- // ---- misc ----
// zalloc allocates zeroed memory. // zalloc allocates zeroed memory.
......
...@@ -475,3 +475,26 @@ void _test_defer() { ...@@ -475,3 +475,26 @@ void _test_defer() {
__test_defer(&called); __test_defer(&called);
ASSERT(called); ASSERT(called);
} }
// ---- sync:: ----
// verify that sync::Once works.
void _test_sync_once_cpp() {
sync::Once once;
int ncall = 0;
ASSERT(ncall == 0);
once.do_([&]() {
ncall++;
});
ASSERT(ncall == 1);
once.do_([&]() {
ncall++;
});
ASSERT(ncall == 1);
once.do_([&]() {
ncall++;
panic("should not panic");
});
ASSERT(ncall == 1);
}
...@@ -34,26 +34,8 @@ import six ...@@ -34,26 +34,8 @@ import six
from golang._sync import \ from golang._sync import \
PySema as Sema, \ PySema as Sema, \
PyMutex as Mutex \ PyMutex as Mutex, \
PyOnce as Once \
# Once allows to execute an action only once.
#
# For example:
#
# once = Once()
# ...
# once.do(doSomething)
class Once(object):
def __init__(once):
once._mu = Mutex()
once._done = False
def do(once, f):
with once._mu:
if not once._done:
once._done = True
f()
# WaitGroup allows to wait for collection of tasks to finish. # WaitGroup allows to wait for collection of tasks to finish.
......
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