PythonMonkey   v1.0.0 (dev)
Loading...
Searching...
No Matches
PyEventLoop.hh
Go to the documentation of this file.
1
11#ifndef PythonMonkey_PyEventLoop_
12#define PythonMonkey_PyEventLoop_
13
14#include <Python.h>
15#include <jsapi.h>
16#include <vector>
17#include <utility>
18#include <atomic>
19
21public:
23 Py_XDECREF(_loop);
24 }
25
26 bool initialized() const {
27 return !!_loop;
28 }
29
34 struct AsyncHandle {
35 using id_t = uint32_t;
36 public:
37 explicit AsyncHandle(PyObject *handle) : _handle(handle) {};
38 AsyncHandle(const AsyncHandle &old) = delete; // forbid copy-initialization
39 AsyncHandle(AsyncHandle &&old) : _handle(std::exchange(old._handle, nullptr)), _refed(old._refed.exchange(false)), _debugInfo(std::exchange(old._debugInfo, nullptr)) {}; // clear the moved-from object
41 if (Py_IsInitialized()) { // the Python runtime has already been finalized when `_timeoutIdMap` is cleared at exit
42 Py_XDECREF(_handle);
43 }
44 }
45
50 static inline id_t newEmpty() {
51 auto handle = AsyncHandle(Py_None);
52 return AsyncHandle::getUniqueId(std::move(handle));
53 }
54
59 void cancel();
63 bool cancelled();
68
73 static inline id_t getUniqueId(AsyncHandle &&handle) {
74 // TODO (Tom Tang): mutex lock
75 _timeoutIdMap.push_back(std::move(handle));
76 return _timeoutIdMap.size() - 1; // the index in `_timeoutIdMap`
77 }
78 static inline AsyncHandle *fromId(id_t timeoutID) {
79 try {
80 return &_timeoutIdMap.at(timeoutID);
81 } catch (...) { // std::out_of_range&
82 return nullptr; // invalid timeoutID
83 }
84 }
85
90 static bool cancelAll();
91
95 inline PyObject *getHandleObject() const {
96 Py_INCREF(_handle); // otherwise the object would be GC-ed as the AsyncHandle destructor decreases the reference count
97 return _handle;
98 }
99
104 inline PyObject *swap(PyObject *newHandleObject) {
105 return std::exchange(_handle, newHandleObject);
106 }
107
111 inline bool hasRef() {
112 return _refed;
113 }
114
118 inline void addRef() {
119 if (!_refed) {
120 _refed = true;
121 if (!_finishedOrCancelled()) { // noop if the timer is finished or canceled
123 }
124 }
125 }
126
130 inline void removeRef() {
131 if (_refed) {
132 _refed = false;
134 }
135 }
136
140 inline void setDebugInfo(PyObject *obj) {
141 _debugInfo = obj;
142 }
143 inline PyObject *getDebugInfo() {
144 return _debugInfo;
145 }
146
150 static inline auto &getAllTimers() {
151 return _timeoutIdMap;
152 }
153 protected:
154 PyObject *_handle;
155 std::atomic_bool _refed = false;
156 PyObject *_debugInfo = nullptr;
157 };
158
164 AsyncHandle enqueue(PyObject *jobFn);
172 [[nodiscard]] AsyncHandle::id_t enqueueWithDelay(PyObject *jobFn, double delaySeconds, bool repeat);
173
178 struct Future {
179 public:
180 explicit Future(PyObject *future) : _future(future) {};
181 Future(const Future &old) = delete; // forbid copy-initialization
182 Future(Future &&old) : _future(std::exchange(old._future, nullptr)) {}; // clear the moved-from object
184 Py_XDECREF(_future);
185 }
186
191 void setResult(PyObject *result);
192
197 void setException(PyObject *exception);
198
203 void addDoneCallback(PyObject *cb);
204
209 bool isCancelled();
210
216 PyObject *getResult();
217
223 PyObject *getException();
224
228 inline PyObject *getFutureObject() const {
229 Py_INCREF(_future); // otherwise the object would be GC-ed as this `PyEventLoop::Future` destructs
230 return _future;
231 }
232 protected:
233 PyObject *_future;
234 };
235
242
247 Future ensureFuture(PyObject *awaitable);
248
256
262 static PyEventLoop getMainLoop();
263
264 struct Lock {
265 public:
266 explicit Lock() {
267 PyObject *asyncio = PyImport_ImportModule("asyncio");
268 _queueIsEmpty = PyObject_CallMethod(asyncio, "Event", NULL); // _queueIsEmpty = asyncio.Event()
269 Py_DECREF(asyncio);
270
271 // The flag should initially be set as the queue is initially empty
272 Py_XDECREF(PyObject_CallMethod(_queueIsEmpty, "set", NULL)); // _queueIsEmpty.set()
273 };
275 Py_DECREF(_queueIsEmpty);
276 }
277
281 inline void incCounter() {
282 _counter++;
283 Py_XDECREF(PyObject_CallMethod(_queueIsEmpty, "clear", NULL)); // _queueIsEmpty.clear()
284 }
285
289 inline void decCounter() {
290 _counter--;
291 if (_counter == 0) { // no job queueing
292 // Notify that the queue is empty and awake (unblock) the event-loop shield
293 Py_XDECREF(PyObject_CallMethod(_queueIsEmpty, "set", NULL)); // _queueIsEmpty.set()
294 } else if (_counter < 0) { // something went wrong
295 PyErr_SetString(PyExc_RuntimeError, "Event-loop job counter went below zero.");
296 }
297 }
298
303 PyObject *_queueIsEmpty = nullptr;
304 protected:
305 std::atomic_int _counter = 0;
306 };
307
309
310 PyObject *_loop;
311protected:
312 PyEventLoop() = delete;
313 PyEventLoop(PyObject *loop) : _loop(loop) {};
314private:
319 static PyEventLoop _loopNotFound();
320
325 static PyEventLoop _getLoopOnThread(PyThreadState *tstate);
326
327 static PyThreadState *_getMainThread();
328 static inline PyThreadState *_getCurrentThread();
329
330 // TODO (Tom Tang): use separate pools of IDs for different global objects
331 static inline std::vector<AsyncHandle> _timeoutIdMap;
332};
333
334#endif
C++ wrapper for Python asyncio.Handle class.
Definition PyEventLoop.hh:34
uint32_t id_t
Definition PyEventLoop.hh:35
bool hasRef()
Getter for if the timer has been ref'ed.
Definition PyEventLoop.hh:111
AsyncHandle(AsyncHandle &&old)
Definition PyEventLoop.hh:39
static id_t newEmpty()
Create a new AsyncHandle without an associated asyncio.Handle Python object.
Definition PyEventLoop.hh:50
void addRef()
Ref the timer so that the event-loop won't exit as long as the timer is active.
Definition PyEventLoop.hh:118
PyObject * _handle
Definition PyEventLoop.hh:154
~AsyncHandle()
Definition PyEventLoop.hh:40
std::atomic_bool _refed
Definition PyEventLoop.hh:155
PyObject * _debugInfo
Definition PyEventLoop.hh:156
bool _finishedOrCancelled()
Definition PyEventLoop.cc:234
AsyncHandle(const AsyncHandle &old)=delete
void setDebugInfo(PyObject *obj)
Set the debug info object for WTFPythonMonkey tool.
Definition PyEventLoop.hh:140
PyObject * getDebugInfo()
Definition PyEventLoop.hh:143
void cancel()
Cancel the scheduled event-loop job. If the job has already been canceled or executed,...
Definition PyEventLoop.cc:208
static AsyncHandle * fromId(id_t timeoutID)
Definition PyEventLoop.hh:78
AsyncHandle(PyObject *handle)
Definition PyEventLoop.hh:37
PyObject * getHandleObject() const
Get the underlying asyncio.Handle Python object.
Definition PyEventLoop.hh:95
static bool cancelAll()
Cancel all pending event-loop jobs.
Definition PyEventLoop.cc:219
void removeRef()
Unref the timer so that the event-loop can exit.
Definition PyEventLoop.hh:130
static auto & getAllTimers()
Get an iterator for the AsyncHandles of all timers.
Definition PyEventLoop.hh:150
bool cancelled()
Definition PyEventLoop.cc:226
PyObject * swap(PyObject *newHandleObject)
Replace the underlying asyncio.Handle Python object with the provided value.
Definition PyEventLoop.hh:104
static id_t getUniqueId(AsyncHandle &&handle)
Get the unique timeoutID for JS setTimeout/clearTimeout methods.
Definition PyEventLoop.hh:73
C++ wrapper for Python asyncio.Future class.
Definition PyEventLoop.hh:178
PyObject * getFutureObject() const
Get the underlying asyncio.Future Python object.
Definition PyEventLoop.hh:228
PyObject * getException()
Get the exception object that was set on this Future, or Py_None if no exception was set....
Definition PyEventLoop.cc:273
Future(PyObject *future)
Definition PyEventLoop.hh:180
void addDoneCallback(PyObject *cb)
Add a callback to be run when the Future is done.
Definition PyEventLoop.cc:254
void setException(PyObject *exception)
Mark the Future as done and set an exception.
Definition PyEventLoop.cc:248
~Future()
Definition PyEventLoop.hh:183
PyObject * _future
Definition PyEventLoop.hh:233
void setResult(PyObject *result)
Mark the Future as done and set its result.
Definition PyEventLoop.cc:242
Future(const Future &old)=delete
bool isCancelled()
Return True if the Future is cancelled.
Definition PyEventLoop.cc:260
Future(Future &&old)
Definition PyEventLoop.hh:182
PyObject * getResult()
Get the result of the Future. Would raise exception if the Future is pending, cancelled,...
Definition PyEventLoop.cc:268
Definition PyEventLoop.hh:264
PyObject * _queueIsEmpty
An asyncio.Event instance to notify that there are no queued asynchronous jobs.
Definition PyEventLoop.hh:303
~Lock()
Definition PyEventLoop.hh:274
void incCounter()
Increment the counter for the number of our job functions in the Python event-loop.
Definition PyEventLoop.hh:281
std::atomic_int _counter
Definition PyEventLoop.hh:305
Lock()
Definition PyEventLoop.hh:266
void decCounter()
Decrement the counter for the number of our job functions in the Python event-loop.
Definition PyEventLoop.hh:289
Definition PyEventLoop.hh:20
PyObject * _loop
Definition PyEventLoop.hh:310
bool initialized() const
Definition PyEventLoop.hh:26
Future createFuture()
Create a Python asyncio.Future object attached to this Python event-loop.
Definition PyEventLoop.cc:105
PyEventLoop()=delete
PyEventLoop(PyObject *loop)
Definition PyEventLoop.hh:313
AsyncHandle::id_t enqueueWithDelay(PyObject *jobFn, double delaySeconds, bool repeat)
Schedule a job to the Python event-loop, with the given delay.
Definition PyEventLoop.cc:95
static PyEventLoop getMainLoop()
Get the running Python event-loop on main thread, or raise a Python RuntimeError if no event-loop run...
Definition PyEventLoop.cc:199
AsyncHandle enqueue(PyObject *jobFn)
Send job to the Python event-loop.
Definition PyEventLoop.cc:71
static PyEventLoop getRunningLoop()
Get the running Python event-loop on the current thread, or raise a Python RuntimeError if no event-l...
Definition PyEventLoop.cc:204
Future ensureFuture(PyObject *awaitable)
Convert a Python awaitable to asyncio.Future attached to this Python event-loop.
Definition PyEventLoop.cc:111
~PyEventLoop()
Definition PyEventLoop.hh:22
static PyEventLoop::Lock * _locker
Definition PyEventLoop.hh:308