Repository navigation
JVM async executor: define each JPype proxy class once per process, not once per callback - #241
Conversation
|
Proof that the new tests fail on
The other eight tests of the file pass on Measured on |
|
Full JVM test run on head |
02ae1f6 to
f085bc1
Compare
|
Outcome of the timed A/B (pre-registered: arm A the It ran four times and every run was invalid by its A/A rule, so no timing from it is reported. The first two used a no-callback control that this PR also moves. The third used a synchronous insert loop in the same process as the callbacks, and that control moved with the PR too: on Decision: adopted as a leak fix, with no speed claim. On |
…ot once per callback (#238) Every _create_*_callback helper declared a @jpype.JImplements class inside the call, so each callback built a Python type, a Java proxy class and a method table that stayed for the life of the process. callbacks.py already caches its proxy classes for the same reason. The classes are now built once per kind under a lock and the Python callables are state of the instance; the callbacks, their arguments and their order are unchanged. Tests count the class definitions and the type objects, and check that callbacks sharing a class keep their own arguments. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
f085bc1 to
6065b16
Compare
Closes #238
The JVM build's
AsyncExecutordefined a new@jpype.JImplementsclass inside every_create_*_callbackhelper, so each callback built a Python type, a Java proxy class and a method table that stayed for the life of the process. This defines each of the eight proxy classes once, on first use and under a lock, the waycallbacks.pyalready does for Python functions in SQL and record events. The Python callables of a call are state of the instance, so callbacks that share a class cannot see each other's arguments.The cache is per process, which includes per executor: a second executor reuses the classes of the first. Python only, no Java change, native is untouched (
async_executor.pyis the JVM path). Thecommanddocstring's callback-cost note is unchanged: the timed A/B gave no number that can be reported (see the status below).Tests (
tests/test_async_proxy_cache.py, JVM only, listed inNATIVE_NOT_APPLICABLE):jpype.JImplementsfrom an empty cache: at most one per kind across three executors and 50 rounds of all eight kinds, and none after the first round of a 600-callback loadcommandresults and errors,queryrows,new_edge,scan_type,transactionblocks and handlers),create_recordcallbacks run in submission order on one writer and deliver every record at parallel level 4, andon_okandon_errorstill fireThe existing
test_async_executor.py,test_async_transaction.pyandtest_concurrency.pyare not edited.Status: adopted as a leak fix, with no speed claim. On
mainevery callback defines a new proxy class and keeps it for the life of the process (about 6 KB of Python heap per definition); with this PR each kind is defined once per process, andtest_registrations_do_not_pile_up_type_objectsbounds the type objects after 800 registrations (it fails onmain, see the CI comment). The pre-registered timed A/B on the JVM wheel ran four times and every run was invalid by its A/A control, so no timing from it is reported; the outcome is in the comment below.Edited 2026-11-05: status updated (adopted as a leak fix, no speed claim); rebased on
main, the code is unchanged.🤖 Generated with Claude Code