Skip to content

time-skipping runtime foundation#9965

Open
feiyang3cat wants to merge 12 commits intotemporalio:mainfrom
feiyang3cat:ts/runtime-1
Open

time-skipping runtime foundation#9965
feiyang3cat wants to merge 12 commits intotemporalio:mainfrom
feiyang3cat:ts/runtime-1

Conversation

@feiyang3cat
Copy link
Copy Markdown
Contributor

@feiyang3cat feiyang3cat commented Apr 15, 2026

What changed?

  1. taskGenerator:
  • add regenerate tasks for time skipping
  1. mutableState:
  • new methods: add key methods to transition time
  • IsStateDirty: captures changes of time-skipping related fields
  • closeTransaction: add closeTransactionHandlerTimeSkipping
  1. event: new time-skipping runtime event added
  2. persistence: new runtime data added to execution info

Why?

add the foundational mechanism of how time skipping works in the runtime of a workflow execution

for reviewers:
this is a intergral foundation of t-s runtime, so it works without any granular and extended features as bound, replication, external-transfer tasks, etc. Over 50% of lines are tests/mocks/pb-gen. can focus on code first, then functional tests, and last ut.

How did you test it?

  • built
  • run locally and tested manually
  • covered by existing tests
  • added new unit test(s)
  • added new functional test(s)

@feiyang3cat feiyang3cat requested review from a team as code owners April 15, 2026 18:25
@feiyang3cat feiyang3cat force-pushed the ts/runtime-1 branch 3 times, most recently from 4ae2cfe to c2a602f Compare April 15, 2026 19:03
@@ -7110,6 +7232,8 @@ func (ms *MutableStateImpl) closeTransaction(
return closeTransactionResult{}, err
}

Copy link
Copy Markdown
Contributor Author

@feiyang3cat feiyang3cat Apr 15, 2026

Choose a reason for hiding this comment

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

@yux0 it seems both active and passive cluster may both call closeTransaction, is it correct that if we want to add mutations of ms and generation of timer tasks in closeTransaction, we need to filter the policy for only active cluster

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Correct, the passive will handle this by seeing the event in the history that the active added.


// time-skipping related methods
AddWorkflowExecutionTimeSkippingTransitionedEvent(ctx context.Context) (*historypb.HistoryEvent, error)
ApplyWorkflowExecutionTimeSkippingTransitionedEvent(ctx context.Context, event *historypb.HistoryEvent) error
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@yux0 is it correct that we still need to add ApplyXXXEvent to support event-based replication?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

why do we need to support event-based replication for this feature?

Copy link
Copy Markdown
Contributor Author

@feiyang3cat feiyang3cat Apr 17, 2026

Choose a reason for hiding this comment

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

emm ~ Not sure if this is a question for me. Do we have standards to exclude features out of event-base replication or other replication method? I am assuming for completeness I need to support this so that replication center will catchup.

}
return true
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I haven't added this RegenerateTimerTasksForTimeSkipping in Refresh/PartialRefresh, and my understanding that the passive cluster will need it to generate timerTasks? @yux0

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

RegenerateTimerTasksForTimeSkipping would not be safe for use in partialRefresh as it adds new tasks rather than the idempotent adjustment that refreshTasksForTimer does. I think you would need to adjust refreshTaskForTimer by adding skipping to timer sequence.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

oh I see. I think we may consider several options
(option-1) I can add a timeSkipped field in TimerInfo to obtain idempotentcy
(option-2) refreshTasksForTimer -> I need to judge if I need to regenerate beased on if WorkflowExecutionInfo.TimeSkippingInfo changed
(option-3) in semantics (not in performance speaking), this method is idempotent calling twice will generate another timerTask with the same content -> is this acceptable?

I will leave a todo here and arrange a meeting when I add the replication feature.

targetTime time.Time,
triggeredDisable bool,
) *historypb.HistoryEvent {
event := b.createHistoryEvent(enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_TIME_SKIPPING_TRANSITIONED, b.timeSource.Now())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This b.timeSource.Now() can be called above and share in this line and 1092.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I guess you wanted to say the VirtualTime()? (this method will be deleted)


message TimeSkippingInfo {
// The config for the time skipping for the workflow.
// Current tconfiguration for the workflow.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: tconfiguration?

skippedDuration := attr.GetTargetTime().AsTime().Sub(event.GetEventTime().AsTime())
disabledAfterBound := attr.GetDisabledAfterBound()

if ms.executionInfo.TimeSkippingInfo.AccumulatedSkippedDuration == nil {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Better to check if TimeSkippingInfo is nil before accessing the member.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

added an error check

nextUserTimerInfo = timerInfo
}
}
if !disabledAfterBound && nextUserTimerInfo == nil {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

if disabledAfterBound is true and nextUserTimerInfo is nil, this check will not return an error. If that happens line 4027 below will panic.

I see that disabledAfterBound is always false now. But that could change when you fix the todo right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed


func (ms *MutableStateImpl) GetTimeSkippingVirtualTime() time.Time {
// TODO@time-skipping: need to use this to adjust all timestamps for tasks sent to SDK worker.
offset := ms.GetExecutionInfo().TimeSkippingInfo.AccumulatedSkippedDuration
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nil check for TimeSkippingInfo?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

method to be deleted.

Copy link
Copy Markdown
Contributor

@prathyushpv prathyushpv Apr 15, 2026

Choose a reason for hiding this comment

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

nit: This comment "this function must be the last call" could cause confusion later. Maybe clarify that this must be the last call before generating time skipping tasks.

enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_TIMED_OUT,
enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_TERMINATED,
enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_CONTINUED_AS_NEW,
enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_TIME_SKIPPING_TRANSITIONED,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

don't think it should be grouped under workflow state changes

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

udpated

}

func (ms *MutableStateImpl) GetTimeSkippingVirtualTime() time.Time {
// TODO@time-skipping: need to use this to adjust all timestamps for tasks sent to SDK worker.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

My understanding was that we will use a single TimeSource implementation and all events would use virtual timestamps, so no adjustment would be needed. Is that still the plan?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

you are right. let me change back the design and try to figure out how to make that simple. I will need to put that in another pr as this pr is growing large.

timerSequence := t.getTimerSequence(mutableState)
referenceTime := t.Now()
if tsInfo := mutableState.GetExecutionInfo().GetTimeSkippingInfo(); tsInfo != nil && tsInfo.GetAccumulatedSkippedDuration() != nil {
referenceTime = mutableState.GetTimeSkippingVirtualTime()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

do we need this for other timer tasks?

I understand for example there's no running activity at time skipping time, but there could be after time is skipped and we need to use the workflow time?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

this is bad design, removed

// pendingTimerInfoIDs in mutable state is only timers that need regenerated
// and the new timerTasks should not change the original pendingTimerInfoIDs in mutable state
for _, timerInfo := range r.mutableState.GetPendingTimerInfos() {
visibilityTimestamp := timerInfo.ExpiryTime.AsTime().Add(-accumulatedSkippedDuration)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

maybe I confused myself. I think we need to adjust timestamp for all timer tasks generated if time for a workflow is ever skipped?

}
}

func (ms *MutableStateImpl) closeTransactionHandlerTimeSkipping(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
func (ms *MutableStateImpl) closeTransactionHandlerTimeSkipping(
func (ms *MutableStateImpl) closeTransactionHandleTimeSkipping(

@feiyang3cat feiyang3cat force-pushed the ts/runtime-1 branch 2 times, most recently from 17add5c to 5e70e98 Compare April 17, 2026 01:09
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.

5 participants