From dff2e79671e746de36d6b7b186e0626b9752e26f Mon Sep 17 00:00:00 2001 From: Matt Silva Date: Fri, 24 Jul 2026 11:00:35 -0700 Subject: [PATCH] fix(client): propagate priority on signalWithStart WorkflowClientRequestFactory.newSignalWithStartWorkflowExecutionRequest copied retryPolicy, memo, searchAttributes, header, startDelay, userMetadata, and versioningOverride off the built start request but never copied priority. As a result, the Priority set on WorkflowOptions (priorityKey, fairnessKey, fairnessWeight) was silently dropped on signalWithStart while a plain start propagated it correctly. Copy the whole Priority proto so all three fields carry through, matching the plain start path and the class's "keep the two builders in sync" comment. --- .../client/WorkflowClientRequestFactory.java | 4 + ...wClientInvokerPriorityPropagationTest.java | 86 +++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 temporal-sdk/src/test/java/io/temporal/internal/client/RootWorkflowClientInvokerPriorityPropagationTest.java diff --git a/temporal-sdk/src/main/java/io/temporal/internal/client/WorkflowClientRequestFactory.java b/temporal-sdk/src/main/java/io/temporal/internal/client/WorkflowClientRequestFactory.java index c8d9a3cca2..f802283efc 100644 --- a/temporal-sdk/src/main/java/io/temporal/internal/client/WorkflowClientRequestFactory.java +++ b/temporal-sdk/src/main/java/io/temporal/internal/client/WorkflowClientRequestFactory.java @@ -210,6 +210,10 @@ SignalWithStartWorkflowExecutionRequest.Builder newSignalWithStartWorkflowExecut request.setVersioningOverride(startParameters.getVersioningOverride()); } + if (startParameters.hasPriority()) { + request.setPriority(startParameters.getPriority()); + } + return request; } diff --git a/temporal-sdk/src/test/java/io/temporal/internal/client/RootWorkflowClientInvokerPriorityPropagationTest.java b/temporal-sdk/src/test/java/io/temporal/internal/client/RootWorkflowClientInvokerPriorityPropagationTest.java new file mode 100644 index 0000000000..f6acf7e77e --- /dev/null +++ b/temporal-sdk/src/test/java/io/temporal/internal/client/RootWorkflowClientInvokerPriorityPropagationTest.java @@ -0,0 +1,86 @@ +package io.temporal.internal.client; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import io.temporal.api.common.v1.Priority; +import io.temporal.api.workflowservice.v1.SignalWithStartWorkflowExecutionRequest; +import io.temporal.api.workflowservice.v1.SignalWithStartWorkflowExecutionResponse; +import io.temporal.client.WorkflowClientOptions; +import io.temporal.client.WorkflowOptions; +import io.temporal.common.interceptors.Header; +import io.temporal.common.interceptors.WorkflowClientCallsInterceptor.WorkflowSignalWithStartInput; +import io.temporal.common.interceptors.WorkflowClientCallsInterceptor.WorkflowStartInput; +import io.temporal.internal.client.external.GenericWorkflowClient; +import io.temporal.internal.common.ProtoConverters; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +/** + * Unit test guarding that {@link RootWorkflowClientInvoker#signalWithStart} propagates the {@link + * io.temporal.common.Priority} set on {@link WorkflowOptions} onto the outgoing {@link + * SignalWithStartWorkflowExecutionRequest}. A plain {@code start} already carries priority, so this + * asserts the signalWithStart path stays in sync. Priority also carries the fairness fields, so a + * regression here silently drops fairness as well. + */ +public class RootWorkflowClientInvokerPriorityPropagationTest { + + private static final String NAMESPACE = "test-namespace"; + private static final String WORKFLOW_ID = "wf-target"; + + private GenericWorkflowClient genericClient; + private RootWorkflowClientInvoker invoker; + + @Before + public void setUp() { + genericClient = mock(GenericWorkflowClient.class); + invoker = + new RootWorkflowClientInvoker( + genericClient, + WorkflowClientOptions.newBuilder() + .setNamespace(NAMESPACE) + .validateAndBuildWithDefaults(), + new WorkerFactoryRegistry()); + } + + @Test + public void signalWithStartPropagatesPriorityAndFairness() { + io.temporal.common.Priority priority = + io.temporal.common.Priority.newBuilder() + .setPriorityKey(5) + .setFairnessKey("tenant-123") + .setFairnessWeight(2.5f) + .build(); + WorkflowOptions options = + WorkflowOptions.newBuilder().setTaskQueue("tq").setPriority(priority).build(); + + when(genericClient.signalWithStart(any(SignalWithStartWorkflowExecutionRequest.class))) + .thenReturn( + SignalWithStartWorkflowExecutionResponse.newBuilder().setRunId("target-run").build()); + + invoker.signalWithStart(newSignalWithStartInput(options)); + + ArgumentCaptor captor = + ArgumentCaptor.forClass(SignalWithStartWorkflowExecutionRequest.class); + org.mockito.Mockito.verify(genericClient).signalWithStart(captor.capture()); + SignalWithStartWorkflowExecutionRequest sent = captor.getValue(); + + Assert.assertTrue("request should carry priority", sent.hasPriority()); + Priority expected = ProtoConverters.toProto(priority); + Assert.assertEquals(expected, sent.getPriority()); + Assert.assertEquals(5, sent.getPriority().getPriorityKey()); + Assert.assertEquals("tenant-123", sent.getPriority().getFairnessKey()); + Assert.assertEquals(2.5f, sent.getPriority().getFairnessWeight(), 0.0f); + } + + private static WorkflowSignalWithStartInput newSignalWithStartInput(WorkflowOptions options) { + WorkflowStartInput startInput = + new WorkflowStartInput( + WORKFLOW_ID, "TestWorkflow", Header.empty(), new Object[] {}, options); + return new WorkflowSignalWithStartInput( + startInput, "test-signal", new Object[] {"signal-payload"}); + } +}