Loading...
Searching...
No Matches
python_future_task.h
1
5#pragma once
6
7#include <chrono>
8#include <future>
9#include <memory>
10#include <mutex>
11#include <queue>
12#include <sstream>
13#include <string>
14#include <utility>
15#include <vector>
16
17#include <iostream>
18
19#include <Python.h>
20
21#include <ucxx/log.h>
22#include <ucxx/python/future.h>
23#include <ucxx/python/python_future_task_collector.h>
24
25namespace ucxx {
26
27namespace python {
28
29namespace detail {
30
37template <typename ReturnType, typename... TaskArgs>
39 public:
40 std::packaged_task<ReturnType(TaskArgs...)> _task{};
41 std::function<PyObject*(ReturnType)>
43 PyObject* _asyncioEventLoop{};
44 PyObject* _handle{};
45 std::future<ReturnType> _future{};
46
47 private:
59 void setResult(const ReturnType result)
60 {
61 // PyLong_FromSize_t requires the GIL
62 if (_handle == nullptr) throw std::runtime_error("Invalid object or already released");
63
64 PyGILState_STATE state = PyGILState_Ensure();
65 ucxx::python::future_set_result_with_event_loop(
67 PyGILState_Release(state);
68 }
69
79 void setPythonException(PyObject* pythonException, const std::string& message)
80 {
81 if (_handle == nullptr) throw std::runtime_error("Invalid object or already released");
82
83 PyGILState_STATE state = PyGILState_Ensure();
84 ucxx::python::future_set_exception_with_event_loop(
85 _asyncioEventLoop, _handle, pythonException, message.c_str());
86 PyGILState_Release(state);
87 }
88
97 void setException(const std::exception& exception)
98 {
99 try {
100 throw exception;
101 } catch (const std::bad_alloc& e) {
102 setPythonException(PyExc_MemoryError, e.what());
103 } catch (const std::bad_cast& e) {
104 setPythonException(PyExc_TypeError, e.what());
105 } catch (const std::bad_typeid& e) {
106 setPythonException(PyExc_TypeError, e.what());
107 } catch (const std::domain_error& e) {
108 setPythonException(PyExc_ValueError, e.what());
109 } catch (const std::invalid_argument& e) {
110 setPythonException(PyExc_ValueError, e.what());
111 } catch (const std::ios_base::failure& e) {
112 setPythonException(PyExc_IOError, e.what());
113 } catch (const std::out_of_range& e) {
114 setPythonException(PyExc_IndexError, e.what());
115 } catch (const std::overflow_error& e) {
116 setPythonException(PyExc_OverflowError, e.what());
117 } catch (const std::range_error& e) {
118 setPythonException(PyExc_ArithmeticError, e.what());
119 } catch (const std::underflow_error& e) {
120 setPythonException(PyExc_ArithmeticError, e.what());
121 } catch (const std::exception& e) {
122 setPythonException(PyExc_RuntimeError, e.what());
123 } catch (...) {
124 setPythonException(PyExc_RuntimeError, "Unknown exception");
125 }
126 }
127
128 public:
147 explicit PythonFutureTask(std::packaged_task<ReturnType(TaskArgs...)> task,
148 std::function<PyObject*(ReturnType)> pythonConvert,
149 PyObject* asyncioEventLoop,
150 std::launch launchPolicy = std::launch::async)
151 : _task{std::move(task)},
152 _pythonConvert(std::move(pythonConvert)),
153 _asyncioEventLoop(asyncioEventLoop),
154 _handle{ucxx::python::create_python_future_with_event_loop(asyncioEventLoop)},
155 _future{std::async(launchPolicy, [this]() {
156 std::future<ReturnType> result = this->_task.get_future();
157 this->_task();
158 try {
159 const ReturnType r = result.get();
160 this->setResult(r);
161 return r;
162 } catch (std::exception& e) {
163 this->setException(e);
164 }
165 })}
166 {
167 }
168
169 PythonFutureTask(const PythonFutureTask&) = delete;
170 PythonFutureTask& operator=(PythonFutureTask const&) = delete;
171
180
189
201
209 [[nodiscard]] std::future<ReturnType>& getFuture()
210 {
211 if (_handle == nullptr) throw std::runtime_error("Invalid object or already released");
212
213 return _future;
214 }
215
230 [[nodiscard]] PyObject* getHandle()
231 {
232 if (_handle == nullptr) throw std::runtime_error("Invalid object or already released");
233
234 return _handle;
235 }
236
248 [[nodiscard]] PyObject* release()
249 {
250 if (_handle == nullptr) throw std::runtime_error("Invalid object or already released");
251
252 return std::exchange(_handle, nullptr);
253 }
254};
255
256} // namespace detail
257
264template <typename ReturnType, typename... TaskArgs>
265class PythonFutureTask : public std::enable_shared_from_this<PythonFutureTask<ReturnType>> {
266 private:
267 std::unique_ptr<detail::PythonFutureTask<ReturnType, TaskArgs...>> _detail{
268 nullptr};
269
270 public:
289 explicit PythonFutureTask(std::packaged_task<ReturnType(TaskArgs...)> task,
290 std::function<PyObject*(ReturnType)> pythonConvert,
291 PyObject* asyncioEventLoop,
292 std::launch launchPolicy = std::launch::async)
293 : _detail(std::make_unique<detail::PythonFutureTask<ReturnType, TaskArgs...>>(
294 std::move(task), std::move(pythonConvert), asyncioEventLoop, launchPolicy))
295 {
296 }
297 PythonFutureTask(const PythonFutureTask&) = delete;
298 PythonFutureTask& operator=(PythonFutureTask const&) = delete;
299
308
317
325 [[nodiscard]] std::future<ReturnType>& getFuture() { return _detail->getFuture(); }
326
341 [[nodiscard]] PyObject* getHandle() { return _detail->getHandle(); }
342
354 [[nodiscard]] PyObject* release() { return _detail->release(); }
355};
356
357} // namespace python
358
359} // namespace ucxx
static PythonFutureTaskCollector & get()
User-facing bridge of C++ and Python futures.
Definition python_future_task.h:265
PyObject * getHandle()
Get the underlying future PyObject* handle but does not release ownership.
Definition python_future_task.h:341
std::future< ReturnType > & getFuture()
Get the C++ future.
Definition python_future_task.h:325
PyObject * release()
Get the underlying future PyObject* handle and release ownership.
Definition python_future_task.h:354
PythonFutureTask(std::packaged_task< ReturnType(TaskArgs...)> task, std::function< PyObject *(ReturnType)> pythonConvert, PyObject *asyncioEventLoop, std::launch launchPolicy=std::launch::async)
Construct a Python future backed by C++ std::packaged_task.
Definition python_future_task.h:289
PythonFutureTask(PythonFutureTask &&o)=default
The move constructor.
PythonFutureTask & operator=(PythonFutureTask &&o)=default
The move operator.
Definition address.h:16
A bridge of C++ and Python futures.
Definition python_future_task.h:38
std::function< PyObject *(ReturnType)> _pythonConvert
Function to convert the C++ result into Python value.
Definition python_future_task.h:42
PyObject * getHandle()
Get the underlying future PyObject* handle but does not release ownership.
Definition python_future_task.h:230
std::future< ReturnType > _future
The C++ future containing the task result.
Definition python_future_task.h:45
PythonFutureTask(std::packaged_task< ReturnType(TaskArgs...)> task, std::function< PyObject *(ReturnType)> pythonConvert, PyObject *asyncioEventLoop, std::launch launchPolicy=std::launch::async)
Construct a Python future backed by C++ std::packaged_task.
Definition python_future_task.h:147
std::future< ReturnType > & getFuture()
Get the C++ future.
Definition python_future_task.h:209
PyObject * release()
Get the underlying future PyObject* handle and release ownership.
Definition python_future_task.h:248
PyObject * _handle
The handle to the Python future.
Definition python_future_task.h:44
PyObject * _asyncioEventLoop
The handle to the Python asyncio event loop.
Definition python_future_task.h:43
std::packaged_task< ReturnType(TaskArgs...)> _task
The user-defined C++ task to run.
Definition python_future_task.h:40
~PythonFutureTask()
Python future destructor.
Definition python_future_task.h:200
PythonFutureTask & operator=(PythonFutureTask &&o)=default
The move operator.
PythonFutureTask(PythonFutureTask &&o)=default
The move constructor.