@@ -169,11 +169,14 @@ type processingTimeFn struct {
169169 Offset int
170170 TimerOutput int
171171 Cap int
172+
173+ InitialDelaySec int
174+ RecurringDelaySec int
172175}
173176
174177func (fn * processingTimeFn ) ProcessElement (sp state.Provider , tp timers.Provider , key string , value int , emit func (string , int )) {
175178 // Sets a processing time callback to occur.
176- fn .Callback .Set (tp , time .Now ().Add (9 * time .Second ))
179+ fn .Callback .Set (tp , time .Now ().Add (time . Duration ( fn . InitialDelaySec ) * time .Second ))
177180
178181 // Only write to the state if we haven't done so already.
179182 // Writing blind would reset the state, and cause duplicated outputs.
@@ -205,7 +208,7 @@ func (fn *processingTimeFn) OnTimer(ctx context.Context, ts beam.EventTime, sp s
205208 if err := fn .MyValue .Write (sp , read + 1 ); err != nil {
206209 panic (err )
207210 }
208- fn .Callback .Set (tp , time .Now ().Add (9 * time .Second ))
211+ fn .Callback .Set (tp , time .Now ().Add (time . Duration ( fn . RecurringDelaySec ) * time .Second ))
209212 }
210213 if num , _ , err := fn .Emissions .Read (sp ); err != nil {
211214 panic (err )
@@ -237,15 +240,28 @@ func init() {
237240 register .Function3x0 (regroup )
238241}
239242
243+ // timersProcessingTimePipelineBuilder constructs a pipeline to validate the behavior of processing time timers.
244+ // It generates a set of keyed elements and uses a DoFn (`processingTimeFn`) to set an initial processing time
245+ // timer for each key. When a timer fires, the DoFn emits an element, increments a counter in state, and
246+ // sets a new timer to fire after a recurring delay, continuing until a specified number of emissions for that
247+ // key is reached.
248+ //
249+ // The total approximate runtime of the timer-based logic for each key is calculated as:
250+ // InitialDelay + (numDuplicateTimers - 1) * RecurringDelay.
251+ // Note that the number of keys is irrelevant to the runtime, because keys are processed in parallel.
240252func timersProcessingTimePipelineBuilder (makeImp func (s beam.Scope ) beam.PCollection ) func (s beam.Scope ) {
241253 return func (s beam.Scope ) {
242254 var inputs , wantOutputs []kv [string , int ]
243255
244256 offset := 5000
245257 timerOutput := 4093
246258
259+ // Control the total runtime of the test to under 30 secs.
260+ // The runtime for the current setting is 3 + (5 - 1) * 1 = 7 secs
247261 numKeys := 40
248- numDuplicateTimers := 15
262+ numDuplicateTimers := 5
263+ initialDelaySec := 3
264+ recurringDelaySec := 1
249265
250266 for key := 0 ; key < numKeys ; key ++ {
251267 k := strconv .Itoa (key )
@@ -261,11 +277,13 @@ func timersProcessingTimePipelineBuilder(makeImp func(s beam.Scope) beam.PCollec
261277 Inputs : inputs ,
262278 }, imp )
263279 times := beam .ParDo (s , & processingTimeFn {
264- Offset : offset ,
265- TimerOutput : timerOutput ,
266- Callback : timers .InProcessingTime ("Callback" ),
267- MyValue : state.MakeValueState [int ]("MyValue" ),
268- Cap : numDuplicateTimers , // Syncs the cycles to the number of duplicate keyed inputs.
280+ Offset : offset ,
281+ TimerOutput : timerOutput ,
282+ Callback : timers .InProcessingTime ("Callback" ),
283+ MyValue : state.MakeValueState [int ]("MyValue" ),
284+ Cap : numDuplicateTimers , // Syncs the cycles to the number of duplicate keyed inputs.
285+ InitialDelaySec : initialDelaySec ,
286+ RecurringDelaySec : recurringDelaySec ,
269287 }, keyed )
270288 // We GroupByKey here so input to passert is blocked until teststream advances time to Infinity.
271289 gbk := beam .GroupByKey (s , times )
@@ -298,6 +316,6 @@ func TimersProcessingTime_Bounded(s beam.Scope) {
298316func TimersProcessingTime_Unbounded (s beam.Scope ) {
299317 timersProcessingTimePipelineBuilder (func (s beam.Scope ) beam.PCollection {
300318 now := time .Now ()
301- return periodic .Impulse (s , now , now .Add (10 * time .Second ), 0 , false )
319+ return periodic .Impulse (s , now , now .Add (10 * time .Second ), 5 * time . Second , false )
302320 })(s )
303321}
0 commit comments