• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

temporalio / sdk-java / #188

25 Sep 2023 04:42PM UTC coverage: 77.369% (-0.3%) from 77.663%
#188

push

github-actions

web-flow
Fix null pointer on trigger immediately (#1865)

4 of 4 new or added lines in 1 file covered. (100.0%)

18670 of 24131 relevant lines covered (77.37%)

0.77 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.28
/temporal-sdk/src/main/java/io/temporal/client/WorkflowStubImpl.java
1
/*
2
 * Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved.
3
 *
4
 * Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
5
 *
6
 * Modifications copyright (C) 2017 Uber Technologies, Inc.
7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this material except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 *   http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
20

21
package io.temporal.client;
22

23
import com.google.common.base.Strings;
24
import io.grpc.Status;
25
import io.grpc.StatusRuntimeException;
26
import io.temporal.api.common.v1.WorkflowExecution;
27
import io.temporal.api.errordetails.v1.QueryFailedFailure;
28
import io.temporal.api.errordetails.v1.WorkflowExecutionAlreadyStartedFailure;
29
import io.temporal.api.errordetails.v1.WorkflowNotReadyFailure;
30
import io.temporal.api.update.v1.WaitPolicy;
31
import io.temporal.common.interceptors.Header;
32
import io.temporal.common.interceptors.WorkflowClientCallsInterceptor;
33
import io.temporal.failure.CanceledFailure;
34
import io.temporal.serviceclient.CheckedExceptionWrapper;
35
import io.temporal.serviceclient.StatusUtils;
36
import java.lang.reflect.Type;
37
import java.util.Optional;
38
import java.util.UUID;
39
import java.util.concurrent.*;
40
import java.util.concurrent.atomic.AtomicReference;
41
import javax.annotation.Nonnull;
42
import javax.annotation.Nullable;
43

44
class WorkflowStubImpl implements WorkflowStub {
45
  private final WorkflowClientOptions clientOptions;
46
  private final WorkflowClientCallsInterceptor workflowClientInvoker;
47
  private final Optional<String> workflowType;
48
  // Execution this stub is bound to
49
  private final AtomicReference<WorkflowExecution> execution = new AtomicReference<>();
1✔
50
  // Full WorkflowExecution that this stub is started if any.
51
  // After a start, WorkflowStub binds to (workflowId, null) to follow the chain of RunIds.
52
  // But this field keeps the full (workflowId, runId) execution info that was started by this stub.
53
  private final AtomicReference<WorkflowExecution> startedExecution = new AtomicReference<>();
1✔
54
  // if null, this stub is created to bound to an existing execution.
55
  // This stub is created to bound to an existing execution otherwise.
56
  private final @Nullable WorkflowOptions options;
57

58
  WorkflowStubImpl(
59
      WorkflowClientOptions clientOptions,
60
      WorkflowClientCallsInterceptor workflowClientInvoker,
61
      Optional<String> workflowType,
62
      WorkflowExecution execution) {
1✔
63
    this.clientOptions = clientOptions;
1✔
64
    this.workflowClientInvoker = workflowClientInvoker;
1✔
65
    this.workflowType = workflowType;
1✔
66
    if (execution == null || execution.getWorkflowId().isEmpty()) {
1✔
67
      throw new IllegalArgumentException("null or empty workflowId");
×
68
    }
69
    this.execution.set(execution);
1✔
70
    this.options = null;
1✔
71
  }
1✔
72

73
  WorkflowStubImpl(
74
      WorkflowClientOptions clientOptions,
75
      WorkflowClientCallsInterceptor workflowClientInvoker,
76
      String workflowType,
77
      @Nonnull WorkflowOptions options) {
1✔
78
    this.clientOptions = clientOptions;
1✔
79
    this.workflowClientInvoker = workflowClientInvoker;
1✔
80
    this.workflowType = Optional.of(workflowType);
1✔
81
    this.options = options;
1✔
82
  }
1✔
83

84
  @Override
85
  public void signal(String signalName, Object... args) {
86
    checkStarted();
1✔
87
    WorkflowExecution targetExecution = currentExecutionWithoutRunId();
1✔
88
    try {
89
      workflowClientInvoker.signal(
1✔
90
          new WorkflowClientCallsInterceptor.WorkflowSignalInput(
91
              targetExecution, signalName, Header.empty(), args));
1✔
92
    } catch (Exception e) {
1✔
93
      Throwable throwable = throwAsWorkflowFailureException(e, targetExecution);
×
94
      throw new WorkflowServiceException(targetExecution, workflowType.orElse(null), throwable);
×
95
    }
1✔
96
  }
1✔
97

98
  private WorkflowExecution startWithOptions(WorkflowOptions options, Object... args) {
99
    checkExecutionIsNotStarted();
1✔
100
    String workflowId = getWorkflowIdForStart(options);
1✔
101
    WorkflowExecution workflowExecution = null;
1✔
102
    try {
103
      WorkflowClientCallsInterceptor.WorkflowStartOutput workflowStartOutput =
1✔
104
          workflowClientInvoker.start(
1✔
105
              new WorkflowClientCallsInterceptor.WorkflowStartInput(
106
                  workflowId, workflowType.get(), Header.empty(), args, options));
1✔
107
      workflowExecution = workflowStartOutput.getWorkflowExecution();
1✔
108
      populateExecutionAfterStart(workflowExecution);
1✔
109
      return workflowExecution;
1✔
110
    } catch (StatusRuntimeException e) {
1✔
111
      throw wrapStartException(workflowId, workflowType.orElse(null), e);
1✔
112
    } catch (Exception e) {
1✔
113
      if (workflowExecution == null) {
1✔
114
        // if start failed with exception - there could be no valid workflow execution populated
115
        // from the server.
116
        // WorkflowServiceException requires not null workflowExecution, so we have to provide
117
        // an WorkflowExecution instance with just a workflowId
118
        workflowExecution = WorkflowExecution.newBuilder().setWorkflowId(workflowId).build();
1✔
119
      }
120
      throw new WorkflowServiceException(workflowExecution, workflowType.orElse(null), e);
1✔
121
    }
122
  }
123

124
  @Override
125
  public WorkflowExecution start(Object... args) {
126
    if (options == null) {
1✔
127
      throw new IllegalStateException("Required parameter WorkflowOptions is missing");
×
128
    }
129
    return startWithOptions(WorkflowOptions.merge(null, null, options), args);
1✔
130
  }
131

132
  private WorkflowExecution signalWithStartWithOptions(
133
      WorkflowOptions options, String signalName, Object[] signalArgs, Object[] startArgs) {
134
    checkExecutionIsNotStarted();
1✔
135
    String workflowId = getWorkflowIdForStart(options);
1✔
136
    WorkflowExecution workflowExecution = null;
1✔
137
    try {
138
      WorkflowClientCallsInterceptor.WorkflowSignalWithStartOutput workflowStartOutput =
1✔
139
          workflowClientInvoker.signalWithStart(
1✔
140
              new WorkflowClientCallsInterceptor.WorkflowSignalWithStartInput(
141
                  new WorkflowClientCallsInterceptor.WorkflowStartInput(
142
                      workflowId, workflowType.get(), Header.empty(), startArgs, options),
1✔
143
                  signalName,
144
                  signalArgs));
145
      workflowExecution = workflowStartOutput.getWorkflowStartOutput().getWorkflowExecution();
1✔
146
      populateExecutionAfterStart(workflowExecution);
1✔
147
      return workflowExecution;
1✔
148
    } catch (StatusRuntimeException e) {
1✔
149
      throw wrapStartException(workflowId, workflowType.orElse(null), e);
1✔
150
    } catch (Exception e) {
×
151
      if (workflowExecution == null) {
×
152
        // if start failed with exception - there could be no valid workflow execution populated
153
        // from the server.
154
        // WorkflowServiceException requires not null workflowExecution, so we have to provide
155
        // an WorkflowExecution instance with just a workflowId
156
        workflowExecution = WorkflowExecution.newBuilder().setWorkflowId(workflowId).build();
×
157
      }
158
      throw new WorkflowServiceException(workflowExecution, workflowType.orElse(null), e);
×
159
    }
160
  }
161

162
  private static String getWorkflowIdForStart(WorkflowOptions options) {
163
    String workflowId = options.getWorkflowId();
1✔
164
    if (workflowId == null) {
1✔
165
      workflowId = UUID.randomUUID().toString();
1✔
166
    }
167
    return workflowId;
1✔
168
  }
169

170
  @Override
171
  public WorkflowExecution signalWithStart(
172
      String signalName, Object[] signalArgs, Object[] startArgs) {
173
    if (options == null) {
1✔
174
      throw new IllegalStateException("Required parameter WorkflowOptions is missing");
×
175
    }
176
    return signalWithStartWithOptions(
1✔
177
        WorkflowOptions.merge(null, null, options), signalName, signalArgs, startArgs);
1✔
178
  }
179

180
  @Override
181
  public Optional<String> getWorkflowType() {
182
    return workflowType;
1✔
183
  }
184

185
  @Override
186
  public WorkflowExecution getExecution() {
187
    return options != null ? startedExecution.get() : execution.get();
1✔
188
  }
189

190
  @Override
191
  public <R> R getResult(Class<R> resultClass) {
192
    return getResult(resultClass, resultClass);
1✔
193
  }
194

195
  @Override
196
  public <R> R getResult(Class<R> resultClass, Type resultType) {
197
    try {
198
      // int max to not overflow long
199
      return getResult(Integer.MAX_VALUE, TimeUnit.MILLISECONDS, resultClass, resultType);
1✔
200
    } catch (TimeoutException e) {
×
201
      throw new WorkflowServiceException(execution.get(), workflowType.orElse(null), e);
×
202
    }
203
  }
204

205
  @Override
206
  public <R> R getResult(long timeout, TimeUnit unit, Class<R> resultClass)
207
      throws TimeoutException {
208
    return getResult(timeout, unit, resultClass, resultClass);
×
209
  }
210

211
  @Override
212
  public <R> R getResult(long timeout, TimeUnit unit, Class<R> resultClass, Type resultType)
213
      throws TimeoutException {
214
    checkStarted();
1✔
215
    WorkflowExecution targetExecution = execution.get();
1✔
216
    try {
217
      WorkflowClientCallsInterceptor.GetResultOutput<R> result =
1✔
218
          workflowClientInvoker.getResult(
1✔
219
              new WorkflowClientCallsInterceptor.GetResultInput<>(
220
                  targetExecution, workflowType, timeout, unit, resultClass, resultType));
221
      return result.getResult();
1✔
222
    } catch (Exception e) {
1✔
223
      return throwAsWorkflowFailureExceptionForResult(e, resultClass, targetExecution);
×
224
    }
225
  }
226

227
  @Override
228
  public <R> CompletableFuture<R> getResultAsync(Class<R> resultClass) {
229
    return getResultAsync(resultClass, resultClass);
1✔
230
  }
231

232
  @Override
233
  public <R> CompletableFuture<R> getResultAsync(Class<R> resultClass, Type resultType) {
234
    return getResultAsync(Long.MAX_VALUE, TimeUnit.MILLISECONDS, resultClass, resultType);
1✔
235
  }
236

237
  @Override
238
  public <R> CompletableFuture<R> getResultAsync(
239
      long timeout, TimeUnit unit, Class<R> resultClass) {
240
    return getResultAsync(timeout, unit, resultClass, resultClass);
1✔
241
  }
242

243
  @Override
244
  public <R> CompletableFuture<R> getResultAsync(
245
      long timeout, TimeUnit unit, Class<R> resultClass, Type resultType) {
246
    checkStarted();
1✔
247
    WorkflowExecution targetExecution = execution.get();
1✔
248
    WorkflowClientCallsInterceptor.GetResultAsyncOutput<R> result =
1✔
249
        workflowClientInvoker.getResultAsync(
1✔
250
            new WorkflowClientCallsInterceptor.GetResultInput<>(
251
                targetExecution, workflowType, timeout, unit, resultClass, resultType));
252
    return result
1✔
253
        .getResult()
1✔
254
        .exceptionally(
1✔
255
            e -> {
256
              try {
257
                return throwAsWorkflowFailureExceptionForResult(e, resultClass, targetExecution);
×
258
              } catch (TimeoutException ex) {
1✔
259
                throw new CompletionException(ex);
1✔
260
              }
261
            });
262
  }
263

264
  @Override
265
  public <R> R query(String queryType, Class<R> resultClass, Object... args) {
266
    return query(queryType, resultClass, resultClass, args);
1✔
267
  }
268

269
  @Override
270
  public <R> R query(String queryType, Class<R> resultClass, Type resultType, Object... args) {
271
    checkStarted();
1✔
272
    WorkflowClientCallsInterceptor.QueryOutput<R> result;
273
    WorkflowExecution targetExecution = execution.get();
1✔
274
    try {
275
      result =
1✔
276
          workflowClientInvoker.query(
1✔
277
              new WorkflowClientCallsInterceptor.QueryInput<>(
278
                  targetExecution, queryType, Header.empty(), args, resultClass, resultType));
1✔
279
    } catch (Exception e) {
1✔
280
      return throwAsWorkflowFailureExceptionForQuery(e, resultClass, targetExecution);
×
281
    }
1✔
282
    if (result.isQueryRejected()) {
1✔
283
      throw new WorkflowQueryConditionallyRejectedException(
1✔
284
          targetExecution,
285
          workflowType.orElse(null),
1✔
286
          clientOptions.getQueryRejectCondition(),
1✔
287
          result.getQueryRejectedStatus(),
1✔
288
          null);
289
    }
290
    return result.getResult();
1✔
291
  }
292

293
  @Override
294
  public <R> R update(String updateName, Class<R> resultClass, Object... args) {
295
    checkStarted();
1✔
296
    try {
297
      UpdateOptions<R> options =
298
          UpdateOptions.<R>newBuilder()
1✔
299
              .setUpdateName(updateName)
1✔
300
              .setWaitPolicy(UpdateWaitPolicy.COMPLETED)
1✔
301
              .setResultClass(resultClass)
1✔
302
              .build();
1✔
303
      return startUpdate(options, args).getResultAsync().get();
1✔
304
    } catch (InterruptedException e) {
×
305
      throw new RuntimeException(e);
×
306
    } catch (ExecutionException e) {
×
307
      throw new RuntimeException(e);
×
308
    }
309
  }
310

311
  @Override
312
  public <R> UpdateHandle<R> startUpdate(String updateName, Class<R> resultClass, Object... args) {
313
    UpdateOptions<R> options =
314
        UpdateOptions.<R>newBuilder()
1✔
315
            .setUpdateName(updateName)
1✔
316
            .setWaitPolicy(UpdateWaitPolicy.ACCEPTED)
1✔
317
            .setResultClass(resultClass)
1✔
318
            .setResultType(resultClass)
1✔
319
            .build();
1✔
320

321
    return startUpdate(options, args);
1✔
322
  }
323

324
  @Override
325
  public <R> UpdateHandle<R> startUpdate(UpdateOptions<R> options, Object... args) {
326
    checkStarted();
1✔
327
    options.validate();
1✔
328
    WorkflowExecution targetExecution = execution.get();
1✔
329
    try {
330
      WorkflowClientCallsInterceptor.StartUpdateOutput<R> result =
1✔
331
          workflowClientInvoker.startUpdate(
1✔
332
              new WorkflowClientCallsInterceptor.StartUpdateInput<>(
333
                  targetExecution,
334
                  options.getUpdateName(),
1✔
335
                  Header.empty(),
1✔
336
                  options.getUpdateId(),
1✔
337
                  args,
338
                  options.getResultClass(),
1✔
339
                  options.getResultType(),
1✔
340
                  options.getFirstExecutionRunId(),
1✔
341
                  WaitPolicy.newBuilder()
1✔
342
                      .setLifecycleStage(options.getWaitPolicy().getProto())
1✔
343
                      .build()));
1✔
344

345
      if (result.hasResult()) {
1✔
346
        return new CompletedUpdateHandleImpl<>(
1✔
347
            result.getReference().getUpdateId(),
1✔
348
            result.getReference().getWorkflowExecution(),
1✔
349
            result.getResult());
1✔
350
      } else {
351
        return new LazyUpdateHandleImpl<>(
1✔
352
            workflowClientInvoker,
353
            workflowType.orElse(null),
1✔
354
            options.getUpdateName(),
1✔
355
            result.getReference().getUpdateId(),
1✔
356
            result.getReference().getWorkflowExecution(),
1✔
357
            options.getResultClass(),
1✔
358
            options.getResultType());
1✔
359
      }
360
    } catch (Exception e) {
1✔
361
      Throwable throwable = throwAsWorkflowFailureException(e, targetExecution);
×
362
      throw new WorkflowServiceException(targetExecution, workflowType.orElse(null), throwable);
×
363
    }
364
  }
365

366
  @Override
367
  public <R> UpdateHandle<R> getUpdateHandle(String updateId, Class<R> resultClass) {
368
    return new LazyUpdateHandleImpl<>(
1✔
369
        workflowClientInvoker,
370
        workflowType.orElse(null),
1✔
371
        "",
372
        updateId,
373
        execution.get(),
1✔
374
        resultClass,
375
        resultClass);
376
  }
377

378
  @Override
379
  public <R> UpdateHandle<R> getUpdateHandle(
380
      String updateId, Class<R> resultClass, Type resultType) {
381
    return new LazyUpdateHandleImpl<>(
×
382
        workflowClientInvoker,
383
        workflowType.orElse(null),
×
384
        "",
385
        updateId,
386
        execution.get(),
×
387
        resultClass,
388
        resultType);
389
  }
390

391
  @Override
392
  public void cancel() {
393
    checkStarted();
1✔
394
    WorkflowExecution targetExecution = currentExecutionWithoutRunId();
1✔
395
    try {
396
      workflowClientInvoker.cancel(new WorkflowClientCallsInterceptor.CancelInput(targetExecution));
1✔
397
    } catch (Exception e) {
1✔
398
      Throwable failure = throwAsWorkflowFailureException(e, targetExecution);
×
399
      throw new WorkflowServiceException(targetExecution, workflowType.orElse(null), failure);
×
400
    }
1✔
401
  }
1✔
402

403
  @Override
404
  public void terminate(@Nullable String reason, Object... details) {
405
    checkStarted();
1✔
406
    WorkflowExecution targetExecution = currentExecutionWithoutRunId();
1✔
407
    try {
408
      workflowClientInvoker.terminate(
1✔
409
          new WorkflowClientCallsInterceptor.TerminateInput(targetExecution, reason, details));
410
    } catch (Exception e) {
1✔
411
      Throwable failure = throwAsWorkflowFailureException(e, targetExecution);
×
412
      throw new WorkflowServiceException(targetExecution, workflowType.orElse(null), failure);
×
413
    }
1✔
414
  }
1✔
415

416
  @Override
417
  public Optional<WorkflowOptions> getOptions() {
418
    return Optional.ofNullable(options);
1✔
419
  }
420

421
  private void checkStarted() {
422
    if (execution.get() == null || execution.get().getWorkflowId() == null) {
1✔
423
      throw new IllegalStateException("Null workflowId. Was workflow started?");
×
424
    }
425
  }
1✔
426

427
  private void checkExecutionIsNotStarted() {
428
    if (execution.get() != null) {
1✔
429
      throw new IllegalStateException(
1✔
430
          "Cannot reuse a stub instance to start more than one workflow execution. The stub "
431
              + "points to already started execution. If you are trying to wait for a workflow completion either "
432
              + "change WorkflowIdReusePolicy from AllowDuplicate or use WorkflowStub.getResult");
433
    }
434
  }
1✔
435

436
  /*
437
   * Exceptions handling and processing for all methods of the stub
438
   */
439
  private RuntimeException wrapStartException(
440
      String workflowId, String workflowType, StatusRuntimeException e) {
441
    WorkflowExecution.Builder executionBuilder =
442
        WorkflowExecution.newBuilder().setWorkflowId(workflowId);
1✔
443

444
    WorkflowExecutionAlreadyStartedFailure f =
1✔
445
        StatusUtils.getFailure(e, WorkflowExecutionAlreadyStartedFailure.class);
1✔
446
    if (f != null) {
1✔
447
      WorkflowExecution exe = executionBuilder.setRunId(f.getRunId()).build();
1✔
448
      populateExecutionAfterStart(exe);
1✔
449
      return new WorkflowExecutionAlreadyStarted(exe, workflowType, e);
1✔
450
    } else {
451
      WorkflowExecution exe = executionBuilder.build();
1✔
452
      return new WorkflowServiceException(exe, workflowType, e);
1✔
453
    }
454
  }
455

456
  /**
457
   * RunId can change e.g. workflow does ContinueAsNew. Emptying runId in workflowExecution allows
458
   * Temporal server figure out the current run id dynamically.
459
   */
460
  private WorkflowExecution currentExecutionWithoutRunId() {
461
    WorkflowExecution workflowExecution = execution.get();
1✔
462
    if (Strings.isNullOrEmpty(workflowExecution.getRunId())) {
1✔
463
      return workflowExecution;
1✔
464
    } else {
465
      return WorkflowExecution.newBuilder(workflowExecution).setRunId("").build();
1✔
466
    }
467
  }
468

469
  private <R> R throwAsWorkflowFailureExceptionForQuery(
470
      Throwable failure,
471
      @SuppressWarnings("unused") Class<R> returnType,
472
      WorkflowExecution targetExecution) {
473
    failure = throwAsWorkflowFailureException(failure, targetExecution);
1✔
474
    if (failure instanceof StatusRuntimeException) {
1✔
475
      StatusRuntimeException sre = (StatusRuntimeException) failure;
1✔
476
      if (StatusUtils.hasFailure(sre, QueryFailedFailure.class)) {
1✔
477
        throw new WorkflowQueryException(execution.get(), workflowType.orElse(null), failure);
1✔
478
      } else if (Status.Code.FAILED_PRECONDITION.equals(sre.getStatus().getCode())
×
479
          && StatusUtils.hasFailure(sre, WorkflowNotReadyFailure.class)) {
×
480
        // Processes the edge case introduced by https://github.com/temporalio/temporal/pull/2826
481
        throw new WorkflowQueryRejectedException(
×
482
            targetExecution, workflowType.orElse(null), failure);
×
483
      }
484
    }
485
    throw new WorkflowServiceException(targetExecution, workflowType.orElse(null), failure);
×
486
  }
487

488
  // This function never returns anything, it only throws
489
  private <R> R throwAsWorkflowFailureExceptionForResult(
490
      Throwable failure,
491
      @SuppressWarnings("unused") Class<R> returnType,
492
      WorkflowExecution targetExecution)
493
      throws TimeoutException {
494
    failure = throwAsWorkflowFailureException(failure, targetExecution);
1✔
495
    if (failure instanceof TimeoutException) {
1✔
496
      throw (TimeoutException) failure;
1✔
497
    } else if (failure instanceof CanceledFailure) {
1✔
498
      throw (CanceledFailure) failure;
×
499
    }
500
    throw new WorkflowServiceException(targetExecution, workflowType.orElse(null), failure);
1✔
501
  }
502

503
  private Throwable throwAsWorkflowFailureException(
504
      Throwable failure, WorkflowExecution targetExecution) {
505
    if (failure instanceof CompletionException) {
1✔
506
      // if we work with CompletableFuture, the exception may be wrapped into CompletionException
507
      failure = failure.getCause();
1✔
508
    }
509
    failure = CheckedExceptionWrapper.unwrap(failure);
1✔
510
    if (failure instanceof Error) {
1✔
511
      throw (Error) failure;
×
512
    }
513
    if (failure instanceof StatusRuntimeException) {
1✔
514
      StatusRuntimeException sre = (StatusRuntimeException) failure;
1✔
515
      if (Status.Code.NOT_FOUND.equals(sre.getStatus().getCode())) {
1✔
516
        throw new WorkflowNotFoundException(targetExecution, workflowType.orElse(null), sre);
1✔
517
      }
518
    } else if (failure instanceof WorkflowException) {
1✔
519
      throw (WorkflowException) failure;
1✔
520
    }
521
    return failure;
1✔
522
  }
523

524
  private void populateExecutionAfterStart(WorkflowExecution startedExecution) {
525
    this.startedExecution.set(startedExecution);
1✔
526
    // bind to an execution without a runId, so queries follow runId chains by default
527
    this.execution.set(WorkflowExecution.newBuilder(startedExecution).setRunId("").build());
1✔
528
  }
1✔
529
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc