Skip to content

Add HTTP endpoint to configure gRPC#3330

Draft
andystaples wants to merge 1 commit intodevfrom
andystaples/add-set-grpc-endpoint
Draft

Add HTTP endpoint to configure gRPC#3330
andystaples wants to merge 1 commit intodevfrom
andystaples/add-set-grpc-endpoint

Conversation

@andystaples
Copy link
Collaborator

Adds an HTTP endpoint that allows clients to configure the extension for gRPC. Useful in the case when function metadata is not a viable option for HTTP vs gRPC detection, like legacy function.json-based programming models without worker indexing.


Project checklist

  • Documentation changes are not required
    • Otherwise: Documentation PR is ready to merge and referenced in pending_docs.md
  • Release notes are not required for the next release
    • Otherwise: Notes added to release_notes.md
  • Backport is not required
    • Otherwise: Backport tracked by issue/PR #issue_or_pr
  • All required tests have been added/updated (unit tests, E2E tests)
  • No extra work is required to be leveraged by OutOfProc SDKs
    • Otherwise: Work tracked here: #issue_or_pr_in_each_sdk
  • No change to the version of the WebJobs.Extensions.DurableTask package
    • Otherwise: Major/minor updates are reflected in /src/Worker.Extensions.DurableTask/AssemblyInfo.cs
  • No EventIds were added to EventSource logs
  • This change should be added to the v2.x branch
    • Otherwise: This change applies exclusively to WebJobs.Extensions.DurableTask v3.x and will be retained only in the dev and main branches
  • Breaking change?
    • If yes:
      • Impact:
      • Migration guidance:

AI-assisted code disclosure (required)

Was an AI tool used? (select one)

  • No
  • Yes, AI helped write parts of this PR (e.g., GitHub Copilot)
  • Yes, an AI agent generated most of this PR

If AI was used:

  • Tool(s):
  • AI-assisted areas/files:
  • What you changed after AI output:

AI verification (required if AI was used):

  • I understand the code and can explain it
  • I verified referenced APIs/types exist and are correct
  • I reviewed edge cases/failure paths (timeouts, retries, cancellation, exceptions)
  • I reviewed concurrency/async behavior
  • I checked for unintended breaking or behavior changes

Testing

Automated tests

  • Result: Passed / Failed (link logs if failed)

Manual validation (only if runtime/behavior changed)

  • Environment (OS, .NET version, components):
  • Steps + observed results:
    1.
    2.
    3.
  • Evidence (optional):

Notes for reviewers

  • N/A

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds an HTTP endpoint to allow runtime configuration of the Durable Task extension for gRPC protocol. This addresses scenarios where function metadata is unavailable for protocol detection, particularly in legacy function.json-based programming models without worker indexing.

Changes:

  • Adds a new /management/{configureGrpc} HTTP endpoint that clears cached task hub workers and configures gRPC protocol
  • Refactors GetClient method into CreateClientAttributeFromRequest and GetClient for better separation of concerns
  • Adds ClearCachedTaskHubWorker method to DurableTaskExtension for invalidating cached workers

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 8 comments.

File Description
src/WebJobs.Extensions.DurableTask/HttpApiHandler.cs Adds new management endpoint route, handler method, and refactors client attribute creation logic
src/WebJobs.Extensions.DurableTask/DurableTaskExtension.cs Adds method to clear cached task hub worker before reconfiguring protocol

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +506 to +507
this.taskHubWorker.Dispose();
this.taskHubWorker = null;
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ClearCachedTaskHubWorker method will throw a NullReferenceException if taskHubWorker is null when Dispose is called. Consider adding a null check before disposing, or using the null-conditional operator.

Suggested change
this.taskHubWorker.Dispose();
this.taskHubWorker = null;
if (this.taskHubWorker != null)
{
this.taskHubWorker.Dispose();
this.taskHubWorker = null;
}

Copilot uses AI. Check for mistakes.
Comment on lines +506 to +507
this.taskHubWorker.Dispose();
this.taskHubWorker = null;
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ClearCachedTaskHubWorker method accesses and modifies the taskHubWorker field without synchronization, while other methods like ConfigureForGrpcProtocol use protocolLockObject for thread safety. This creates a potential race condition where multiple threads could simultaneously access or modify taskHubWorker. Consider adding lock protection around this operation using the same protocolLockObject to maintain consistency with the existing synchronization pattern.

Suggested change
this.taskHubWorker.Dispose();
this.taskHubWorker = null;
lock (this.protocolLockObject)
{
if (this.taskHubWorker != null)
{
this.taskHubWorker.Dispose();
this.taskHubWorker = null;
}
}

Copilot uses AI. Check for mistakes.
Comment on lines +742 to +753
private async Task<HttpResponseMessage> HandleConfigureGrpcRequest(HttpRequestMessage request)
{
this.config.ClearCachedTaskHubWorker();
this.config.ConfigureForGrpcProtocol();

var attribute = this.CreateClientAttributeFromRequest(request);
IDurableOrchestrationClient client = this.GetClient(request, attribute);
var bindings = new BindingHelper(this.config);
string clientString = bindings.DurableOrchestrationClientToString(client, attribute);

return request.CreateResponse(HttpStatusCode.OK, clientString);
}
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The HandleConfigureGrpcRequest endpoint does not validate the HTTP method being used. Looking at other endpoints in this file (lines 315, 344, 398, 431), they typically check for specific HTTP methods and return HttpStatusCode.NotFound for unsupported methods. Consider adding a check to ensure this endpoint only responds to the appropriate HTTP method (likely POST given it modifies state).

Copilot uses AI. Check for mistakes.
Comment on lines +742 to +753
private async Task<HttpResponseMessage> HandleConfigureGrpcRequest(HttpRequestMessage request)
{
this.config.ClearCachedTaskHubWorker();
this.config.ConfigureForGrpcProtocol();

var attribute = this.CreateClientAttributeFromRequest(request);
IDurableOrchestrationClient client = this.GetClient(request, attribute);
var bindings = new BindingHelper(this.config);
string clientString = bindings.DurableOrchestrationClientToString(client, attribute);

return request.CreateResponse(HttpStatusCode.OK, clientString);
}
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new HandleConfigureGrpcRequest endpoint lacks test coverage. The HttpApiHandlerTests.cs file contains comprehensive tests for other HTTP endpoints in this class. Consider adding tests to verify: the endpoint correctly clears the cached task hub worker, configures gRPC protocol, returns the expected HTTP status code, and returns the correct client string format.

Copilot uses AI. Check for mistakes.
Comment on lines +742 to +753
private async Task<HttpResponseMessage> HandleConfigureGrpcRequest(HttpRequestMessage request)
{
this.config.ClearCachedTaskHubWorker();
this.config.ConfigureForGrpcProtocol();

var attribute = this.CreateClientAttributeFromRequest(request);
IDurableOrchestrationClient client = this.GetClient(request, attribute);
var bindings = new BindingHelper(this.config);
string clientString = bindings.DurableOrchestrationClientToString(client, attribute);

return request.CreateResponse(HttpStatusCode.OK, clientString);
}
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is marked as async but contains no await operations. The async keyword is unnecessary here and creates a state machine overhead for no benefit. Consider removing the async keyword and returning Task.FromResult or directly wrapping the synchronous result, which would improve performance.

Copilot uses AI. Check for mistakes.
Comment on lines +742 to +753
private async Task<HttpResponseMessage> HandleConfigureGrpcRequest(HttpRequestMessage request)
{
this.config.ClearCachedTaskHubWorker();
this.config.ConfigureForGrpcProtocol();

var attribute = this.CreateClientAttributeFromRequest(request);
IDurableOrchestrationClient client = this.GetClient(request, attribute);
var bindings = new BindingHelper(this.config);
string clientString = bindings.DurableOrchestrationClientToString(client, attribute);

return request.CreateResponse(HttpStatusCode.OK, clientString);
}
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new HandleConfigureGrpcRequest endpoint performs a significant configuration change (clearing cached task hub worker and reconfiguring protocol) but has no apparent authentication or authorization checks. This endpoint could potentially be exploited if exposed to unauthorized users, allowing them to disrupt service by repeatedly clearing the cached worker or changing the protocol configuration. Consider adding appropriate security measures to restrict access to this administrative endpoint, similar to how other management endpoints might be protected.

Copilot uses AI. Check for mistakes.

private static TemplateMatcher GetConfigureGrpcRoute()
{
return new TemplateMatcher(TemplateParser.Parse($"{ManagementControllerSegment}{{{ConfigureGrpcRouteParameter}}}"), new RouteValueDictionary());
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The route template includes a route parameter {ConfigureGrpcRouteParameter} which is never accessed or used in the HandleConfigureGrpcRequest method. This creates an inconsistent API where the route expects /management/{configureGrpc} with a variable segment, but that value is ignored. Comparing with MakePrimaryRoute (line 187-189), which uses a fixed path without parameters, the ConfigureGrpc route should likely be a fixed path like /management/configureGrpc instead. Either remove the route parameter to make it a fixed path, or document why the parameter exists and what valid values it accepts.

Suggested change
return new TemplateMatcher(TemplateParser.Parse($"{ManagementControllerSegment}{{{ConfigureGrpcRouteParameter}}}"), new RouteValueDictionary());
return new TemplateMatcher(TemplateParser.Parse($"{ManagementControllerSegment}{ConfigureGrpcRouteParameter}"), new RouteValueDictionary());

Copilot uses AI. Check for mistakes.
Comment on lines +1231 to +1237
if (taskHub is null
&& key.Equals(TaskHubParameter, StringComparison.OrdinalIgnoreCase)
&& !string.IsNullOrWhiteSpace(pairs[key]))
{
taskHub = pairs[key];
}
else if (connectionName == null
else if (connectionName is null
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The null comparison pattern has been changed from == null to is null. However, the codebase predominantly uses == null for null checks (see lines 367, 383, 590, 629, 675, 844, 868, 892, 1064, 1102, 1195). For consistency with the existing codebase conventions, consider using == null instead of is null.

Copilot uses AI. Check for mistakes.
@andystaples andystaples marked this pull request as draft January 30, 2026 22:20
@andystaples
Copy link
Collaborator Author

Unsolved issue: the TriggerValueType for orchestrators is determined using OutOfProcProtocol set in ConfigureForGrpcProtocol(). In other use cases of ConfigureForGrpcProtocol (using function metadata) the protocol is set before TriggerValueType is accessed by WebJobs and there is no conflict, however, if this new endpoint is hit, WebJobs will continue using the wrong type for orchestration bindings. Need to come up with a way around this if this solution is to be properly considered

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants