Skip to content

Commit d7d6ae5

Browse files
committed
Fix passing of extra arguments to py_safe_call_once (#7585)
It wasn't compiling because I wasn't using `std::forward` correctly - they should be consistently forwarding references. Being able to pass arguments is useful because it can avoid the need for a closure in some cases.
1 parent e9242df commit d7d6ae5

2 files changed

Lines changed: 75 additions & 2 deletions

File tree

Cython/Includes/libcpp/mutex.pxd

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ cdef extern from *:
232232
}
233233
234234
template <typename Callable, typename ... Args>
235-
void __pyx_cpp_py_safe_call_once(std::once_flag& flag, Callable& callable, Args&&... args) {
235+
void __pyx_cpp_py_safe_call_once(std::once_flag& flag, Callable&& callable, Args&&... args) {
236236
class PyException : public std::exception {
237237
public:
238238
using std::exception::exception;
@@ -246,7 +246,7 @@ cdef extern from *:
246246
247247
try {
248248
std::call_once(flag,
249-
[&](Args& ...args) {
249+
[&](Args&& ...args) {
250250
// Make sure we have the GIL
251251
PyGILState_STATE gil_state;
252252
int had_gil_on_call = __Pyx_UnknownThreadStateDefinitelyHadGil(thread_state);

tests/run/cpp_mutex.pyx

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,79 @@ def test_py_safe_once_cdef_nogil():
270270
print(global_value)
271271

272272

273+
cdef void _do_lazy_init(void *o):
274+
as_py = <SomeCdefClass>o
275+
as_py._lazy_init_value = as_py.initial_value * 2
276+
as_py.initial_value = -1 # change it so it doesn't work a second time
277+
278+
cdef class SomeCdefClass:
279+
cdef py_safe_once_flag flag
280+
cdef double initial_value
281+
cdef double _lazy_init_value
282+
283+
def __cinit__(self, initial_value):
284+
self.initial_value = initial_value
285+
286+
@property
287+
def lazy_init_value(self):
288+
# Pass as void* because we can't pass object to variadic functions
289+
py_safe_call_once(self.flag, _do_lazy_init, <void*>self)
290+
return self._lazy_init_value
291+
292+
@property
293+
def lazy_init_lvalue(self):
294+
# Pass as void* because we can't pass object to variadic functions
295+
cdef void* self_v = <void*>self
296+
py_safe_call_once(self.flag, _do_lazy_init, self_v)
297+
return self._lazy_init_value
298+
299+
def test_py_safe_call_once_with_arg_passing(v):
300+
"""
301+
>>> test_py_safe_call_once_with_arg_passing(20)
302+
40.0
303+
>>> test_py_safe_call_once_with_arg_passing(1)
304+
2.0
305+
"""
306+
inst = SomeCdefClass(v)
307+
v1 = inst.lazy_init_value
308+
v2 = inst.lazy_init_value
309+
assert v1 == v2
310+
return v1
311+
312+
def test_py_safe_call_once_with_lvalue_arg_passing(v):
313+
"""
314+
>>> test_py_safe_call_once_with_lvalue_arg_passing(20)
315+
40.0
316+
>>> test_py_safe_call_once_with_lvalue_arg_passing(1)
317+
2.0
318+
"""
319+
inst = SomeCdefClass(v)
320+
v1 = inst.lazy_init_lvalue
321+
v2 = inst.lazy_init_lvalue
322+
assert v1 == v2
323+
return v1
324+
325+
326+
cdef void print_arg(int arg) noexcept:
327+
print(arg)
328+
329+
ctypedef void (*void_int_func)(int) noexcept
330+
331+
cdef void_int_func get_print_arg() noexcept:
332+
return print_arg
333+
334+
def test_py_safe_call_once_rvalue_func(int arg):
335+
"""
336+
>>> test_py_safe_call_once_rvalue_func(5)
337+
5
338+
>>> test_py_safe_call_once_rvalue_func(0)
339+
0
340+
"""
341+
cdef py_safe_once_flag flag
342+
py_safe_call_once(flag, get_print_arg(), arg)
343+
py_safe_call_once(flag, get_print_arg(), arg)
344+
345+
273346
def test_scoped_lock():
274347
"""
275348
>>> test_scoped_lock()

0 commit comments

Comments
 (0)