-
Notifications
You must be signed in to change notification settings - Fork 36
add refreshing client sample #119
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
8afca44
a88e345
b972d4a
1b1b9da
456b1ce
ea5e468
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| namespace TemporalioSamples.RefreshingClient; | ||
|
|
||
| using Temporalio.Activities; | ||
|
|
||
| public class MyActivities | ||
| { | ||
| private readonly MyDatabaseClient dbClient = new(); | ||
|
|
||
| // Activities can be static and/or sync | ||
| [Activity] | ||
| public static string DoStaticThing() => "some-static-value"; | ||
|
|
||
| // Activities can be methods that can access state | ||
| [Activity] | ||
| public Task<string> SelectFromDatabaseAsync(string table) => | ||
| dbClient.SelectValueAsync(table); | ||
|
|
||
| public class MyDatabaseClient | ||
| { | ||
| public Task<string> SelectValueAsync(string table) => | ||
| Task.FromResult($"some-db-value from table {table}"); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| namespace TemporalioSamples.RefreshingClient; | ||
|
|
||
| using Microsoft.Extensions.Logging; | ||
| using Temporalio.Workflows; | ||
|
|
||
| [Workflow] | ||
| public class MyWorkflow | ||
| { | ||
| [WorkflowRun] | ||
| public async Task<string> RunAsync() | ||
| { | ||
| // Run an async instance method activity. | ||
| var result1 = await Workflow.ExecuteActivityAsync( | ||
| (MyActivities act) => act.SelectFromDatabaseAsync("some-db-table"), | ||
| new() | ||
| { | ||
| StartToCloseTimeout = TimeSpan.FromMinutes(5), | ||
| }); | ||
| Workflow.Logger.LogInformation("Activity instance method result: {Result}", result1); | ||
|
|
||
| // Run a sync static method activity. | ||
| var result2 = await Workflow.ExecuteActivityAsync( | ||
| () => MyActivities.DoStaticThing(), | ||
| new() | ||
| { | ||
| StartToCloseTimeout = TimeSpan.FromMinutes(5), | ||
| }); | ||
| Workflow.Logger.LogInformation("Activity static method result: {Result}", result2); | ||
|
|
||
| // We'll go ahead and return this result | ||
| return result2; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| using Microsoft.Extensions.Logging; | ||
| using Temporalio.Client; | ||
| using Temporalio.Client.EnvConfig; | ||
| using Temporalio.Worker; | ||
| using TemporalioSamples.RefreshingClient; | ||
|
|
||
| async Task<TemporalClient> CreateClientAsync() | ||
| { | ||
| var connectOptions = ClientEnvConfig.LoadClientConnectOptions(); | ||
| if (string.IsNullOrEmpty(connectOptions.TargetHost)) | ||
| { | ||
| connectOptions.TargetHost = "localhost:7233"; | ||
| } | ||
| connectOptions.LoggerFactory = LoggerFactory.Create(builder => | ||
| builder. | ||
| AddSimpleConsole(options => options.TimestampFormat = "[HH:mm:ss] "). | ||
| SetMinimumLevel(LogLevel.Information)); | ||
| return await TemporalClient.ConnectAsync(connectOptions); | ||
| } | ||
|
|
||
| async Task RunWorkerAsync(TemporalClient client) | ||
| { | ||
| // Cancellation token cancelled on ctrl+c | ||
| using var tokenSource = new CancellationTokenSource(); | ||
| Console.CancelKeyPress += (_, eventArgs) => | ||
| { | ||
| tokenSource.Cancel(); | ||
| eventArgs.Cancel = true; | ||
| }; | ||
|
|
||
| // Create an activity instance with some state | ||
| var activities = new MyActivities(); | ||
|
|
||
| // Run worker until cancelled | ||
| Console.WriteLine("Running worker"); | ||
| using var worker = new TemporalWorker( | ||
| client, | ||
| new TemporalWorkerOptions(taskQueue: "activity-simple-sample"). | ||
| AddActivity(activities.SelectFromDatabaseAsync). | ||
| AddActivity(MyActivities.DoStaticThing). | ||
| AddWorkflow<MyWorkflow>()); | ||
|
|
||
| var replaceWorkerClient = (TemporalClient newClient) => | ||
| { | ||
| worker.Client = newClient; | ||
| Console.WriteLine("Worker's client has been refreshed."); | ||
| return Task.FromResult(true); | ||
| }; | ||
|
|
||
| try | ||
| { | ||
| await Task.WhenAll(ClientRefreshAsync(replaceWorkerClient, tokenSource.Token), worker.ExecuteAsync(tokenSource.Token)); | ||
| } | ||
| catch (OperationCanceledException) | ||
| { | ||
| Console.WriteLine("Worker cancelled"); | ||
| } | ||
| } | ||
|
|
||
| async Task ExecuteWorkflowAsync(TemporalClient client) | ||
| { | ||
| Console.WriteLine("Executing workflow"); | ||
| await client.ExecuteWorkflowAsync( | ||
| (MyWorkflow wf) => wf.RunAsync(), | ||
| new(id: "activity-simple-workflow-id", taskQueue: "activity-simple-sample")); | ||
| } | ||
|
|
||
| async Task ClientRefreshAsync(Func<TemporalClient, Task> asyncFunc, CancellationToken cancellationToken) | ||
| { | ||
| // Change the frequency of rotation as per your requirements | ||
| Console.WriteLine("This program will refresh its Temporal client every 2 hours."); | ||
| await RunRecurringTaskAsync(TimeSpan.FromHours(2), cancellationToken, asyncFunc); | ||
| } | ||
|
|
||
| async Task RunRecurringTaskAsync(TimeSpan interval, CancellationToken cancellationToken, Func<TemporalClient, Task> asyncFunc) | ||
| { | ||
| while (!cancellationToken.IsCancellationRequested) | ||
| { | ||
| try | ||
| { | ||
| await Task.Delay(interval, cancellationToken); | ||
| Console.WriteLine("Refreshing client..."); | ||
| var client = await CreateClientAsync(); | ||
| await asyncFunc(client); | ||
| } | ||
| catch (OperationCanceledException) | ||
| { | ||
| Console.WriteLine("Refreshing task cancelled."); | ||
| break; | ||
| } | ||
| #pragma warning disable CA1031 // Do not catch general exception types | ||
| catch (Exception ex) | ||
| { | ||
| Console.WriteLine($"Error: {ex.Message}"); | ||
| // Continue running even if one iteration fails | ||
| } | ||
| #pragma warning restore CA1031 // Do not catch general exception types | ||
| } | ||
| } | ||
|
|
||
| var client = await CreateClientAsync(); | ||
| switch (args.ElementAtOrDefault(0)) | ||
| { | ||
| case "worker": | ||
| await RunWorkerAsync(client); | ||
| break; | ||
| case "workflow": | ||
| await ExecuteWorkflowAsync(client); | ||
| break; | ||
| default: | ||
| throw new ArgumentException("Must pass 'worker' or 'workflow' as the single argument"); | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hrmm, I wonder if a single client refresh after some time would be a better demonstration. I have not seen this sample in any other Core-based SDKs, so it is a bit strange to only be in .NET, but not a big deal. However, I do not want to encourage the practice of rotating a client every 10 seconds (many calls are up to a minute long anyways, so rotation may happen a few times before the client is even used). I fear the sample demonstrating this kind of frequent rotation may encourage it.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, that is a valid point. I will change to rotating it every 2 hours for the sample.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done, let me know if you still prefer the demonstration to be once instead of every 2 hours.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This works for me, but realistically a user may never see the client refresh in the sample. Just want confirmation that's ok with you before merging. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| # Refreshing Client | ||
|
|
||
| This sample demonstrates how to periodically refresh the Temporal client in a Worker. | ||
| The Worker program refreshes the Temporal client every 2 hours, which is useful for scenarios requiring credential mTLS or api key rotation. | ||
|
|
||
| `ClientRefreshAsync` accepts a Func to deliver a new client, to replace the callers Worker client. | ||
|
|
||
| To run, first see [README.md](../../README.md) for prerequisites. Then, run the following from this directory | ||
| in a separate terminal to start the worker: | ||
|
|
||
| dotnet run worker | ||
|
|
||
| Then in another terminal, run a workflow every second from this directory: | ||
|
|
||
| watch -n1 dotnet run workflow | ||
|
|
||
| This will show logs in the worker window of the workflow running. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
|
||
| <PropertyGroup> | ||
| <OutputType>Exe</OutputType> | ||
| </PropertyGroup> | ||
|
|
||
| </Project> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure this is worth breaking out into 2 more separate methods vs just inlining the loop here, but not a big deal