Skip to content
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

Reserve __temporal prefix #410

Merged
merged 5 commits into from
Feb 11, 2025
Merged

Reserve __temporal prefix #410

merged 5 commits into from
Feb 11, 2025

Conversation

Sushisource
Copy link
Member

What was changed

See temporalio/features#576

Why?

Future flexibility for us to implement built-in handlers

Checklist

  1. Closes [Feature Request] Special behavior for Temporal built-in prefixes #391

  2. How was this tested:
    New tests & test upgrades

  3. Any docs updates needed?

@@ -47,6 +47,9 @@ dotnet_diagnostic.CA2237.severity = none
# Warn on unused imports
dotnet_diagnostic.IDE0005.severity = warning

# Don't warn on using var
dotnet_diagnostic.IDE0008.severity = none
Copy link
Member

Choose a reason for hiding this comment

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

If you remember, am a bit curious where in code this was triggered

Copy link
Member Author

Choose a reason for hiding this comment

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

All over. I think it's just a matter of how the default "low" severity shows up in different lang servers. All the low severity stuff shows up in omnisharp, which can be kinda nice to auto fix some stuff sometimes, but, this one is obviously a little silly.

/// This interceptor ensures that handlers using the reserved prefix bypass user
/// interceptors.
/// </summary>
private class InboundReservedPrefixInterceptor : WorkflowInboundInterceptor
Copy link
Member

Choose a reason for hiding this comment

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

I do not believe this should be implemented as an interceptor/wrapper that now becomes the outer interceptor always just for this small reason. I believe in ApplyQueryWorkflow we need to just make the two reserved queries call query logic directly without going through the interceptor. And it is there we should fail if it's reserved and not one of those. Can also fail update and signal in their apply-level places too.

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure, I did this because it would be robust in the face of different ways to handle built-in operations. If you're not worried about that then I'll get rid of it.

Comment on lines 436 to 458
void CheckNamesForPrefix(IEnumerable<string> keys, string type)
{
foreach (var key in keys)
{
if (key.StartsWith(Constants.ReservedNamePrefix))
{
errs.Add($"{type} handler name {key} cannot start with {Constants.ReservedNamePrefix}");
}
else if (type == "Query")
{
foreach (var reservedQ in Workflow.ReservedQueryHandlerPrefixes)
{
if (key.StartsWith(reservedQ))
{
errs.Add($"{type} handler name {key} cannot start with {reservedQ}");
}
}
}
}
}
CheckNamesForPrefix(signals.Keys, "Signal");
CheckNamesForPrefix(queries.Keys, "Query");
CheckNamesForPrefix(updates.Keys, "Update");
Copy link
Member

@cretz cretz Feb 10, 2025

Choose a reason for hiding this comment

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

I think you can just check signal name prefix in signal definition, update in update definition, and query in query definition. It is clearer to keep validation with the definition. Can just do it in those constructors if you want.

Copy link
Member Author

Choose a reason for hiding this comment

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

The downside of this is now you need a specific bypassing constructor -- see my last commit

/// <summary>
/// Prefix for reserved handler and definition names.
/// </summary>
internal const string ReservedNamePrefix = "__temporal";
Copy link
Member

Choose a reason for hiding this comment

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

If you can think of any other class, would be ok moving there (e.g. even TemporalRuntime since it's internal) instead of a whole new class/file for a single constant.

Copy link
Member Author

Choose a reason for hiding this comment

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

Well there's always going to be something that'd be the first reason to have a common place for constants - no where else felt like an accurate fit, but I can move it to Runtime.

src/Temporalio/Workflows/Workflow.cs Outdated Show resolved Hide resolved
/// <summary>
/// Prefix for reserved handler and definition names.
/// </summary>
internal const string ReservedNamePrefix = "__temporal";
Copy link
Member

Choose a reason for hiding this comment

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

Technically __temporal_, but I am ok with this without trailing underscore

Copy link
Member Author

Choose a reason for hiding this comment

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

I think we should probably actually make it specifically be without that. Seems very odd you could register __temporalmything but not __temporal_mything

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, was unsure here because in spec I added underscore not thinking and other SDKs already merged theirs, though I doubt an inconsistency here matters.

Comment on lines 1148 to 1154
queryDefn = WorkflowQueryDefinition.CreateWithoutAttributeReservedName(
"__stack_trace", getter);
}
else if (query.QueryType == "__temporal_workflow_metadata")
{
Func<Api.Sdk.V1.WorkflowMetadata> getter = GetWorkflowMetadata;
queryDefn = WorkflowQueryDefinition.CreateWithoutAttribute(
queryDefn = WorkflowQueryDefinition.CreateWithoutAttributeReservedName(
Copy link
Member

Choose a reason for hiding this comment

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

For these two, we need to not go through interceptors to invoke them. Would be ok if they aren't even "query definitions" and just invoked directly or something (also saves you from having that bypass constructor param for query definition)

{
if (!bypassReserved && name != null)
Copy link
Member

Choose a reason for hiding this comment

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

If you want to save lines, add using System.Linq; import and then:

Suggested change
if (!bypassReserved && name != null)
if (!bypassReserved && name != null && ReservedQueryHandlerPrefixes.Any(p => name.StartsWith(p))

Copy link
Member Author

Choose a reason for hiding this comment

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

Don't save all that much if you want to keep the prefix name in the error which I do, but, changed.

src/Temporalio/Workflows/WorkflowQueryDefinition.cs Outdated Show resolved Hide resolved
Copy link
Member

@cretz cretz left a comment

Choose a reason for hiding this comment

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

LGTM, one minor change about removing constructor param

private static readonly ConcurrentDictionary<MethodInfo, WorkflowQueryDefinition> MethodDefinitions = new();
private static readonly ConcurrentDictionary<PropertyInfo, WorkflowQueryDefinition> PropertyDefinitions = new();

private WorkflowQueryDefinition(string? name, string? description, MethodInfo? method, Delegate? del)
private WorkflowQueryDefinition(string? name, string? description, MethodInfo? method, Delegate? del, bool bypassReserved = false)
Copy link
Member

Choose a reason for hiding this comment

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

Can you now remove this constructor parameter for bypassing since it is never true anymore?

Copy link
Member Author

Choose a reason for hiding this comment

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

Woops, thanks

/// <summary>
/// Prefix for reserved handler and definition names.
/// </summary>
internal const string ReservedNamePrefix = "__temporal";

Choose a reason for hiding this comment

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

Shouldn't it be __temporal_ per temporalio/features#576

Copy link
Member Author

Choose a reason for hiding this comment

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

See up here: #410 (comment)

Yeah, that's what's written there, but, also that just seems wrong. Probably we should change that spec.

@Sushisource Sushisource merged commit f2ba7dd into main Feb 11, 2025
9 checks passed
@Sushisource Sushisource deleted the reserved-prefixes branch February 11, 2025 20:50
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.

[Feature Request] Special behavior for Temporal built-in prefixes
3 participants