diff --git a/src/wrap_cl.hpp b/src/wrap_cl.hpp index 36881b9f70164f8f5863865bc4a55dd579bb645d..6c004171ce7288e0fc0595667b72771f954ff29d 100644 --- a/src/wrap_cl.hpp +++ b/src/wrap_cl.hpp @@ -46,6 +46,10 @@ #endif +#include +#include +#include + #include #include #include @@ -1345,6 +1349,105 @@ namespace pyopencl { PYOPENCL_CALL_GUARDED_THREADED(clWaitForEvents, (1, &m_event)); } + +#if PYOPENCL_CL_VERSION >= 0x1010 + // {{{ set_callback, by way of a a thread-based construction + + private: + struct event_callback_info_t + { + std::mutex m_mutex; + std::condition_variable m_condvar; + + py::object m_py_event; + py::object m_py_callback; + + bool m_set_callback_suceeded; + + cl_event m_event; + cl_int m_command_exec_status; + + event_callback_info_t(py::object py_event, py::object py_callback) + : m_set_callback_suceeded(true), m_py_event(py_event), m_py_callback(py_callback) + {} + }; + + static void evt_callback(cl_event evt, cl_int command_exec_status, void *user_data) + { + event_callback_info_t *cb_info = reinterpret_cast(user_data); + { + std::lock_guard lg(cb_info->m_mutex); + cb_info->m_event = evt; + cb_info->m_command_exec_status = command_exec_status; + } + cb_info->m_condvar.notify_one(); + } + + public: + void set_callback(cl_int command_exec_callback_type, py::object pfn_event_notify) + { + // The reason for doing this via a thread is that we're able to wait on + // acquiring the GIL. (which we can't in the callback) + + std::unique_ptr cb_info_holder( + new event_callback_info_t( + handle_from_new_ptr(new event(*this)), + pfn_event_notify)); + event_callback_info_t *cb_info = cb_info_holder.get(); + + std::thread notif_thread([cb_info]() + { + std::unique_lock ulk(cb_info->m_mutex); + cb_info->m_condvar.wait(ulk); + + { + py::gil_scoped_acquire acquire; + + if (cb_info->m_set_callback_suceeded) + { + try { + cb_info->m_py_callback( + // cb_info->m_py_event, + cb_info->m_command_exec_status); + } + catch (std::exception &exc) + { + std::cerr + << "[pyopencl] event callback handler threw an exception, ignoring: " + << exc.what() + << std::endl; + } + } + + // Need to hold GIL to delete py::object instances in + // event_callback_info_t + delete cb_info; + } + }); + // Thread is away--it is now its responsibility to free cb_info. + cb_info_holder.release(); + + // notif_thread should no longer be coupled to the lifetime of the thread. + notif_thread.detach(); + + try + { + PYOPENCL_CALL_GUARDED(clSetEventCallback, ( + data(), command_exec_callback_type, &event::evt_callback, cb_info)); + } + catch (...) { + // Setting the callback did not succeed. The thread would never + // be woken up. Wake it up to let it know that it can stop. + { + std::lock_guard lg(cb_info->m_mutex); + cb_info->m_set_callback_suceeded = false; + } + cb_info->m_condvar.notify_one(); + throw; + } + } + // }}} +#endif }; #ifdef PYOPENCL_USE_NEW_BUFFER_INTERFACE diff --git a/src/wrap_cl_part_1.cpp b/src/wrap_cl_part_1.cpp index 785427ee0f86d691512e76de548b5d0f49906447..a88dc75632e3154f6d59c62508dca66ac1f9c89a 100644 --- a/src/wrap_cl_part_1.cpp +++ b/src/wrap_cl_part_1.cpp @@ -111,6 +111,9 @@ void pyopencl_expose_part_1(py::module &m) .def(py::self != py::self) .def("__hash__", &cls::hash) PYOPENCL_EXPOSE_TO_FROM_INT_PTR(cl_event) +#if PYOPENCL_CL_VERSION >= 0x1010 + .DEF_SIMPLE_METHOD(set_callback) +#endif ; } { diff --git a/test/test_wrapper.py b/test/test_wrapper.py index bba4ca6af72f681c5e7ac4eb841a0cce3ca6026f..45d04b54a891c13c79e8eabb31481a40bf165ab4 100644 --- a/test/test_wrapper.py +++ b/test/test_wrapper.py @@ -793,7 +793,7 @@ def test_event_set_callback(ctx_factory): queue = cl.CommandQueue(ctx) if ctx._get_cl_version() < (1, 1): - pytest.skip("OpenCL 1.1 or newer required fro set_callback") + pytest.skip("OpenCL 1.1 or newer required for set_callback") a_np = np.random.rand(50000).astype(np.float32) b_np = np.random.rand(50000).astype(np.float32)