Skip to content

Commit

Permalink
Make durable client registration idempotent. (#2950)
Browse files Browse the repository at this point in the history
  • Loading branch information
jviau authored Nov 8, 2024
1 parent 79e2295 commit aa30752
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 56 deletions.
2 changes: 2 additions & 0 deletions release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
### New Features

- Fail fast if extendedSessionsEnabled set to 'true' for the worker type that doesn't support extended sessions (https://github.com/Azure/azure-functions-durable-extension/pull/2732).
- Added an `IFunctionsWorkerApplicationBuilder.ConfigureDurableExtension()` extension method for cases where auto-registration does not work (no source gen running). (#2950)

### Bug Fixes

- Fix custom connection name not working when using IDurableClientFactory.CreateClient() - contributed by [@hctan](https://github.com/hctan)
- Made durable extension for isolated worker configuration idempotent, allowing multiple calls safely. (#2950)

### Breaking Changes

Expand Down
57 changes: 1 addition & 56 deletions src/Worker.Extensions.DurableTask/DurableTaskExtensionStartup.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using Azure.Core.Serialization;
using Microsoft.Azure.Functions.Worker.Core;
using Microsoft.Azure.Functions.Worker.Extensions.DurableTask;
using Microsoft.DurableTask;
using Microsoft.DurableTask.Client;
using Microsoft.DurableTask.Converters;
using Microsoft.DurableTask.Worker;
using Microsoft.DurableTask.Worker.Shims;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

[assembly: WorkerExtensionStartup(typeof(DurableTaskExtensionStartup))]

Expand All @@ -28,49 +16,6 @@ public sealed class DurableTaskExtensionStartup : WorkerExtensionStartup
/// <inheritdoc/>
public override void Configure(IFunctionsWorkerApplicationBuilder applicationBuilder)
{
applicationBuilder.Services.AddSingleton<FunctionsDurableClientProvider>();
applicationBuilder.Services.AddOptions<DurableTaskClientOptions>()
.Configure(options => options.EnableEntitySupport = true)
.PostConfigure<IServiceProvider>((opt, sp) =>
{
if (GetConverter(sp) is DataConverter converter)
{
opt.DataConverter = converter;
}
});

applicationBuilder.Services.AddOptions<DurableTaskWorkerOptions>()
.Configure(options => options.EnableEntitySupport = true)
.PostConfigure<IServiceProvider>((opt, sp) =>
{
if (GetConverter(sp) is DataConverter converter)
{
opt.DataConverter = converter;
}
});

applicationBuilder.Services.TryAddSingleton(sp =>
{
DurableTaskWorkerOptions options = sp.GetRequiredService<IOptions<DurableTaskWorkerOptions>>().Value;
ILoggerFactory factory = sp.GetRequiredService<ILoggerFactory>();
return new DurableTaskShimFactory(options, factory); // For GrpcOrchestrationRunner
});

applicationBuilder.Services.Configure<WorkerOptions>(o =>
{
o.InputConverters.Register<OrchestrationInputConverter>();
});

applicationBuilder.UseMiddleware<DurableTaskFunctionsMiddleware>();
}

private static DataConverter? GetConverter(IServiceProvider services)
{
// We intentionally do not consider a DataConverter in the DI provider, or if one was already set. This is to
// ensure serialization is consistent with the rest of Azure Functions. This is particularly important because
// TaskActivity bindings use ObjectSerializer directly for the time being. Due to this, allowing DataConverter
// to be set separately from ObjectSerializer would give an inconsistent serialization solution.
WorkerOptions? worker = services.GetRequiredService<IOptions<WorkerOptions>>()?.Value;
return worker?.Serializer is not null ? new ObjectConverterShim(worker.Serializer) : null;
applicationBuilder.ConfigureDurableExtension();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Linq;
using Azure.Core.Serialization;
using Microsoft.Azure.Functions.Worker.Core;
using Microsoft.Azure.Functions.Worker.Extensions.DurableTask;
using Microsoft.DurableTask;
using Microsoft.DurableTask.Client;
using Microsoft.DurableTask.Converters;
using Microsoft.DurableTask.Worker;
using Microsoft.DurableTask.Worker.Shims;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Microsoft.Azure.Functions.Worker;

/// <summary>
/// Extensions for <see cref="IFunctionsWorkerApplicationBuilder"/>.
/// </summary>
public static class FunctionsWorkerApplicationBuilderExtensions
{
/// <summary>
/// Configures the Durable Functions extension for the worker.
/// </summary>
/// <param name="builder">The builder to configure.</param>
/// <returns>The <paramref name="builder"/> for call chaining.</returns>
public static IFunctionsWorkerApplicationBuilder ConfigureDurableExtension(this IFunctionsWorkerApplicationBuilder builder)
{
if (builder is null)
{
throw new ArgumentNullException(nameof(builder));
}

builder.Services.TryAddSingleton<FunctionsDurableClientProvider>();
builder.Services.TryAddEnumerable(
ServiceDescriptor.Singleton<IConfigureOptions<DurableTaskClientOptions>, ConfigureClientOptions>());
builder.Services.TryAddEnumerable(
ServiceDescriptor.Singleton<IPostConfigureOptions<DurableTaskClientOptions>, PostConfigureClientOptions>());
builder.Services.TryAddEnumerable(
ServiceDescriptor.Singleton<IConfigureOptions<DurableTaskWorkerOptions>, ConfigureWorkerOptions>());
builder.Services.TryAddEnumerable(
ServiceDescriptor.Singleton<IPostConfigureOptions<DurableTaskWorkerOptions>, PostConfigureWorkerOptions>());

builder.Services.TryAddSingleton(sp =>
{
DurableTaskWorkerOptions options = sp.GetRequiredService<IOptions<DurableTaskWorkerOptions>>().Value;
ILoggerFactory factory = sp.GetRequiredService<ILoggerFactory>();
return new DurableTaskShimFactory(options, factory); // For GrpcOrchestrationRunner
});

builder.Services.TryAddEnumerable(
ServiceDescriptor.Singleton<IConfigureOptions<WorkerOptions>, ConfigureInputConverter>());
if (!builder.Services.Any(d => d.ServiceType == typeof(DurableTaskFunctionsMiddleware)))
{
builder.UseMiddleware<DurableTaskFunctionsMiddleware>();
}

return builder;
}

private class ConfigureInputConverter : IConfigureOptions<WorkerOptions>
{
public void Configure(WorkerOptions options)
{
options.InputConverters.Register<OrchestrationInputConverter>();
}
}

private class ConfigureClientOptions : IConfigureOptions<DurableTaskClientOptions>
{
public void Configure(DurableTaskClientOptions options)
{
options.EnableEntitySupport = true;
}
}

private class PostConfigureClientOptions : IPostConfigureOptions<DurableTaskClientOptions>
{
readonly IOptionsMonitor<WorkerOptions> workerOptions;

public PostConfigureClientOptions(IOptionsMonitor<WorkerOptions> workerOptions)
{
this.workerOptions = workerOptions;
}

public void PostConfigure(string name, DurableTaskClientOptions options)
{
if (this.workerOptions.Get(name).Serializer is { } serializer)
{
options.DataConverter = new ObjectConverterShim(serializer);
}
}
}

private class ConfigureWorkerOptions : IConfigureOptions<DurableTaskWorkerOptions>
{
public void Configure(DurableTaskWorkerOptions options)
{
options.EnableEntitySupport = true;
}
}

private class PostConfigureWorkerOptions : IPostConfigureOptions<DurableTaskWorkerOptions>
{
readonly IOptionsMonitor<WorkerOptions> workerOptions;

public PostConfigureWorkerOptions(IOptionsMonitor<WorkerOptions> workerOptions)
{
this.workerOptions = workerOptions;
}

public void PostConfigure(string name, DurableTaskWorkerOptions options)
{
if (this.workerOptions.Get(name).Serializer is { } serializer)
{
options.DataConverter = new ObjectConverterShim(serializer);
}
}
}
}

0 comments on commit aa30752

Please sign in to comment.