| 32 | * @see https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.Handle |
| 33 | */ |
| 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 |
| 40 | ~AsyncHandle() { |
| 41 | if (Py_IsInitialized()) { // the Python runtime has already been finalized when `_timeoutIdMap` is cleared at exit |
| 42 | Py_XDECREF(_handle); |
| 43 | } |
| 44 | } |
| 45 | |
| 46 | /** |
| 47 | * @brief Create a new `AsyncHandle` without an associated `asyncio.Handle` Python object |
| 48 | * @return the timeoutId |
| 49 | */ |
| 50 | static inline id_t newEmpty() { |
| 51 | auto handle = AsyncHandle(Py_None); |
| 52 | return AsyncHandle::getUniqueId(std::move(handle)); |
| 53 | } |
| 54 | |
| 55 | /** |
| 56 | * @brief Cancel the scheduled event-loop job. |
| 57 | * If the job has already been canceled or executed, this method has no effect. |
| 58 | */ |
| 59 | void cancel(); |
| 60 | /** |
| 61 | * @return true if the job has been cancelled. |
| 62 | */ |
| 63 | bool cancelled(); |
| 64 | /** |
| 65 | * @return true if the job function has already been executed or cancelled. |
| 66 | */ |
| 67 | bool _finishedOrCancelled(); |
| 68 | |
| 69 | /** |
| 70 | * @brief Get the unique `timeoutID` for JS `setTimeout`/`clearTimeout` methods |
| 71 | * @see https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#return_value |
| 72 | */ |
| 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 | |
| 86 | /** |
| 87 | * @brief Cancel all pending event-loop jobs. |
| 88 | * @return success |
| 89 | */ |
| 90 | static bool cancelAll(); |
| 91 | |