-
Notifications
You must be signed in to change notification settings - Fork 27
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
Added Counter Interceptor Sample #77
Changes from 9 commits
d678366
f428229
26fdbee
c68e75d
ec9da55
31c0743
ca90698
7ac67a9
34c83af
7682937
37692a7
d548089
d113bd3
c0131fd
4557ffe
c7b6e31
f3a82cc
078bbe6
fc4fada
c6f5e55
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,19 @@ | ||
namespace TemporalioSamples.CounterInterceptor; | ||
|
||
using System.Diagnostics; | ||
using Temporalio.Activities; | ||
|
||
public class MyActivities | ||
{ | ||
[Activity] | ||
public string SayHello(string name, string title) | ||
{ | ||
return "Hello " + title + " " + name; | ||
} | ||
|
||
[Activity] | ||
public string SayGoodBye(string name, string title) | ||
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. You are inconsistent with your single-line methods, this should be 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. Fixed |
||
{ | ||
return "Goodbye " + title + " " + name; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
namespace TemporalioSamples.CounterInterceptor; | ||
|
||
using Temporalio.Workflows; | ||
|
||
[Workflow] | ||
public class MyChildWorkflow | ||
{ | ||
private readonly ActivityOptions activityOptions = new() | ||
{ | ||
StartToCloseTimeout = TimeSpan.FromSeconds(10), | ||
}; | ||
|
||
[WorkflowRun] | ||
public async Task<string> RunAsync(string name, string title) => | ||
await Workflow.ExecuteActivityAsync((MyActivities act) => act.SayHello(name, title), activityOptions) + | ||
await Workflow.ExecuteActivityAsync((MyActivities act) => act.SayGoodBye(name, title), activityOptions); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
namespace TemporalioSamples.CounterInterceptor; | ||
|
||
using Temporalio.Workflows; | ||
|
||
[Workflow] | ||
public class MyWorkflow | ||
{ | ||
private bool exit; // Automatically defaults to false | ||
|
||
[WorkflowRun] | ||
public async Task<string> RunAsync() | ||
{ | ||
// Wait for greeting info | ||
await Workflow.WaitConditionAsync(() => Name != null && Title != null); | ||
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. These properties can never be null, so this statement has no value, it will always pass immediately 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. Bump, this was never addressed 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. Bump again, this is still not addressed 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. Fixed. |
||
|
||
// Execute Child Workflow | ||
var result = await Workflow.ExecuteChildWorkflowAsync( | ||
(MyChildWorkflow wf) => wf.RunAsync(Name, Title), | ||
new() { Id = "counter-interceptor-child" }); | ||
|
||
// Wait for exit signal | ||
await Workflow.WaitConditionAsync(() => exit); | ||
|
||
return result; | ||
} | ||
|
||
[WorkflowSignal] | ||
public async Task SignalNameAndTitleAsync(string name, string title) | ||
{ | ||
Name = name; | ||
Title = title; | ||
} | ||
|
||
[WorkflowQuery] | ||
public string Name { get; private set; } = string.Empty; | ||
|
||
[WorkflowQuery] | ||
public string Title { get; private set; } = string.Empty; | ||
|
||
[WorkflowSignal] | ||
public async Task ExitAsync() => exit = true; | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,86 @@ | ||||||||||
namespace TemporalioSamples.CounterInterceptor; | ||||||||||
|
||||||||||
using Temporalio.Client; | ||||||||||
using Temporalio.Worker; | ||||||||||
|
||||||||||
internal class Program | ||||||||||
{ | ||||||||||
private static async Task Main(string[] args) | ||||||||||
{ | ||||||||||
var clientInterceptor = new SimpleClientCallsInterceptor(); | ||||||||||
var client = await TemporalClient.ConnectAsync( | ||||||||||
options: new("localhost:7233") | ||||||||||
{ | ||||||||||
Interceptors = new[] | ||||||||||
{ | ||||||||||
clientInterceptor, | ||||||||||
}, | ||||||||||
}); | ||||||||||
|
||||||||||
using var tokenSource = new CancellationTokenSource(); | ||||||||||
Console.CancelKeyPress += (_, eventArgs) => | ||||||||||
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 program is a run then complete, you don't need ctrl+c support as if it was a long running worker. I would recommend just removing cancel token and shutting down the worker manually if you must have the entire program self-contained (i.e. combining client and worker). 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. Bump, this was never addressed 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. Bump again, this is still not addressed 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. Fixed. |
||||||||||
{ | ||||||||||
tokenSource.Cancel(); | ||||||||||
eventArgs.Cancel = true; | ||||||||||
}; | ||||||||||
|
||||||||||
var activities = new MyActivities(); | ||||||||||
|
||||||||||
var taskQueue = "CounterInterceptorTaskQueue"; | ||||||||||
|
||||||||||
var workerOptions = new TemporalWorkerOptions(taskQueue). | ||||||||||
AddAllActivities(activities). | ||||||||||
AddWorkflow<MyWorkflow>(). | ||||||||||
AddWorkflow<MyChildWorkflow>(); | ||||||||||
|
||||||||||
var workerInterceptor = new SimpleCounterWorkerInterceptor(); | ||||||||||
workerOptions.Interceptors = new[] { workerInterceptor }; | ||||||||||
|
||||||||||
using var worker = new TemporalWorker( | ||||||||||
client, | ||||||||||
workerOptions); | ||||||||||
|
||||||||||
// Run worker until cancelled | ||||||||||
Console.WriteLine("Running worker..."); | ||||||||||
try | ||||||||||
{ | ||||||||||
// Start the workers | ||||||||||
var workerResult = worker.ExecuteAsync(tokenSource.Token); | ||||||||||
|
||||||||||
// Start the workflow | ||||||||||
var handle = await client.StartWorkflowAsync( | ||||||||||
(MyWorkflow wf) => wf.RunAsync(), | ||||||||||
new(id: Guid.NewGuid().ToString(), taskQueue: taskQueue)); | ||||||||||
|
||||||||||
Console.WriteLine("Sending name and title to workflow"); | ||||||||||
await handle.SignalAsync(wf => wf.SignalNameAndTitleAsync("John", "Customer")); | ||||||||||
|
||||||||||
var name = await handle.QueryAsync(wf => wf.Name); | ||||||||||
var title = await handle.QueryAsync(wf => wf.Title); | ||||||||||
|
||||||||||
// Send exit signal to workflow | ||||||||||
await handle.SignalAsync(wf => wf.ExitAsync()); | ||||||||||
|
||||||||||
var result = await handle.GetResultAsync(); | ||||||||||
|
||||||||||
Console.WriteLine($"Workflow result is {result}", result); | ||||||||||
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. Inconsistent with whether you use string concatenation or string interpolation 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.
Suggested change
or
Suggested change
But not both. This mistake is made in other lines below. Probably the former approach is best. 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. Bump this was never addressed. If you are using interpolation, you don't also pass as a param. |
||||||||||
|
||||||||||
Console.WriteLine("Query results: "); | ||||||||||
Console.WriteLine($"\tName: {name}", name); | ||||||||||
Console.WriteLine($"\tTitle: {title}", title); | ||||||||||
|
||||||||||
// Print worker counter info | ||||||||||
Console.WriteLine("Collected Worker Counter Info: "); | ||||||||||
Console.WriteLine(workerInterceptor.Info()); | ||||||||||
|
||||||||||
// Print client counter info | ||||||||||
Console.WriteLine(); | ||||||||||
Console.WriteLine("Collected Client Counter Info:"); | ||||||||||
Console.WriteLine(clientInterceptor.Info()); | ||||||||||
} | ||||||||||
catch (OperationCanceledException) | ||||||||||
{ | ||||||||||
Console.WriteLine("Worker cancelled"); | ||||||||||
} | ||||||||||
} | ||||||||||
} |
rross marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,28 @@ | ||||||||||||||
# dotnet-counter-interceptor | ||||||||||||||
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. Not really the best title, but that's ok |
||||||||||||||
The sample demonstrates: | ||||||||||||||
- the use of a Worker Workflow Interceptor that counts the number of Workflow Executions, Child Workflow Executions, and Activity Executions and the number of Signals and Queries. It is based | ||||||||||||||
off of the [Java sample](https://github.com/temporalio/samples-java/tree/main) located [here](https://github.com/temporalio/samples-java/tree/main/core/src/main/java/io/temporal/samples/countinterceptor) | ||||||||||||||
- the use of a Client Workflow Interceptor that counts the number of Workflow Executions and the number of Signals and Queries. | ||||||||||||||
|
||||||||||||||
## Start local Temporal Server | ||||||||||||||
```bash | ||||||||||||||
# run only once | ||||||||||||||
temporal server start-dev | ||||||||||||||
``` | ||||||||||||||
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.
Suggested change
We like to keep the starting of the local server in one place since it can change and there are multiple options. This can be seen in other samples. (in fact, we will make these run easy on cloud too soon, so we don't want to have to come back and update these) 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. Bump, this was never addressed 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. Bump again, this is still not addressed 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. Finally addressed :) |
||||||||||||||
|
||||||||||||||
## Run Worker Locally | ||||||||||||||
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 is more than the worker, it's the worker and the client calls mixed into one. Many samples split these, but don't have to here, but it's not just "worker" as the title suggests. 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. Bump, this was never addressed 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. Bump again, this is still not addressed 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. Fixed. |
||||||||||||||
```bash | ||||||||||||||
# make sure you have temporal server running (see section above) | ||||||||||||||
dotnet run | ||||||||||||||
``` | ||||||||||||||
|
||||||||||||||
## Run Worker using Temporal Cloud | ||||||||||||||
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. I don't think this section applies anymore 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. Bump, this was never addressed 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. Deleted. |
||||||||||||||
```bash | ||||||||||||||
# set up environment variables | ||||||||||||||
export TEMPORAL_NAMESPACE=<namespace>.<accountId> | ||||||||||||||
export TEMPORAL_ADDRESS=<namespace>.<accountId>.tmprl.cloud:7233 | ||||||||||||||
export TEMPORAL_TLS_CERT=/path/to/cert | ||||||||||||||
export TEMPORAL_TLS_KEY=/path/to/key | ||||||||||||||
# run the worker | ||||||||||||||
dotnet run | ||||||||||||||
``` |
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,97 @@ | ||||||||||
namespace TemporalioSamples.CounterInterceptor; | ||||||||||
|
||||||||||
using Temporalio.Client; | ||||||||||
using Temporalio.Client.Interceptors; | ||||||||||
|
||||||||||
public record ClientCounts | ||||||||||
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. You should only have top-level types named the same as the file in A non-Program.cs C# file. |
||||||||||
{ | ||||||||||
public uint Executions { get; internal set; } | ||||||||||
|
||||||||||
public uint Signals { get; internal set; } | ||||||||||
|
||||||||||
public uint Queries { get; internal set; } | ||||||||||
|
||||||||||
public override string ToString() => | ||||||||||
$"\n\tTotal Number of Workflow Exec: {Executions}\n\t" + | ||||||||||
$"Total Number of Signals: {Signals}\n\t" + | ||||||||||
$"Total Number of Queries: {Queries}"; | ||||||||||
} | ||||||||||
|
||||||||||
public class SimpleClientCallsInterceptor : IClientInterceptor | ||||||||||
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. IMO for simplicity you should make one interceptor that implements both 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 |
||||||||||
{ | ||||||||||
private const string NumberOfWorkflowExecutions = "numOfWorkflowExec"; | ||||||||||
private const string NumberOfSignals = "numOfSignals"; | ||||||||||
private const string NumberOfQueries = "numOfQueries"; | ||||||||||
private static Dictionary<string, ClientCounts> clientDictionary = new(); | ||||||||||
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 should not be static and it should be readonly and you do not have to suffix "Dictionary", just call it 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. |
||||||||||
|
||||||||||
public ClientOutboundInterceptor InterceptClient(ClientOutboundInterceptor nextInterceptor) => | ||||||||||
new ClientOutbound(this, nextInterceptor); | ||||||||||
|
||||||||||
public string Info() => | ||||||||||
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. May be easier if you just let the caller access the dictionary and do what they want with it instead of all of these separate methods to read/write it 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 |
||||||||||
string.Join( | ||||||||||
"\n", | ||||||||||
clientDictionary.Select(kvp => $"** Workflow ID: {kvp.Key} {kvp.Value}")); | ||||||||||
|
||||||||||
public uint NumOfWorkflowExecutions(string workflowId) => | ||||||||||
clientDictionary[workflowId].Executions; | ||||||||||
|
||||||||||
public uint NumOfSignals(string workflowId) => | ||||||||||
clientDictionary[workflowId].Signals; | ||||||||||
|
||||||||||
public uint NumOfQueries(string workflowId) => | ||||||||||
clientDictionary[workflowId].Queries; | ||||||||||
|
||||||||||
private void Add(string workflowId, string type) | ||||||||||
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 is not thread safe. What if multiple threads for the same workflow ID come in at the same time, there is a case where one dictionary value may get overwritten with another. Consider a |
||||||||||
{ | ||||||||||
if (!clientDictionary.TryGetValue(workflowId, out ClientCounts? value)) | ||||||||||
{ | ||||||||||
value = new ClientCounts(); | ||||||||||
clientDictionary.Add(workflowId, value); | ||||||||||
} | ||||||||||
|
||||||||||
switch (type) | ||||||||||
{ | ||||||||||
case NumberOfWorkflowExecutions: | ||||||||||
value.Executions++; | ||||||||||
break; | ||||||||||
case NumberOfQueries: | ||||||||||
value.Queries++; | ||||||||||
break; | ||||||||||
case NumberOfSignals: | ||||||||||
value.Signals++; | ||||||||||
break; | ||||||||||
default: | ||||||||||
throw new NotImplementedException("Unknown type: " + type); | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
private class ClientOutbound : ClientOutboundInterceptor | ||||||||||
{ | ||||||||||
private SimpleClientCallsInterceptor root; | ||||||||||
|
||||||||||
public ClientOutbound(SimpleClientCallsInterceptor root, ClientOutboundInterceptor next) | ||||||||||
: base(next) => this.root = root; | ||||||||||
|
||||||||||
public override Task<WorkflowHandle<TWorkflow, TResult>> StartWorkflowAsync<TWorkflow, TResult>( | ||||||||||
StartWorkflowInput input) | ||||||||||
{ | ||||||||||
var id = input.Options.Id ?? "None"; | ||||||||||
root.Add(id, NumberOfWorkflowExecutions); | ||||||||||
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. No need for these string constants, can change this to something like:
Suggested change
Or
Suggested change
Depending on how you want to do the thread safety |
||||||||||
return base.StartWorkflowAsync<TWorkflow, TResult>(input); | ||||||||||
} | ||||||||||
|
||||||||||
public override Task SignalWorkflowAsync(SignalWorkflowInput input) | ||||||||||
{ | ||||||||||
var id = input.Id ?? "None"; | ||||||||||
root.Add(id, NumberOfSignals); | ||||||||||
return base.SignalWorkflowAsync(input); | ||||||||||
} | ||||||||||
|
||||||||||
public override Task<TResult> QueryWorkflowAsync<TResult>(QueryWorkflowInput input) | ||||||||||
{ | ||||||||||
var id = input.Id ?? "None"; | ||||||||||
root.Add(id, NumberOfQueries); | ||||||||||
return base.QueryWorkflowAsync<TResult>(input); | ||||||||||
} | ||||||||||
} | ||||||||||
} |
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.
Use string interpolation and single-statement syntax (will stop commenting on this, but it applies project wide)
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.
Bump, this was not addressed