Skip to content

Conversation

@yuandrew
Copy link
Contributor

@yuandrew yuandrew commented Dec 4, 2025

What was changed

  • Enabled worker heartbeat
  • plumb plugin names (both client and worker) to core
  • update core to 44a6576
    • 💥temporal_sdk_core_c_bridge.dll got renamed to temporalio_sdk_core_c_bridge.dll

Why?

New worker heartbeating feature

Checklist

  1. Closes [Feature Request] Enable Worker Heartbeating #551

  2. How was this tested:
    Manually tested

  3. Any docs updates needed?


Note

Enables worker heartbeating, plumbs plugin names to Core, updates interop/bindings and build paths, and renames the native bridge library to temporalio_sdk_core_c_bridge across code, CI, and docs.

  • Runtime/Worker:
    • Add TemporalRuntimeOptions.WorkerHeartbeatInterval and propagate to Core (worker_heartbeat_interval_millis).
    • Pass plugin names (client + worker) to Core via TemporalCoreByteArrayRefArray on client/worker options.
    • Replace no_remote_activities with TemporalCoreWorkerTaskTypes in TemporalCoreWorkerOptions and populate based on registered work.
    • Bridge.Worker constructor accepts clientPlugins and forwards to interop conversion.
  • Interop/Core bindings:
    • Update header path to sdk-core/crates/sdk-core-c-bridge and adjust all DllImport names and library path to temporalio_sdk_core_c_bridge.
    • Add new interop structs/fields: TemporalCoreWorkerTaskTypes, extra fields on client/runtime/worker options.
    • Rename Rust crate log/targets in telemetry defaults from temporal_* to temporalio_*.
  • Build/CI/Packaging:
    • Rename emitted artifacts to temporalio_sdk_core_c_bridge.*; update .csproj, build/net462/Temporalio.targets, and GitHub Actions workflow to new paths and Cargo manifest.
    • Adjust CI matrices/includes and smoke-test config; use Manylinux Alpine/ARM containers.
  • Docs/Tests:
    • Update README and test expectations for new library name and log targets; minor test fixes (unique task queues, metric assertions).

Written by Cursor Bugbot for commit 2bbb1f4. This will update automatically on new commits. Configure here.

@yuandrew yuandrew requested a review from a team as a code owner December 4, 2025 22:08
@yuandrew yuandrew force-pushed the worker-heartbeating branch from 23d3dc6 to fbc46ea Compare December 4, 2025 22:13

<PropertyGroup Condition="$([MSBuild]::IsOSPlatform('Windows'))">
<BridgeLibraryFile>temporal_sdk_core_c_bridge.dll</BridgeLibraryFile>
<BridgeLibraryFile>temporalio_sdk_core_c_bridge.dll</BridgeLibraryFile>
Copy link
Member

Choose a reason for hiding this comment

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

This is considered a backwards incompatible change. We have to mark the PR with a 💥 in the title of the PR at least so we know to update release notes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, updated PR title and desc


<PropertyGroup Condition="$([MSBuild]::IsOSPlatform('Windows'))">
<BridgeLibraryFile>temporal_sdk_core_c_bridge.dll</BridgeLibraryFile>
<BridgeLibraryFile>temporalio_sdk_core_c_bridge.dll</BridgeLibraryFile>
Copy link
Member

Choose a reason for hiding this comment

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

This change was not made to the PackBridgeRuntimes from below. Should probably also temporarily enable the package.yml GH workflow in this branch to confirm it still works.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

options.NexusTaskPollerBehavior ??= new PollerBehavior.SimpleMaximum(options.MaxConcurrentNexusTaskPolls);
var workerPluginNames = options.Plugins?
.Select(p => p.Name)
.Where(name => !string.IsNullOrEmpty(name)) ?? Enumerable.Empty<string>();
Copy link
Member

Choose a reason for hiding this comment

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

Pedantic, but I don't think we need the IsNullOrEmpty check, it is a required property. If we're concerned about empty plugin names, we should validate that somewhere else, but I don't think we're really that concerned about it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

removed

/// </summary>
/// <param name="telemetry"><see cref="Telemetry" />.</param>
/// <param name="workerHeartbeatInterval">Worker heartbeat interval. If 0 is specified, heartbeat is disabled.</param>
public TemporalRuntimeOptions(TelemetryOptions telemetry, TimeSpan workerHeartbeatInterval)
Copy link
Member

Choose a reason for hiding this comment

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

I don't think you need a constructor for this, workerHeartbeatInterval would be rarely set, they can use the parameterless or post-construction set approaches

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sounds good, removed

/// <summary>
/// Gets or sets the worker heartbeat duration in milliseconds.
/// </summary>
public TimeSpan WorkerHeartbeatInterval { get; set; } = TimeSpan.FromSeconds(60);
Copy link
Member

Choose a reason for hiding this comment

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

This should be nullable IMO (i.e. ?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sounds good, added in

return new Interop.TemporalCoreRuntimeOptions()
{
telemetry = scope.Pointer(options.Telemetry.ToInteropOptions(scope)),
worker_heartbeat_interval_millis = (ulong)options.WorkerHeartbeatInterval.TotalMilliseconds,
Copy link
Member

Choose a reason for hiding this comment

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

This should support unset (if 0 means unset in bridge, then can convert here from null to unset)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

converted null and unset to 0, lmk if I misunderstood the comment

@cretz
Copy link
Member

cretz commented Dec 5, 2025

It looks like something about the Core upgrade broke the ExecuteWorkflowAsync_CustomMetrics_FloatsAndDurations test. I think we have to investigate this (let me know if help is needed here).

Copy link
Member

Choose a reason for hiding this comment

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

Note, you'll have to update the TelemetryFilterOptions class to account for the crate name changes

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ty, I didn't do a thorough search through the repo to update crate names. Kinda thought AI woulda taken care of that, my bad

Copy link
Member

Choose a reason for hiding this comment

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

All good, I missed this in Ruby which was pointed out to me at temporalio/sdk-ruby#368 (comment) which reminded me to point it out here

@yuandrew yuandrew changed the title Enable Worker heartbeating, update Core to 7419ada 💥Enable Worker heartbeating, update Core to 7419ada Dec 5, 2025
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Bug: Client plugin names not passed to Core

The TemporalCoreClientOptions struct now has a plugin_names field added by this PR, but the ToInteropOptions method for TemporalConnectionOptions doesn't set this field. The PR description mentions "plumb plugin names (both client and worker) to core", and while worker plugin names are properly passed (via the worker options), client plugin names are not being sent to Core during connection. The field will be default-initialized to an empty array, which may cause the worker heartbeat feature to not properly report client-side plugins.

src/Temporalio/Bridge/OptionsExtensions.cs#L246-L269

var scheme = options.Tls == null ? "http" : "https";
return new Interop.TemporalCoreClientOptions()
{
target_url = scope.ByteArray($"{scheme}://{options.TargetHost}"),
client_name = ClientName.Ref,
client_version = ClientVersion.Ref,
metadata = scope.Metadata(options.RpcMetadata),
api_key = scope.ByteArray(options.ApiKey),
identity = scope.ByteArray(options.Identity),
tls_options =
options.Tls == null ? null : scope.Pointer(options.Tls.ToInteropOptions(scope)),
retry_options =
options.RpcRetry == null
? null
: scope.Pointer(options.RpcRetry.ToInteropOptions()),
keep_alive_options =
options.KeepAlive == null
? null
: scope.Pointer(options.KeepAlive.ToInteropOptions()),
http_connect_proxy_options =
options.HttpConnectProxy == null
? null
: scope.Pointer(options.HttpConnectProxy.ToInteropOptions(scope)),
};

src/Temporalio/Bridge/Interop/Interop.cs#L217-L219

[NativeTypeName("struct TemporalCoreByteArrayRefArray")]
public TemporalCoreByteArrayRefArray plugin_names;

Fix in Cursor Fix in Web


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, only blocking concern is to remove the temporary enablement of the package.yml GH workflow

}
var pluginNames = options.Plugins?
.Select(p => p.Name)
.Where(name => !string.IsNullOrEmpty(name))
Copy link
Member

Choose a reason for hiding this comment

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

As cursor bot mentions above, probably don't need this line

Copy link
Member

Choose a reason for hiding this comment

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

All good, I missed this in Ruby which was pointed out to me at temporalio/sdk-ruby#368 (comment) which reminded me to point it out here

// We'll also test the metric prefix
Metrics = new() { Prometheus = new(promAddr), MetricPrefix = "foo_" },
},
WorkerHeartbeatInterval = null,
Copy link
Member

Choose a reason for hiding this comment

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

I'm curious why these had to be set to null in these tests (just trying to make sure our users won't have to do this in their tests)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ahh this is due to the metrics test failing from the Worker heartbeating configured for runtime, but server version does not support it warning message, the solution is either to disable it for now, or we can set the dynamic config flag to support heartbeating

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the test fails from seeing an additional log statement

Copy link
Member

@cretz cretz Dec 8, 2025

Choose a reason for hiding this comment

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

Hrmm, I don't see where these tests are checking logs. Can you explain a bit more how these tests were failing before setting this to null? I want to make sure our users don't have to do the same thing in the same situations. I am not seeing where the metrics would clash with worker heartbeat metrics, but I may be missing it. If unclear where it is failing, I can help debug.

Copy link
Member

Choose a reason for hiding this comment

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

Discussed off-PR, provided diff for TestUtils.cs to help this situation

@yuandrew yuandrew changed the title 💥Enable Worker heartbeating, update Core to 7419ada 💥Enable Worker heartbeating, update Core to 44a6576 Dec 5, 2025
@yuandrew yuandrew merged commit f384f20 into temporalio:main Dec 10, 2025
11 checks passed
@yuandrew yuandrew deleted the worker-heartbeating branch December 10, 2025 21:47
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] Enable Worker Heartbeating

2 participants