@@ -137,6 +137,18 @@ public Integer Double(Integer n) {
137137 }
138138 }
139139
140+ public interface CronWorkflow {
141+ @ WorkflowMethod (executionStartToCloseTimeoutSeconds = 120 , taskList = TASK_LIST )
142+ String execute ();
143+ }
144+
145+ public static class CronWorkflowImpl implements CronWorkflow {
146+ @ Override
147+ public String execute () {
148+ return "done" ;
149+ }
150+ }
151+
140152 private static final boolean useDockerService = TestEnvironment .isUseDockerService ();
141153 private static final Logger logger = LoggerFactory .getLogger (StartWorkflowTest .class );
142154 private static final String DOMAIN = "test-domain" ;
@@ -259,6 +271,93 @@ public void testSignalStartWorkflowGRPCNoPropagation() {
259271 testSignalWithStartWorkflowHelper (service , mockTracer , false );
260272 }
261273
274+ @ Test
275+ public void testCronWorkflowSetsIsCronSpanTagGRPC () {
276+ Assume .assumeTrue (useDockerService );
277+ MockTracer mockTracer = new MockTracer ();
278+ IWorkflowService service =
279+ new WorkflowServiceGrpc (
280+ ClientOptions .newBuilder ().setTracer (mockTracer ).setPort (7833 ).build ());
281+ testCronWorkflowHelper (service , mockTracer );
282+ }
283+
284+ private void testCronWorkflowHelper (IWorkflowService service , MockTracer mockTracer ) {
285+ try {
286+ service .RegisterDomain (new RegisterDomainRequest ().setName (DOMAIN ));
287+ } catch (DomainAlreadyExistsError e ) {
288+ logger .info ("domain already registered" );
289+ } catch (Exception e ) {
290+ fail ("fail to register domain: " + e );
291+ }
292+
293+ WorkflowClient client =
294+ WorkflowClient .newInstance (
295+ service , WorkflowClientOptions .newBuilder ().setDomain (DOMAIN ).build ());
296+
297+ WorkerFactory workerFactory =
298+ WorkerFactory .newInstance (client , WorkerFactoryOptions .newBuilder ().build ());
299+ Worker worker = workerFactory .newWorker (TASK_LIST , WorkerOptions .newBuilder ().build ());
300+ worker .registerWorkflowImplementationTypes (CronWorkflowImpl .class );
301+ workerFactory .start ();
302+
303+ Span rootSpan = mockTracer .buildSpan ("Test Started" ).start ();
304+ mockTracer .activateSpan (rootSpan );
305+
306+ WorkflowStub wf =
307+ client .newUntypedWorkflowStub (
308+ "CronWorkflow::execute" ,
309+ new WorkflowOptions .Builder ()
310+ .setExecutionStartToCloseTimeout (Duration .ofMinutes (2 ))
311+ .setTaskList (TASK_LIST )
312+ .setCronSchedule ("* * * * *" )
313+ .build ());
314+ try {
315+ wf .start ();
316+
317+ // Cron's minimum interval is 1 minute, so wait for the first scheduled run to execute and
318+ // produce a finished cadence-ExecuteWorkflow span.
319+ MockSpan executeWorkflowSpan = awaitExecuteWorkflowSpan (mockTracer , Duration .ofSeconds (150 ));
320+ assertNotNull ("cadence-ExecuteWorkflow span not found" , executeWorkflowSpan );
321+ assertEquals (
322+ "cadenceIsCron tag should be true for cron workflows" ,
323+ Boolean .TRUE ,
324+ executeWorkflowSpan .tags ().get ("cadenceIsCron" ));
325+ } catch (Exception e ) {
326+ fail ("workflow failure: " + e );
327+ } finally {
328+ try {
329+ wf .cancel ();
330+ } catch (Exception ignored ) {
331+ // best effort: stop further cron runs
332+ }
333+ rootSpan .finish ();
334+ workerFactory .shutdown ();
335+ }
336+ }
337+
338+ private MockSpan awaitExecuteWorkflowSpan (MockTracer mockTracer , Duration timeout ) {
339+ long deadline = System .currentTimeMillis () + timeout .toMillis ();
340+ while (System .currentTimeMillis () < deadline ) {
341+ MockSpan span =
342+ mockTracer
343+ .finishedSpans ()
344+ .stream ()
345+ .filter (s -> s .operationName ().equals ("cadence-ExecuteWorkflow" ))
346+ .findFirst ()
347+ .orElse (null );
348+ if (span != null ) {
349+ return span ;
350+ }
351+ try {
352+ Thread .sleep (1000 );
353+ } catch (InterruptedException e ) {
354+ Thread .currentThread ().interrupt ();
355+ break ;
356+ }
357+ }
358+ return null ;
359+ }
360+
262361 private void testStartWorkflowHelper (
263362 IWorkflowService service , MockTracer mockTracer , boolean shouldPropagate ) {
264363 try {
0 commit comments