All Classes Namespaces Functions Variables Typedefs Enumerations Friends
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 
25 namespace ucxx {
26 
27 namespace python {
28 
29 namespace detail {
30 
37 template <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 
264 template <typename ReturnType, typename... TaskArgs>
265 class 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 * release()
Get the underlying future PyObject* handle and release ownership.
Definition: python_future_task.h:354
std::future< ReturnType > & getFuture()
Get the C++ future.
Definition: python_future_task.h:325
PythonFutureTask & operator=(PythonFutureTask &&o)=default
The move operator.
PyObject * getHandle()
Get the underlying future PyObject* handle but does not release ownership.
Definition: python_future_task.h:341
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.
Definition: address.h:15
A bridge of C++ and Python futures.
Definition: python_future_task.h:38
PythonFutureTask & operator=(PythonFutureTask &&o)=default
The move operator.
std::function< PyObject *(ReturnType)> _pythonConvert
Function to convert the C++ result into Python value.
Definition: python_future_task.h:42
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 * _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
PyObject * release()
Get the underlying future PyObject* handle and release ownership.
Definition: python_future_task.h:248
~PythonFutureTask()
Python future destructor.
Definition: python_future_task.h:200
PythonFutureTask(PythonFutureTask &&o)=default
The move constructor.
PyObject * getHandle()
Get the underlying future PyObject* handle but does not release ownership.
Definition: python_future_task.h:230