diff --git a/all.sln b/all.sln index e1c7377a4..e93f39c4b 100644 --- a/all.sln +++ b/all.sln @@ -199,6 +199,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.AI.Microsoft.Extension EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MicrosoftAiExtensionsDemo", "examples\AI\MicrosoftAiExtensionsDemo\MicrosoftAiExtensionsDemo.csproj", "{EDEEEAED-1856-4408-BAA6-A83E1B497B2A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.DistributedLock", "src\Dapr.DistributedLock\Dapr.DistributedLock.csproj", "{9BD12D26-AD9B-4C76-A97F-7A89B7276ABE}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DistributedLock", "DistributedLock", "{11D2CA0F-6D38-4DC7-AE06-C1DAE7FC1C20}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DistributedLock", "examples\DistributedLock\DistributedLock\DistributedLock.csproj", "{F2DFB0FE-DF35-4D94-9CC9-43212B1D6F93}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -527,6 +533,14 @@ Global {EDEEEAED-1856-4408-BAA6-A83E1B497B2A}.Debug|Any CPU.Build.0 = Debug|Any CPU {EDEEEAED-1856-4408-BAA6-A83E1B497B2A}.Release|Any CPU.ActiveCfg = Release|Any CPU {EDEEEAED-1856-4408-BAA6-A83E1B497B2A}.Release|Any CPU.Build.0 = Release|Any CPU + {9BD12D26-AD9B-4C76-A97F-7A89B7276ABE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9BD12D26-AD9B-4C76-A97F-7A89B7276ABE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9BD12D26-AD9B-4C76-A97F-7A89B7276ABE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9BD12D26-AD9B-4C76-A97F-7A89B7276ABE}.Release|Any CPU.Build.0 = Release|Any CPU + {F2DFB0FE-DF35-4D94-9CC9-43212B1D6F93}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F2DFB0FE-DF35-4D94-9CC9-43212B1D6F93}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F2DFB0FE-DF35-4D94-9CC9-43212B1D6F93}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F2DFB0FE-DF35-4D94-9CC9-43212B1D6F93}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -623,6 +637,9 @@ Global {AE9804A8-906C-4C3B-B2A8-41F4D3269C19} = {27C5D71D-0721-4221-9286-B94AB07B58CF} {CA680984-0D3A-40E5-88FD-E2A125C6A126} = {27C5D71D-0721-4221-9286-B94AB07B58CF} {EDEEEAED-1856-4408-BAA6-A83E1B497B2A} = {3046DBF4-C2FF-4F3A-9176-E1C01E0A90E5} + {9BD12D26-AD9B-4C76-A97F-7A89B7276ABE} = {27C5D71D-0721-4221-9286-B94AB07B58CF} + {11D2CA0F-6D38-4DC7-AE06-C1DAE7FC1C20} = {D687DDC4-66C5-4667-9E3A-FD8B78ECAA78} + {F2DFB0FE-DF35-4D94-9CC9-43212B1D6F93} = {11D2CA0F-6D38-4DC7-AE06-C1DAE7FC1C20} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {65220BF2-EAE1-4CB2-AA58-EBE80768CB40} diff --git a/daprdocs/content/en/dotnet-sdk-docs/_index.md b/daprdocs/content/en/dotnet-sdk-docs/_index.md index 4448c859b..ffe51ce68 100644 --- a/daprdocs/content/en/dotnet-sdk-docs/_index.md +++ b/daprdocs/content/en/dotnet-sdk-docs/_index.md @@ -39,50 +39,26 @@ Put the Dapr .NET SDK to the test. Walk through the .NET quickstarts and tutoria ## Available packages -
-
-
-
Client
-

Create .NET clients that interact with a Dapr sidecar and other Dapr applications.

- -
-
-
-
-
Server
-

Write servers and services in .NET using the Dapr SDK. Includes support for ASP.NET.

- -
-
-
-
-
Actors
-

Create virtual actors with state, reminders/timers, and methods in .NET.

- -
-
-
-
-
Workflow
-

Create and manage workflows that work with other Dapr APIs in .NET.

- -
-
-
-
-
Jobs
-

Create and manage the scheduling and orchestration of jobs in .NET.

- -
-
-
-
-
AI
-

Create and manage AI operations in .NET

- -
-
-
+| Package Name | Documentation Link | Description | +|-----------------------------------------------------------------------------------------------------------|---------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------| +| [Dapr.Client](https://www.nuget.org/packages/Dapr.Client) | [Documentation]({{% ref dotnet-client %}}) | Create .NET clients that interact with a Dapr sidecar and other Dapr applications. | +| [Dapr.AI](https://www.nuget.org/packages/Dapr.AI) | [Documentation]({{% ref dotnet-ai %}}) | Create and manage AI operations in .NET. | +| [Dapr.AI.A2a](https://www.nuget.org/packages/Dapr.AI.A2a) | | Dapr SDK for implementing agent-to-agent operations using the [A2A](https://github.com/a2aproject/a2a-dotnet) framework. | +| [Dapr.AI.Microsoft.Extensions](https://www.nuget.org/packages/Dapr.AI.Microsoft.Extensions) | [Documentation]({{% ref dotnet-ai-extensions-howto %}}) | Easily interact with LLMs conversationally and using tooling via the Dapr Conversation building block. | +| [Dapr.AspNetCore](https://www.nuget.org/packages/Dapr.AspNetCore) | [Documentation]({{% ref dotnet-server %}}) | Write servers and services in .NET using the Dapr SDK. Includes support and utilities providing richer integration with ASP.NET Core. | +| [Dapr.Actors](https://www.nuget.org/packages/Dapr.Actors) | [Documentation]({{% ref dotnet-actors %}}) | Create virtual actors with state, reminders/timers, and methods. | +| [Dapr.Actors.AspNetCore](https://www.nuget.org/packages/Dapr.Actors) | [Documentation]({{% ref dotnet-actors %}}) | Create virtual actors with state, reminders/timers, and methods with rich integration with ASP.NET Core. | +| [Dapr.Actors.Analyzers](https://www.nuget.org/packages/Dapr.Actors.Analyzers) | [Documentation]({{% ref dotnet-guidance-source-generators %}}) | A collection of Roslyn source generators and analyzers for enabling better practices and preventing common errors when using Dapr Actors in .NET | +| [Dapr.Cryptography](https://www.nuget.org/packages/Dapr.Cryptography) | [Documentation]({{% dotnet-cryptography %}}) | Encrypt and decrypt streaming state of any size using Dapr's cryptography building block. | +| [Dapr.Jobs](https://www.nuget.org/packages/Dapr.Jobs) | [Documentation]({{% ref dotnet-jobs %}}) | Create and manage the scheduling and orchestration of jobs. | +| [Dapr.DistributedLocks](https://www.nuget.org/packages/Dapr.DistributedLocks) | [Documentation]({{% ref dotnet-distributed-lock %}}) | Create and manage distributed locks for managing exclusive resource access. | +| [Dapr.Extensions.Configuration](https://www.nuget.org/packages/Dapr.Extensions.Configuration) | | Dapr secret store configuration provider implementation for `Microsoft.Extensions.Configuration`. | +| [Dapr.PluggableComponents](https://www.nuget.org/packages/Dapr.PluggableComponents) | | Used to implement pluggable components with Dapr using .NET. | +| [Dapr.PluggableComponents.AspNetCore](https://www.nuget.org/packages/Dapr.PluggableComponents.AspNetCore) | | Implement pluggable components with Dapr using .NET with rich ASP.NET Core support. | +| [Dapr.PluggableComponents.Protos](https://www.nuget.org/packages/Dapr.PluggableComponents.Protos) | | **Note:** Developers needn't install this package directly in their applications. | +| [Dapr.Messaging](https://www.nuget.org/packages/Dapr.Messaging) | [Documentation]({{% ref dotnet-messaging %}}) | Build distributed applications using the Dapr Messaging SDK that utilize messaging components like streaming pub/sub subscriptions. | +| [Dapr.Workflow](https://www.nuget.org/packages/Dapr.Workflow) | [Documentation]({{% ref dotnet-workflow %}}) | Create and manage workflows that work with other Dapr APIs. | +| [Dapr.Workflow.Analyzers](https://www.nuget.org/packages/Dapr.Workflow.Analyzers) | [Documentation]({{% ref dotnet-guidance-source-generators %}}) | A collection of Roslyn source generators and analyzers for enabling better practices and preventing common errors when using Dapr Workflows in .NET | ## More information diff --git a/daprdocs/content/en/dotnet-sdk-docs/dotnet-ai/dotnet-ai-conversation-usage.md b/daprdocs/content/en/dotnet-sdk-docs/dotnet-ai/dotnet-ai-conversation-usage.md index 444a59a01..6fdec0f88 100644 --- a/daprdocs/content/en/dotnet-sdk-docs/dotnet-ai/dotnet-ai-conversation-usage.md +++ b/daprdocs/content/en/dotnet-sdk-docs/dotnet-ai/dotnet-ai-conversation-usage.md @@ -19,7 +19,7 @@ instance throughout your application. `DaprConversationClient` instances are thr This can be aided by utilizing the dependency injection functionality. The registration method supports registration as a singleton, a scoped instance or as transient (meaning it's recreated every time it's injected), but also enables -registration to utilize values from an `IConfiguration` or other injected service in a way that's impractical when +registration to utilize values from an `IConfiguration` or other injected services in a way that's impractical when creating the client from scratch in each of your classes. Avoid creating a `DaprConversationClient` for each operation. diff --git a/daprdocs/content/en/dotnet-sdk-docs/dotnet-distributed-lock/_index.md b/daprdocs/content/en/dotnet-sdk-docs/dotnet-distributed-lock/_index.md new file mode 100644 index 000000000..b8aad64e3 --- /dev/null +++ b/daprdocs/content/en/dotnet-sdk-docs/dotnet-distributed-lock/_index.md @@ -0,0 +1,143 @@ +--- +type: docs +title: "Dapr Distributed Lock .NET SDK" +linkTitle: "Distributed Lock" +weight: 61000 +description: Get up and running with the Dapr Distributed .NET SDK +--- + +With the Dapr Distributed Lock package, you can create and remove locks on resources to manage exclusivity across +your distributed applications. + +While this capability is implemented in both the `Dapr.Client` and `Dapr.DistributedLock` packages, the approach differs +slightly between them and a future release will see the `Dapr.Client` package be deprecated. It's recommended that new +implementations use the `Dapr.DistributedLock` package. This document will reflect the implementation in the +`Dapr.DistributedLock` package. + +## Lifetime management +A `DaprDistributedLockClient` is a version of the Dapr client that is dedicated to interacting with Dapr's distributed +lock API. It can be registered alongside a `DaprClient` and other Dapr clients without issue. + +It maintains access to networking resources in the form of TCP sockets used to communicate with the Dapr sidecar runtime. + +For best performance, it is recommended that you utilize the dependency injection container mechanisms provided with the +`Dapr.DistributedLock` package to provide easy access to an injected instance throughout your application. These injected +instances are thread-safe and intended to be used across different types within your application. Registration via +dependency injection can utilize values from an `IConfiguration` or other injected services in a way that's impractical +when creating the client from scratch in each of your classes. + +If you do opt to manually create a `DaprDistributedLockClient` instance, it is recommended that you use the `DaprClientBuilder` +to create the client. This will ensure that the client is properly configured to communicate with the Dapr sidecar runtime.` + +Avoid creawting a `DaprDistributedLockClient` for each operation. + +## Configuring a `DaprDistributedLockClient` via `DaprDistributedLockBuilder` + +A `DaprDistributedLockClient` can be configured by invoking methods on the `DaprDistributedLockBuilder` class before calling +`.Build()` to create the client itself. The settings for each `DaprDistributedLockClient` are separate and cannot be changed +after calling `.Build()`. + +```csharp +var daprDistributedLockClient = new DaprDistributedLockBuilder() + .UseDaprApiToken("abc123") // Optionally specify the API token used to authenticate to other Dapr sidecars + .Build(); +``` + +The `DaprDistributedLockBuilder` contains settings for: + +- The HTTP endpoint of the Dapr sidecar +- The gRPC endpoint of the Dapr sidecar +- The `JsonSerializerOptions` object used to configure JSON serialization +- The `GrpcChannelOptions` object used to configure gRPC +- The API token used to authenticate requests to the sidecar +- The factory method used to create the `HttpClient` instance used by the SDK +- The timeout used for the `HttpClient` instance when making requests to the sidecar + +The SDK will read the following environment variables to configure the default values: + +- `DAPR_HTTP_ENDPOINT`: used to find the HTTP endpoint of the Dapr sidecar, example: `https://dapr-api.mycompany.com` +- `DAPR_GRPC_ENDPOINT`: used to find the gRPC endpoint of the Dapr sidecar, example: `https://dapr-grpc-api.mycompany.com` +- `DAPR_HTTP_PORT`: if `DAPR_HTTP_ENDPOINT` is not set, this is used to find the HTTP local endpoint of the Dapr sidecar +- `DAPR_GRPC_PORT`: if `DAPR_GRPC_ENDPOINT` is not set, this is used to find the gRPC local endpoint of the Dapr sidecar +- `DAPR_API_TOKEN`: used to set the API token + +### Configuring gRPC channel options + +Dapr's use of `CancellationToken` for cancellation relies on the configuration of the gRPC channel options. If you need +to configure these options yourself, make sure to enable the [ThrowOperationCanceledOnCancellation setting](https://grpc.github.io/grpc/csharp-dotnet/api/Grpc.Net.Client.GrpcChannelOptions.html#Grpc_Net_Client_GrpcChannelOptions_ThrowOperationCanceledOnCancellation). + +```cs +var daprDistributedLockClient = new DaprDistributedLockBuilder() + .UseGrpcChannelOptions(new GrpcChannelOptions { ... ThrowOperationCanceledOnCancellation = true }) + .Build(); +``` + +## Using cancellation with `DaprDistributedLockClient` + +The APIs on `DaprDistributedLockClient` perform asynchronous operations and accept an optional `CancellationToken` parameter. This +follows a standard .NET practice for cancellable operations. Note that when cancellation occurs, there is no guarantee that +the remote endpoint stops processing the request, only that the client has stopped waiting for completion. + +When an operation is cancelled, it will throw an `OperationCancelledException`. + +## Configuring `DaprDistributedLockClient` via dependency injection + +Using the built-in extension methods for registering the `DaprDistributedLockClient` in a dependency injection container can +provide the benefit of registering the long-lived service a single time, centralize complex configuration and improve +performance by ensuring similarly long-lived resources are re-purposed when possible (e.g. `HttpClient` instances). + +There are three overloads available to give the developer the greatest flexibility in configuring the client for their +scenario. Each of these will register the `IHttpClientFactory` on your behalf if not already registered, and configure +the `DaprDistributedLockBuilder` to use it when creating the `HttpClient` instance in order to re-use the same instance as +much as possible and avoid socket exhaustion and other issues. + +In the first approach, there's no configuration done by the developer and the `DaprDistributedLockClient` is configured with the +default settings. + +```cs +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddDaprDistributedLock(); //Registers the `DaprDistributedLockClient` to be injected as needed +var app = builder.Build(); +``` + +Sometimes the developer will need to configure the created client using the various configuration options detailed +above. This is done through an overload that passes in the `DaprDistributedLockBuilder` and exposes methods for configuring +the necessary options. + +```cs +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddDaprDistributedLock((_, daprDistributedLockBuilder) => { + //Set the API token + daprDistributedLockBuilder.UseDaprApiToken("abc123"); + //Specify a non-standard HTTP endpoint + daprDistributedLockBuilder.UseHttpEndpoint("http://dapr.my-company.com"); +}); + +var app = builder.Build(); +``` + +Finally, it's possible that the developer may need to retrieve information from another service in order to populate +these configuration values. That value may be provided from a `DaprClient` instance, a vendor-specific SDK or some +local service, but as long as it's also registered in DI, it can be injected into this configuration operation via the +last overload: + +```cs +var builder = WebApplication.CreateBuilder(args); + +//Register a fictional service that retrieves secrets from somewhere +builder.Services.AddSingleton(); + +builder.Services.AddDaprDistributedLock((serviceProvider, daprDistributedLockBuilder) => { + //Retrieve an instance of the `SecretService` from the service provider + var secretService = serviceProvider.GetRequiredService(); + var daprApiToken = secretService.GetSecret("DaprApiToken").Value; + + //Configure the `DaprDistributedLockBuilder` + daprDistributedLockBuilder.UseDaprApiToken(daprApiToken); +}); + +var app = builder.Build(); +``` + diff --git a/daprdocs/content/en/dotnet-sdk-docs/dotnet-distributed-lock/dotnet-distributedlock-howto.md b/daprdocs/content/en/dotnet-sdk-docs/dotnet-distributed-lock/dotnet-distributedlock-howto.md new file mode 100644 index 000000000..89fbe0afd --- /dev/null +++ b/daprdocs/content/en/dotnet-sdk-docs/dotnet-distributed-lock/dotnet-distributedlock-howto.md @@ -0,0 +1,80 @@ +--- +type: docs +title: "How to: Create and use Dapr Distributed Lock in the .NET SDK" +linkTitle: "How to: Use the Distributed Lock client" +weight: 61050 +description: Learn how to create and use the Dapr Distributed Lock client using the .NET SDK +--- + +## Prerequisites +- [.NET 8](https://dotnet.microsoft.com/download/dotnet/8.0), or [.NET 9](https://dotnet.microsoft.com/download/dotnet/9.0) installed +- [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/) +- [Initialized Dapr environment](https://docs.dapr.io/getting-started/install-dapr-selfhost) + +## Installation + +To get started with the Dapr Distributed lock .NET SDK client, install the [Dapr.Distributed Lock package](https://www.nuget.org/packages/Dapr.DistributedLock) from NuGet: +```sh +dotnet add package Dapr.DistributedLock +``` + +A `DaprDistributedLockClient` maintains access to networking resources in the form of TCP sockets used to communicate with the Dapr sidecar. + +### Dependency Injection + +The `AddDaprDistributedLock()` method will register the Dapr client ASP.NET Core dependency injection and is the recommended approach +for using this package. This method accepts an optional options delegate for configuring the `DaprDistributedLockClient` and a +`ServiceLifetime` argument, allowing you to specify a different lifetime for the registered services instead of the default `Singleton` +value. + +The following example assumes all default values are acceptable and is sufficient to register the `DaprDistributedLockClient`: + +```csharp +services.AddDaprDistributedLock(); +``` + +The optional configuration delegate is used to configure the `DaprDistributedLockClient` by specifying options on the +`DaprDistributedLockBuilder` as in the following example: +```csharp +services.AddSingleton(); +services.AddDaprDistributedLock((serviceProvider, clientBuilder) => { + //Inject a service to source a value from + var optionsProvider = serviceProvider.GetRequiredService(); + var standardTimeout = optionsProvider.GetStandardTimeout(); + + //Configure the value on the client builder + clientBuilder.UseTimeout(standardTimeout); +}); +``` + +### Manual Instantiation +Rather than using dependency injection, a `DaprDistributedLockClient` can also be built using the static client builder. + +For best performance, create a single long-lived instance of `DaprDistributedLockClient` and provide access to that shared instance throughout +your application. `DaprDistributedLockClient` instances are thread-safe and intended to be shared. + +Avoid creating a `DaprDistributedLockClient` per-operation. + +A `DaprDistributedLockClient` can be configured by invoking methods on the `DaprDistributedLockBuilder` class before calling `.Build()` +to create the client. The settings for each `DaprDistributedLockClient` are separate and cannot be changed after calling `.Build()`. + +```csharp +var daprDistributedLockClient = new DaprDistributedLockBuilder() + .UseJsonSerializerSettings( ... ) //Configure JSON serializer + .Build(); +``` + +See the .NET [documentation here]({{% ref dotnet-distributed-lock %}}) for more information about the options available +when configuring the Dapr Distributed Lock client via the builder. + +## Try it out +Put the Dapr Distributed Lock .NET SDK to the test. Walk through the samples to see Dapr in action: + +| SDK Samples | Description | +| ----------- | ----------- | +| [SDK samples](https://github.com/dapr/dotnet-sdk/tree/master/examples) | Clone the SDK repo to try out some examples and get started. | + +## Building Blocks + +This part of the .NET SDK allows you to interface with the Distributed Lock API to place and remove locks for managing +resource exclusivity across your distributed applications. diff --git a/examples/DistributedLock/DistributedLock/DistributedLock.csproj b/examples/DistributedLock/DistributedLock/DistributedLock.csproj new file mode 100644 index 000000000..8460d7a15 --- /dev/null +++ b/examples/DistributedLock/DistributedLock/DistributedLock.csproj @@ -0,0 +1,18 @@ + + + + Exe + enable + enable + + + + + + + + + + + + diff --git a/examples/DistributedLock/DistributedLock/Program.cs b/examples/DistributedLock/DistributedLock/Program.cs new file mode 100644 index 000000000..1f394b48f --- /dev/null +++ b/examples/DistributedLock/DistributedLock/Program.cs @@ -0,0 +1,57 @@ +// ------------------------------------------------------------------------ +// Copyright 2025 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using Dapr.DistributedLock; +using Dapr.DistributedLock.Extensions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +var builder = Host.CreateDefaultBuilder(args); + +builder.ConfigureServices(services => +{ + services.AddDaprDistributedLock(); + services.AddLogging(); +}); +builder.ConfigureLogging((_, loggingBuilder) => +{ + loggingBuilder.AddConsole(); +}); + +var app = builder.Build(); + +using var scope = app.Services.CreateScope(); +var logger = scope.ServiceProvider.GetRequiredService>(); +var distributedLockClient = scope.ServiceProvider.GetRequiredService(); + +// Locks are disposable and will automatically unlock at the end of a using scope +const string resourceName = "myFile.txt"; +const string appId = "myApp"; + +logger.LogInformation("Attempting to lock: {fileName}", resourceName); +await using var fileLock = await distributedLockClient.TryLockAsync("redislock", resourceName, appId, 60); +if (fileLock is not null) +{ + // Lock was acquired successfully + logger.LogInformation("Successfully locked file: {fileName}", resourceName); + + // Simulate processing the file + await Task.Delay(TimeSpan.FromSeconds(3)); + + logger.LogInformation("Done processing: {fileName}", resourceName); +} +else +{ + logger.LogInformation("Failed to lock: {fileName}", resourceName); +} diff --git a/examples/DistributedLock/components/redis-lock.yaml b/examples/DistributedLock/components/redis-lock.yaml new file mode 100644 index 000000000..4da37e5d0 --- /dev/null +++ b/examples/DistributedLock/components/redis-lock.yaml @@ -0,0 +1,12 @@ +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: redislock +spec: + type: lock.redis + version: v1 + metadata: + - name: redisHost + value: localhost:6379 + - name: redisPassword + value: "" \ No newline at end of file diff --git a/src/Dapr.Common/AssemblyInfo.cs b/src/Dapr.Common/AssemblyInfo.cs index f4dccf424..f6ee4249a 100644 --- a/src/Dapr.Common/AssemblyInfo.cs +++ b/src/Dapr.Common/AssemblyInfo.cs @@ -20,6 +20,7 @@ [assembly: InternalsVisibleTo("Dapr.AspNetCore, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")] [assembly: InternalsVisibleTo("Dapr.Client, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")] [assembly: InternalsVisibleTo("Dapr.Cryptography, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")] +[assembly: InternalsVisibleTo("Dapr.DistributedLock, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")] [assembly: InternalsVisibleTo("Dapr.Jobs, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")] [assembly: InternalsVisibleTo("Dapr.Messaging, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")] [assembly: InternalsVisibleTo("Dapr.Extensions.Configuration, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")] @@ -37,6 +38,7 @@ [assembly: InternalsVisibleTo("Dapr.Client.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")] [assembly: InternalsVisibleTo("Dapr.Cryptography.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")] [assembly: InternalsVisibleTo("Dapr.Common.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")] +[assembly: InternalsVisibleTo("Dapr.DistributedLock.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")] [assembly: InternalsVisibleTo("Dapr.E2E.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")] [assembly: InternalsVisibleTo("Dapr.E2E.Test.Actors, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")] [assembly: InternalsVisibleTo("Dapr.E2E.Test.Actors.Generators, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")] diff --git a/src/Dapr.DistributedLock/Dapr.DistributedLock.csproj b/src/Dapr.DistributedLock/Dapr.DistributedLock.csproj new file mode 100644 index 000000000..13c33d270 --- /dev/null +++ b/src/Dapr.DistributedLock/Dapr.DistributedLock.csproj @@ -0,0 +1,28 @@ + + + + enable + enable + Dapr.DistributedLock + Dapr Distributed Lock SDK + Dapr Distributed Lock SDK for facilitating operations that require a exclusive access to a shared resource. + alpha + + + + + + + + + + + + + + + + + + + diff --git a/src/Dapr.DistributedLock/DaprDistributedLockClient.cs b/src/Dapr.DistributedLock/DaprDistributedLockClient.cs new file mode 100644 index 000000000..400c1d4fe --- /dev/null +++ b/src/Dapr.DistributedLock/DaprDistributedLockClient.cs @@ -0,0 +1,68 @@ +// ------------------------------------------------------------------------ +// Copyright 2025 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using System.Diagnostics.CodeAnalysis; +using Dapr.DistributedLock.Models; +using Autogenerated = Dapr.Client.Autogen.Grpc.v1; + +namespace Dapr.DistributedLock; + +/// +/// A client for performing distributed locking operations with Dapr. +/// +[Experimental("DAPR_DISTRIBUTEDLOCK", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/distributed-lock/distributed-lock-api-overview/")] +public abstract class DaprDistributedLockClient : IDaprDistributedLockClient +{ + private bool disposed; + private readonly Autogenerated.Dapr.DaprClient client; + private readonly string daprApiToken; + + /// + /// A client for performing distributed locking operations with Dapr. + /// + protected DaprDistributedLockClient(Autogenerated.Dapr.DaprClient client, HttpClient _, string daprApiToken) + { + this.client = client; + this.daprApiToken = daprApiToken; + } + + internal Autogenerated.Dapr.DaprClient Client => client; + internal string DaprApiToken => daprApiToken; + + /// + public abstract Task TryLockAsync(string storeName, string resourceId, string lockOwner, + int expiryInSeconds, + CancellationToken cancellationToken = default); + + /// + public abstract Task TryUnlockAsync(string storeName, string resourceId, string lockOwner, + CancellationToken cancellationToken = default); + + /// + public void Dispose() + { + if (!this.disposed) + { + Dispose(disposing: true); + this.disposed = true; + } + } + + /// + /// Disposes the resources associcated with the object. + /// + /// true if called by a call to the Dispose method; otherwise false. + protected virtual void Dispose(bool disposing) + { + } +} diff --git a/src/Dapr.DistributedLock/DaprDistributedLockGrpcClient.cs b/src/Dapr.DistributedLock/DaprDistributedLockGrpcClient.cs new file mode 100644 index 000000000..56e985b89 --- /dev/null +++ b/src/Dapr.DistributedLock/DaprDistributedLockGrpcClient.cs @@ -0,0 +1,115 @@ +// ------------------------------------------------------------------------ +// Copyright 2025 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using System.Diagnostics.CodeAnalysis; +using Dapr.Common; +using Dapr.DistributedLock.Models; +using Grpc.Core; +using Autogenerated = Dapr.Client.Autogen.Grpc.v1; + +namespace Dapr.DistributedLock; + +/// +/// A client for performing distributed locking operations with Dapr. +/// +[Experimental("DAPR_DISTRIBUTEDLOCK", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/distributed-lock/distributed-lock-api-overview/")] +internal sealed class DaprDistributedLockGrpcClient : DaprDistributedLockClient +{ + /// + /// A client for performing distributed locking operations with Dapr. + /// + public DaprDistributedLockGrpcClient(Autogenerated.Dapr.DaprClient client, HttpClient httpClient, string daprApiToken) : base(client, httpClient, daprApiToken) + { + } + + /// + public override async Task TryLockAsync(string storeName, string resourceId, string lockOwner, + int expiryInSeconds, + CancellationToken cancellationToken = default) + { + ArgumentException.ThrowIfNullOrEmpty(storeName); + ArgumentException.ThrowIfNullOrEmpty(resourceId); + ArgumentException.ThrowIfNullOrEmpty(lockOwner); + + if (expiryInSeconds <= 0) + { + throw new ArgumentOutOfRangeException(nameof(expiryInSeconds), expiryInSeconds, "The value cannot be less than or equal to zero."); + } + + var request = new Autogenerated.TryLockRequest + { + StoreName = storeName, ResourceId = resourceId, LockOwner = lockOwner, ExpiryInSeconds = expiryInSeconds + }; + + try + { + var grpcCallOptions = + DaprClientUtilities.ConfigureGrpcCallOptions(typeof(DaprDistributedLockClient).Assembly, this.DaprApiToken, + cancellationToken); + var response = await this.Client.TryLockAlpha1Async(request, grpcCallOptions); + return !response.Success + ? null + : new LockResponse(this) { StoreName = storeName, ResourceId = resourceId, LockOwner = lockOwner }; + } + catch (RpcException ex) + { + throw new DaprException("Lock operation failed: the Dapr endpoint indicated a failure. See InnerException for details.", ex); + } + } + + /// + public override async Task TryUnlockAsync(string storeName, string resourceId, string lockOwner, + CancellationToken cancellationToken = default) + { + ArgumentException.ThrowIfNullOrEmpty(storeName); + ArgumentException.ThrowIfNullOrEmpty(resourceId); + ArgumentException.ThrowIfNullOrEmpty(lockOwner); + + var request = new Autogenerated.UnlockRequest + { + StoreName = storeName, ResourceId = resourceId, LockOwner = lockOwner + }; + + Autogenerated.UnlockResponse response; + try + { + var grpcCallOptions = + DaprClientUtilities.ConfigureGrpcCallOptions(typeof(DaprDistributedLockClient).Assembly, this.DaprApiToken, + cancellationToken); + response = await this.Client.UnlockAlpha1Async(request, grpcCallOptions); + } + catch (RpcException ex) + { + throw new DaprException( + "Lock operation failed: the Dapr endpoint indicated a failure. See InnerException for details.", ex); + } + + var status = GetUnlockStatus(response.Status); + return new UnlockResponse(status); + } + + /// + /// Maps the to the equivalent value. + /// + /// The status value to map. + /// The properly mapped value. + private static LockStatus GetUnlockStatus(Autogenerated.UnlockResponse.Types.Status status) + => status switch + { + Autogenerated.UnlockResponse.Types.Status.Success => LockStatus.Success, + Autogenerated.UnlockResponse.Types.Status.LockDoesNotExist => LockStatus.LockDoesNotExist, + Autogenerated.UnlockResponse.Types.Status.LockBelongsToOthers => LockStatus.LockBelongsToOthers, + Autogenerated.UnlockResponse.Types.Status.InternalError => LockStatus.InternalError, + _ => throw new ArgumentOutOfRangeException(nameof(status), status, $"Status '{status}' is not supported") + }; +} diff --git a/src/Dapr.DistributedLock/Extensions/DaprDistributedLockBuilder.cs b/src/Dapr.DistributedLock/Extensions/DaprDistributedLockBuilder.cs new file mode 100644 index 000000000..666b0117b --- /dev/null +++ b/src/Dapr.DistributedLock/Extensions/DaprDistributedLockBuilder.cs @@ -0,0 +1,38 @@ +// ------------------------------------------------------------------------ +// Copyright 2025 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using System.Diagnostics.CodeAnalysis; +using Dapr.Common; +using Microsoft.Extensions.Configuration; +using Autogenerated = Dapr.Client.Autogen.Grpc.v1; + +namespace Dapr.DistributedLock.Extensions; + +/// +/// Builds a . +/// +/// +[Experimental("DAPR_DISTRIBUTEDLOCK", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/distributed-lock/distributed-lock-api-overview/")] +public sealed class DaprDistributedLockBuilder(IConfiguration? configuration = null) : DaprGenericClientBuilder(configuration) +{ + /// + /// Builds the client instance from the properties of the builder. + /// + /// The dapr client instance. + public override DaprDistributedLockClient Build() + { + var daprClientDependencies = this.BuildDaprClientDependencies(typeof(DaprDistributedLockClient).Assembly); + var client = new Autogenerated.Dapr.DaprClient(daprClientDependencies.channel); + return new DaprDistributedLockGrpcClient(client, daprClientDependencies.httpClient, daprClientDependencies.daprApiToken); + } +} diff --git a/src/Dapr.DistributedLock/Extensions/DaprDistributedLockServiceCollectionExtensions.cs b/src/Dapr.DistributedLock/Extensions/DaprDistributedLockServiceCollectionExtensions.cs new file mode 100644 index 000000000..896642189 --- /dev/null +++ b/src/Dapr.DistributedLock/Extensions/DaprDistributedLockServiceCollectionExtensions.cs @@ -0,0 +1,38 @@ +// ------------------------------------------------------------------------ +// Copyright 2025 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using System.Diagnostics.CodeAnalysis; +using Dapr.Common.Extensions; +using Microsoft.Extensions.DependencyInjection; + +namespace Dapr.DistributedLock.Extensions; + +/// +/// Contains extension methods for using Dapr distributed lock with dependency injection. +/// +[Experimental("DAPR_DISTRIBUTEDLOCK", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/distributed-lock/distributed-lock-api-overview/")] +public static class DaprDistributedLockServiceCollectionExtensions +{ + /// + /// Adds Dapr distributed lock support to the service collection. + /// + /// The . + /// Optionally allows greater configuration of the . + /// The lifetime of the registered services. + /// + public static IDaprDistributedLockBuilder AddDaprDistributedLock( + this IServiceCollection services, + Action? configure = null, + ServiceLifetime lifetime = ServiceLifetime.Scoped) => + services.AddDaprClient(configure, lifetime); +} diff --git a/src/Dapr.DistributedLock/Extensions/DaprLockBuilder.cs b/src/Dapr.DistributedLock/Extensions/DaprLockBuilder.cs new file mode 100644 index 000000000..cf7d6f92f --- /dev/null +++ b/src/Dapr.DistributedLock/Extensions/DaprLockBuilder.cs @@ -0,0 +1,28 @@ +// ------------------------------------------------------------------------ +// Copyright 2025 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using Microsoft.Extensions.DependencyInjection; + +namespace Dapr.DistributedLock.Extensions; + +/// +/// Used by the fluent registration builder to configure a Dapr distributed lock client. +/// +/// +public sealed class DaprLockBuilder(IServiceCollection services) : IDaprDistributedLockBuilder +{ + /// + /// The registered services on the builder. + /// + public IServiceCollection Services { get; } = services; +} diff --git a/src/Dapr.DistributedLock/IDaprDistributedLockBuilder.cs b/src/Dapr.DistributedLock/IDaprDistributedLockBuilder.cs new file mode 100644 index 000000000..4b2fd8fbc --- /dev/null +++ b/src/Dapr.DistributedLock/IDaprDistributedLockBuilder.cs @@ -0,0 +1,22 @@ +// ------------------------------------------------------------------------ +// Copyright 2025 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using Dapr.Common; + +namespace Dapr.DistributedLock; + +/// +/// Provides a root builder for the Dapr distributed lock operations facilitating a new fluent-style registration +/// that looks ahead to additional Dapr capabilities in this space. +/// +public interface IDaprDistributedLockBuilder : IDaprServiceBuilder; diff --git a/src/Dapr.DistributedLock/IDaprDistributedLockClient.cs b/src/Dapr.DistributedLock/IDaprDistributedLockClient.cs new file mode 100644 index 000000000..448c26dde --- /dev/null +++ b/src/Dapr.DistributedLock/IDaprDistributedLockClient.cs @@ -0,0 +1,52 @@ +// ------------------------------------------------------------------------ +// Copyright 2025 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using System.Diagnostics.CodeAnalysis; +using Dapr.Common; +using Dapr.DistributedLock.Models; + +namespace Dapr.DistributedLock; + +/// +/// Provides the implementation shape for the Dapr distributed lock client. +/// +[Experimental("DAPR_DISTRIBUTEDLOCK", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/distributed-lock/distributed-lock-api-overview/")] +public interface IDaprDistributedLockClient : IDaprClient +{ + /// + /// Attempt to lock the given resourceId with response indicating success. + /// + /// The name of the lock store to be queried. + /// Lock key that stands for which resource to protect. + /// Indicates the identifier of lock owner. + /// The time after which the lock gets expired. + /// A that can be used to cancel the operation. + /// A containing a + public Task TryLockAsync(string storeName, string resourceId, string lockOwner, int expiryInSeconds, + CancellationToken cancellationToken = default); + + /// + /// Attempt to unlock the given resourceId with response indicating success. + /// + /// The name of the lock store to be queried. + /// Lock key that stands for which resource to protect. + /// Indicates the identifier of lock owner. + /// A that can be used to cancel the operation. + /// A containing a + [Experimental("DAPR_DISTRIBUTEDLOCK", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/distributed-lock/distributed-lock-api-overview/")] + public Task TryUnlockAsync( + string storeName, + string resourceId, + string lockOwner, + CancellationToken cancellationToken = default); +} diff --git a/src/Dapr.DistributedLock/Models/LockResponse.cs b/src/Dapr.DistributedLock/Models/LockResponse.cs new file mode 100644 index 000000000..8b6e29c5e --- /dev/null +++ b/src/Dapr.DistributedLock/Models/LockResponse.cs @@ -0,0 +1,54 @@ +// ------------------------------------------------------------------------ +// Copyright 2025 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using System.Diagnostics.CodeAnalysis; + +namespace Dapr.DistributedLock.Models; + +/// +/// Class representing the response from a Lock API call. +/// +[Experimental("DAPR_DISTRIBUTEDLOCK", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/distributed-lock/distributed-lock-api-overview/")] +public sealed class LockResponse : IAsyncDisposable +{ + private readonly IDaprDistributedLockClient _daprClient; + + /// + /// Constructs a new instance of a . + /// + internal LockResponse(IDaprDistributedLockClient daprClient) + { + _daprClient = daprClient; + } + + /// + /// The resource identifier that was locked. + /// + public required string ResourceId { get; init; } + + /// + /// The name of the owner required to unlock the lock. + /// + public required string LockOwner { get; init; } + + /// + /// The name of the store required to unlock the lock. + /// + public required string StoreName { get; init; } + + /// + public async ValueTask DisposeAsync() + { + await _daprClient.TryUnlockAsync(StoreName, ResourceId, LockOwner).ConfigureAwait(false); + } +} diff --git a/src/Dapr.DistributedLock/Models/LockStatus.cs b/src/Dapr.DistributedLock/Models/LockStatus.cs new file mode 100644 index 000000000..5bd761428 --- /dev/null +++ b/src/Dapr.DistributedLock/Models/LockStatus.cs @@ -0,0 +1,37 @@ +// ------------------------------------------------------------------------ +// Copyright 2025 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +namespace Dapr.DistributedLock.Models; + +/// +/// Represents the result of an attempt to unlock an existing lock. +/// +public enum LockStatus +{ + /// + /// Indicates the lock was released successfully. + /// + Success, + /// + /// Indicates the lock does not exist. + /// + LockDoesNotExist, + /// + /// Indicates the lock was acquired by another process. + /// + LockBelongsToOthers, + /// + /// Indicates there was an error while unlocking. + /// + InternalError +} diff --git a/src/Dapr.DistributedLock/Models/UnlockResponse.cs b/src/Dapr.DistributedLock/Models/UnlockResponse.cs new file mode 100644 index 000000000..13d412d90 --- /dev/null +++ b/src/Dapr.DistributedLock/Models/UnlockResponse.cs @@ -0,0 +1,20 @@ +// ------------------------------------------------------------------------ +// Copyright 2025 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +namespace Dapr.DistributedLock.Models; + +/// +/// Represents the response from an attempt to unlock a distributed lock. +/// +/// The status of the lock following the attempted unlock operation. +public sealed record UnlockResponse(LockStatus Status);