Skip to content

JVM async executor: define each JPype proxy class once, not once per callback #238

Open

Description

@tae898

The JVM build's AsyncExecutor defines a new @jpype.JImplements class for every callback it hands to the engine. create_record, command, query, new_edge, new_edge_by_keys, scan_type, transaction, on_ok and on_error each go through a _create_*_callback helper (src/arcadedb/async_executor.py at 1b315c1, lines 348 to 357 and 964 to 1083), and each helper declares its class inside the call. JPype builds a Python type, a Java proxy class and a method table for each of those definitions, and keeps them for the life of the process.

src/arcadedb/callbacks.py already solved the same problem for Python functions in SQL and record events: the proxy classes are defined once and the per-registration state lives on the instance (its comment records about 6 KB of Python heap per registration before the cache). The executor was not changed with it.

The cost shows in the executor's own docs: the command docstring says a Python callback costs about 100 us per operation against about 8 us without one, and the project docs quote about 104 us against 5.5 us per completion. Those are documentation figures, not an ab.py measurement. How much of that gap is the class definition and how much is the crossing itself (Java thread to Python under the GIL) is not known yet, so this issue claims a cut in class definitions and type objects, and no speedup.

Proposed change, Python only, no Java: define each proxy class once per process under a lock (the callbacks.py pattern) and pass the Python callables to the instance constructor. The callbacks, their arguments and the order they run in do not change.

Verification plan:

  • Step 0 probe, untimed first: type objects and class definitions after 1,000 registrations, registration cost per callback, completions per second with and without callbacks, on main and on the branch.
  • Tests that fail on main: class definitions are counted (once per kind across executors), type objects do not pile up, concurrent first use defines one class, and callbacks that share a class still get their own call's arguments in submission order.
  • The existing async and concurrency tests run unchanged.
  • A timed A/B on the JVM wheel (one library, Python-only overlay) before anything is said about speed, with the rule written before the first run.

Happy to adjust the shape of the cache (module level versus per executor) if you prefer the other.

🤖 Generated with Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions