Commit Graph

29 Commits

Author SHA1 Message Date
rdb c54c908d28 Merge branch 'release/1.10.x' 2025-12-29 10:55:23 +01:00
rdb 12b5fafa9f tests: Fix future tests always failing on Python pre-3.5 2025-12-23 20:31:56 +01:00
rdb 8177021155 tests: Fix unit test suite on Python 2.7 2025-12-23 20:30:56 +01:00
rdb 469df73ce7 tests: Add unit tests for send() and throw() on generator wrapper 2025-08-28 12:31:51 +02:00
rdb e57c71d8e4 event: Change result of AsyncFuture gather() to return list
Better matches asyncio gather

See #1738
2025-07-28 15:43:12 +02:00
rdb 9a2da04573 Merge branch 'release/1.10.x' 2025-07-28 15:41:08 +02:00
rdb 5bc9b70e86 event: Make cancel() for regular tasks work like remove()
Fixes #1741
2025-07-28 15:18:03 +02:00
rdb 41e4cf5d11 event: Fix `await gather()` returning first item instead of tuple
Fixes #1738
2025-07-28 14:58:42 +02:00
rdb fd7da1908f tests: Fix test suite with 3.13t build
[skip ci]
2024-10-29 16:45:08 +01:00
John C. Allwein e7604cb163 event: fix result refcount leak when awaiting AsyncFuture
Raising the StopIteration exception left the underlying
result with an elevated refcount which prevented it from
being cleaned up.
2024-09-14 15:59:05 -06:00
John C. Allwein cfe3885c0e
event: use the persistent python wrapper when appending task object (#1681) 2024-09-14 20:25:08 +02:00
John C. Allwein 21b39da65d pythonTask: fix refcount leak of non-panda Future::done
A missing Py_DECREF on the future's "done" method
caused both the bound method and the underlying
self instance of the future to be leaked when
awaiting non-panda futures (such as _asyncio.Future).

This change includes a simple new test addition
to catch this in the future.
2024-08-08 21:51:03 -06:00
rdb e5bd00f91f task: Implement garbage collector support for PythonTask
This adds persistent wrapper support (introduced by the previous commit) to PythonTask, which makes it possible for reference cycles involving tasks to be found and destroyed.

The major caveat is that it always creates a reference cycle.  This can be broken automatically if there is no more Python reference to it by the time the last C++ reference is dropped, but the other way around requires the garbage collector.

For tasks, I think this it is generally the case that the last reference is in C++, since tasks are usually created and then handed off to the C++ task manager, and for applications that don't want to rely on the GC, it is easy to work around.  If this turns out to be a problem, though, we can add a special garbage collection pass to the task manager.
2024-03-29 20:51:45 +01:00
rdb 38692dd525 interrogate: Support subclassing C++ objects from Python
This change enables persistent Python wrapper objects for Python subclasses of typed, reference counted C++ objects.  That means that these objects will store a reference to `self` on the C++ object, and interrogate will always return that instead of making a new Python wrapper every time it is returned from C++.

Practically, this means that you could subclass eg. PandaNode, Event, Fog, what have you, and store these in the scene graph - the Python data in the subclass will be retained and Panda will return your subclass when you ask for the object back rather than creating a new wrapper object without your original data.

To do this, Interrogate generates a proxy class inheriting from the C++ type with additional room to store a `self` pointer and a TypeHandle (which is returned by an overridden `get_type()`).  This TypeHandle is automatically created by registering the Python subclass with the typing system.  The proxy class is only used when the constructor detects that it's constructing for a subtype, so that regular uses of the C++ type are not affected by this mechanism.

(The registration with the typing system could use some improvement.  There's no regard for namespacing right now, in particular.  Furthermore, we could move the registration to an `__init_subclass__()` method, with parameters to specify an existing TypeHandle or to customize the type name.)

This creates a reference cycle, which must be cleared somehow.  One way this happens is by overriding `unref()` in the proxy, which checks that if the Python and C++ reference counts are both 1, this must be the circular reference, and then it breaks the cycle.  Note that this will _only_ work if all other Python references have been cleared before the last C++ reference goes away.  For the other case, we need to rely on Python's garbage collector, so these classes also implement tp_traverse and tp_clear.

This commit also therefore re-enables Python garbage collector support (ie. defining `__traverse__()`), which was previously disabled due to the problems caused by multiple Python wrappers referring to the same C++ object.  To avoid these problems, Panda will only cooperate with Python's GC if the C++ reference count is 1, in which case we can trivially prove that the last reference must be from the Python wrapper.  Note that this explains why we need persistent wrappers for traversal to work--if there are multiple Python wrappers pointing to the C++ object, and they are all participating in a reference cycle, the reference count cannot be 1, and cycle detection does not work.  By default, the GC is only enabled for Python subclasses, but a class may define `__traverse__()` in order to opt-in, keeping the previous limitation in mind.

There is a second mechanism introduced by this commit: classes may define a public `__self__` member of type `PyObject *` if they want to use persistent objects even if they aren't being subclassed.  This allows these classes to participate in Python's GC and avoid the situation above.

The cycle detection is implemented for PandaNode's Python tags, but this is very incomplete, since the traversal doesn't recurse into children and it won't work if there is more than one wrapper object that is part of a cycle, since PandaNode by default doesn't use persistent wrappers.  This problem may need more attention later.

Fixes #1410
2024-03-29 20:51:34 +01:00
rdb f80cd08993 Remove support for EOL Python versions 3.6 and 3.7 2023-08-04 14:20:59 +02:00
rdb 2e21cf87e4 tests: Skip test_task_cancel_waiting() test if no threads enabled 2023-02-22 23:18:23 +01:00
rdb 5695d1a719 tests: Add separate unit test for AsyncFuture.wait() with timeout
Since it's using a different implementation than the no-timeout version now
2022-02-24 11:43:11 +01:00
rdb 5ef1b44455 event: cancel future being awaited when cancelling coroutine task
Fixes #1136
2021-04-18 17:05:00 +02:00
rdb cdf5b16ddd task: Add AsyncFuture::shield() ability, part of #1136
This is modelled after `asyncio.shield()` and can be used to protect an inner future from cancellation when the outer future is cancelled.
2021-04-09 18:28:07 +02:00
rdb 050d86dadb event: AsyncFuture::set_result() behavior changes:
* Passing a Python subclass of a C++ class now works, the extra Python stuff isn't just discarded
* EventParameter objects are no longer automagically unwrapped - there's no more reason to pass an EventParameter to this method anyway, and it might be unexpected if it is treated specially.
2021-03-01 20:50:25 +01:00
rdb 8cbe36b352 Merge branch 'release/1.10.x' 2021-03-01 19:33:27 +01:00
rdb 064e0383be event: Allow AsyncFuture to store arbitrary PyObject result 2021-03-01 19:16:22 +01:00
rdb bfbbcad990 task: Support calling cancel() on currently awaiting futures
Fixes #911
2020-05-05 13:21:49 +02:00
rdb d799a09002 cleanup: Remove support for EOL versions of Python
Fixes #905
2020-04-26 20:07:56 +02:00
rdb 5f0f005723 tests: add various tests for PythonTask.__dict__ access 2019-03-04 22:59:17 +01:00
rdb 4bc0a1ef5e tests: fix futures test when building without true threading 2018-10-17 17:28:25 +02:00
rdb ec06d3f4f8 tests: fix occasional timing issue in future test 2018-07-05 12:59:02 +02:00
rdb ed5e5386b9 AsyncFuture improvements, incl. support for gathering futures 2017-12-21 14:07:01 +01:00
rdb 2e20a0f16e Implement awaitable thread-safe future for async operations
This introduces AsyncFuture as a new base class of AsyncTask.  It's modelled after asyncio's Future class, except that it is thread-safe and you can use result() to block the current thread waiting for the future to finish (of course this is not necessary for use with coroutines).

AsyncFuture should be used for any operation that finishes in the future, to get the benefit of awaitability within coroutines as well as a standard interface for querying status and results of the operation as well as cancelling it.  As such, it's been implemented in various places, including texture.prepare() and win.trigger_copy().

Note that AsyncFuture is intended to be used *once*; it cannot be used more than once.  As an example of how this works, tex.prepare() will return the same future as long as the prepare isn't complete, but when it is done, subsequent calls to tex.prepare() will return a new future.
2017-12-04 22:25:28 +01:00