Follow-up from PR #282 (automated-reviewer thread, robustness/resource)
In the new durable-functions v4 provider, the app.client.* starter helpers wrap the user handler with convertToFunctionHandler (packages/azure-functions-durable/src/app-client.ts:260-264):
function convertToFunctionHandler(clientHandler) {
return (triggerInput, context) => {
const client = getClient(context); // <-- per invocation
return clientHandler(triggerInput, client, context);
};
}
getClient (src/get-client.ts) constructs a new DurableFunctionsClient on every call:
return new DurableFunctionsClient(asClientInput(bindingData));
DurableFunctionsClient extends TaskHubGrpcClient, and the core TaskHubGrpcClient eagerly opens a gRPC channel in its constructor (packages/durabletask-js/src/client/client.ts:146):
this._stub = new GrpcClient(resolvedHostAddress, resolvedOptions, resolvedUseTLS, resolvedCredentials).stub;
It also exposes stop() which closes the channel (client.ts:152-153), but the compat provider never calls it and does not cache/reuse the client.
Impact
Every client-starter invocation creates a new @grpc/grpc-js channel that is never closed. In a long-lived Functions worker process under sustained load this accumulates open channels / sockets and their keep-alive machinery — a slow resource leak. The v3 provider reused a single client via the durable-client input binding, so this is a regression in resource behavior.
Options
- Cache the
DurableFunctionsClient keyed by the resolved client-binding config (most bindings resolve to the same host/task-hub within a worker), reusing the channel across invocations.
- Construct the client once per registered function (at registration time) rather than per invocation, if the binding value is stable.
- If per-invocation construction must stay,
stop() the client when the invocation completes (adds teardown cost per call; least preferred).
Caveats to design around: binding data can in principle differ per invocation; a cache needs a bounded size / eviction and must stop() evicted channels; and gRPC channel sharing must stay compatible with per-call metadata (task hub / auth token) which is already generated per call via metadataGenerator.
Acceptance
- Client-starter invocations reuse a gRPC channel instead of opening one per call.
- No unbounded growth of open channels in a long-running worker (add a focused test/benchmark or a channel-count assertion if feasible).
Links
Follow-up to an automated-reviewer thread on PR #282.
Follow-up from PR #282 (automated-reviewer thread, robustness/resource)
In the new
durable-functionsv4 provider, theapp.client.*starter helpers wrap the user handler withconvertToFunctionHandler(packages/azure-functions-durable/src/app-client.ts:260-264):getClient(src/get-client.ts) constructs a newDurableFunctionsClienton every call:DurableFunctionsClient extends TaskHubGrpcClient, and the coreTaskHubGrpcClienteagerly opens a gRPC channel in its constructor (packages/durabletask-js/src/client/client.ts:146):It also exposes
stop()which closes the channel (client.ts:152-153), but the compat provider never calls it and does not cache/reuse the client.Impact
Every client-starter invocation creates a new
@grpc/grpc-jschannel that is never closed. In a long-lived Functions worker process under sustained load this accumulates open channels / sockets and their keep-alive machinery — a slow resource leak. The v3 provider reused a single client via the durable-client input binding, so this is a regression in resource behavior.Options
DurableFunctionsClientkeyed by the resolved client-binding config (most bindings resolve to the same host/task-hub within a worker), reusing the channel across invocations.stop()the client when the invocation completes (adds teardown cost per call; least preferred).Caveats to design around: binding data can in principle differ per invocation; a cache needs a bounded size / eviction and must
stop()evicted channels; and gRPC channel sharing must stay compatible with per-call metadata (task hub / auth token) which is already generated per call viametadataGenerator.Acceptance
Links
durable-functions@4.0.0— Azure Functions Durable provider on the gRPC core (+ core host helpers, E2E CI, and release pipeline) #282packages/azure-functions-durable/src/app-client.ts:260-264,src/get-client.tspackages/durabletask-js/src/client/client.ts:146(stub),:152-153(stop())Follow-up to an automated-reviewer thread on PR #282.