temporalio / temporalio/sdk-java

TimeLockingFuture implementation is incorrect and easy to break

Open
#392 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Java
Stars
433
Forks
249
Avg merge
5d 6h
Merged PRs (30d)
26

Description

Expected Behavior

CompletableFuture returned from WorkflowStub#getResult and WorkflowStub#getResultAsync that has time skipping functionality built-in can correctly work with all CompletableFuture methods without losing time skipping functionality.

Actual Behavior

CompletableFuture that is returned from #getResultAsync when we use TestWorkflowService is TimeLockingFuture. It overrides CompletableFuture#get and CompletableFuture#join to provide time skipping functionality.
This implementation is extremely brittle because any chaining of this CompletableFuture will not call the overridden #get and #join. So even trivial chaining completely and unexpectedly breaks the time-skipping feature.

Steps to Reproduce the Problem

  1. Use TestWorkflowService
  2. Perform any trivial chaining on the result returning from getResultAsync
  3. Time skipping doesn't work on #get call on a chained CompletableFuture from step 2.

Demonstration of the problem: https://github.com/Spikhalskiy/java-sdk/commit/e73d574428a7001534601c3bebabb60a23612bff
Adding a trivial result = result.thenApply(s -> s); chaining breaks the test and it hangs.

Proposed solution

The current solution with intercepting stub methods and extending CompletableFuture looks like a hack and I'm not sure if it's possible to make it work correctly with CompletableFuture implementation.
My proposal is to completely remove built-in time skipping from WorkflowStub and TimeLockingFuture.
One of the correct and nice ways to give time-skipping to tests developers can be through exposing of method .skipTime on some helper object that would return an AutoCloseable object that will trigger disabling of time-skipping at the end. It could be used by the test writers using the following pattern:

try (TimeSkipUtils.forStub(worflowStub).skipTimeSegment()) {
    result = resultFuture.get()
}

TimeSkipUtils.forStub can dig into worflowStub and make a helper that is aware of specific TestWorkflowService and provides 
.skipTimeSegment() that returns our ```AutoCloseable``` resource described above.

Specifications

  • Version: Current master (5f2ac6133c6560984eff35cc85faafb85cbf2c94)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with WorkflowStub#getResultAsync, TimeLockingFuture, and TestWorkflowService, then reproduce the reported failure by applying thenApply to the returned CompletableFuture. Review how CompletableFuture chaining bypasses the overridden get and join methods. Done means the proposed time-skipping behavior remains reliable for chained futures, or the replacement test-facing API is defined and verified.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
testing
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.