What is the issue?
The entity executor's method-signature cache is recreated for every operation in an entity batch, so it does not avoid repeated reflection.
What is the impact?
Entity-heavy workloads repeatedly execute inspect.signature() for the same entity operation. This is unnecessary CPU overhead, especially for batches containing repeated operations or high-throughput small entity requests.
Details about the issue including code reference
Relevant code:
|
results: list[pb.OperationResult] = [] |
|
for operation in req.operations: |
|
start_time = datetime.now(timezone.utc) |
|
executor = _EntityExecutor(self._registry, self._logger, self._data_converter) |
|
|
|
class _EntityExecutor: |
|
def __init__(self, registry: _Registry, logger: logging.Logger, |
|
data_converter: DataConverter): |
|
self._registry = registry |
|
self._logger = logger |
|
self._data_converter = data_converter |
|
self._entity_method_cache: dict[tuple[type, str], bool] = {} |
|
|
|
def execute( |
|
self, |
|
orchestration_id: str, |
|
entity_id: EntityInstanceId, |
|
operation: str, |
|
state: StateShim, |
|
encoded_input: str | None, |
|
) -> str | None: |
|
"""Executes an entity function and returns the serialized result, if any.""" |
|
self._logger.debug( |
|
f"{orchestration_id}: Executing entity '{entity_id}'..." |
|
) |
|
fn = self._registry.get_entity(entity_id.entity) |
|
if not fn: |
|
raise EntityNotRegisteredError( |
|
f"Entity function named '{entity_id.entity}' was not registered!" |
|
) |
|
|
|
input_type = type_discovery.entity_input_type(fn, operation, self._data_converter) if encoded_input else None |
|
entity_input = self._data_converter.deserialize(encoded_input, input_type) |
|
ctx = EntityContext(orchestration_id, operation, state, entity_id, self._data_converter) |
|
|
|
if isinstance(fn, type) and issubclass(fn, DurableEntity): |
|
entity_instance = fn() |
|
if not hasattr(entity_instance, operation): |
|
raise AttributeError(f"Entity '{entity_id}' does not have operation '{operation}'") |
|
method = getattr(entity_instance, operation) |
|
if not callable(method): |
|
raise TypeError(f"Entity operation '{operation}' is not callable") |
|
# Execute the entity method |
|
entity_instance._initialize_entity_context(ctx) # pyright: ignore[reportPrivateUsage] |
|
cache_key = (type(entity_instance), operation) |
|
has_required_param = self._entity_method_cache.get(cache_key) |
|
if has_required_param is None: |
|
sig = inspect.signature(method) |
|
has_required_param = any( |
|
p.default == inspect.Parameter.empty |
|
for p in sig.parameters.values() |
|
if p.kind not in (inspect.Parameter.VAR_POSITIONAL, |
|
inspect.Parameter.VAR_KEYWORD) |
|
) |
|
self._entity_method_cache[cache_key] = has_required_param |
_execute_entity_batch() creates _EntityExecutor inside its operation loop. _EntityExecutor initializes _entity_method_cache and uses it to cache the required-parameter check, but the cache is cold for every operation because the executor is new each time.
A potential or proposed solution
Keep executor lifetime independent from entity instance lifetime, but move the signature result to a bounded per-worker cache or create one executor for the full batch. The cache should safely support concurrent worker threads and preserve the current no-argument versus input-argument behavior for class-based entities.
What is the issue?
The entity executor's method-signature cache is recreated for every operation in an entity batch, so it does not avoid repeated reflection.
What is the impact?
Entity-heavy workloads repeatedly execute
inspect.signature()for the same entity operation. This is unnecessary CPU overhead, especially for batches containing repeated operations or high-throughput small entity requests.Details about the issue including code reference
Relevant code:
durabletask-python/durabletask/worker.py
Lines 1372 to 1376 in 55d8e0b
durabletask-python/durabletask/worker.py
Lines 3104 to 3153 in 55d8e0b
_execute_entity_batch()creates_EntityExecutorinside its operation loop._EntityExecutorinitializes_entity_method_cacheand uses it to cache the required-parameter check, but the cache is cold for every operation because the executor is new each time.A potential or proposed solution
Keep executor lifetime independent from entity instance lifetime, but move the signature result to a bounded per-worker cache or create one executor for the full batch. The cache should safely support concurrent worker threads and preserve the current no-argument versus input-argument behavior for class-based entities.