@@ -1215,7 +1215,9 @@ type stageKind interface {
12151215 // buildEventTimeBundle handles building bundles for the stage per it's kind.
12161216 buildEventTimeBundle (ss * stageState , watermark mtime.Time ) (toProcess elementHeap , minTs mtime.Time , newKeys set [string ],
12171217 holdsInBundle map [mtime.Time ]int , panesInBundle []bundlePane , schedulable bool , pendingAdjustment int )
1218-
1218+ // buildProcessingTimeBundle handles building processing-time bundles for the stage per it's kind.
1219+ buildProcessingTimeBundle (ss * stageState , em * ElementManager , emNow mtime.Time ) (toProcess elementHeap , minTs mtime.Time , newKeys set [string ],
1220+ holdsInBundle map [mtime.Time ]int , schedulable bool )
12191221 // getPaneOrDefault based on the stage state, element metadata, and bundle id.
12201222 getPaneOrDefault (ss * stageState , defaultPane typex.PaneInfo , w typex.Window , keyBytes []byte , bundID string ) typex.PaneInfo
12211223}
@@ -1327,17 +1329,54 @@ func (ss *stageState) injectTriggeredBundlesIfReady(em *ElementManager, window t
13271329 ready := ss .strat .IsTriggerReady (triggerInput {
13281330 newElementCount : 1 ,
13291331 endOfWindowReached : endOfWindowReached ,
1332+ emNow : em .ProcessingTimeNow (),
13301333 }, & state )
13311334
13321335 if ready {
13331336 state .Pane = computeNextTriggeredPane (state .Pane , endOfWindowReached )
1337+ } else {
1338+ if pts := ss .strat .GetAfterProcessingTimeTriggers (); pts != nil {
1339+ for _ , t := range pts {
1340+ ts := (& state ).getTriggerState (t )
1341+ if ts .extra == nil || t .shouldFire ((& state )) {
1342+ // Skipping inserting a processing time timer if the firing time
1343+ // is not set or it already should fire.
1344+ // When the after processing time triggers should fire, there are
1345+ // two scenarios:
1346+ // (1) the entire trigger of this window is ready to fire. In this
1347+ // case, `ready` should be true and we won't reach here.
1348+ // (2) we are still waiting for other triggers (subtriggers) to
1349+ // fire (e.g. AfterAll).
1350+ continue
1351+ }
1352+ firingTime := ts .extra .(afterProcessingTimeState ).firingTime
1353+ notYetHolds := map [mtime.Time ]int {}
1354+ timer := element {
1355+ window : window ,
1356+ timestamp : firingTime ,
1357+ holdTimestamp : window .MaxTimestamp (),
1358+ pane : typex .NoFiringPane (),
1359+ transform : ss .ID , // Use stage id to fake transform id
1360+ family : "AfterProcessingTime" ,
1361+ tag : "" ,
1362+ sequence : 1 ,
1363+ elmBytes : nil ,
1364+ keyBytes : []byte (key ),
1365+ }
1366+ // TODO: how to deal with watermark holds for this implicit processing time timer
1367+ // ss.watermarkHolds.Add(timer.holdTimestamp, 1)
1368+ ss .processingTimeTimers .Persist (firingTime , timer , notYetHolds )
1369+ em .processTimeEvents .Schedule (firingTime , ss .ID )
1370+ em .wakeUpAt (firingTime )
1371+ }
1372+ }
13341373 }
13351374 // Store the state as triggers may have changed it.
13361375 ss .state [LinkID {}][window ][key ] = state
13371376
13381377 // If we're ready, it's time to fire!
13391378 if ready {
1340- count += ss .buildTriggeredBundle (em , key , window )
1379+ count += ss .startTriggeredBundle (em , key , window )
13411380 }
13421381 return count
13431382}
@@ -1524,16 +1563,11 @@ func (ss *stageState) savePanes(bundID string, panesInBundle []bundlePane) {
15241563 }
15251564}
15261565
1527- // buildTriggeredBundle must be called with the stage.mu lock held.
1528- // When in discarding mode, returns 0.
1529- // When in accumulating mode, returns the number of fired elements to maintain a correct pending count.
1530- func (ss * stageState ) buildTriggeredBundle (em * ElementManager , key string , win typex.Window ) int {
1566+ func (ss * stageState ) buildTriggeredBundle (em * ElementManager , key string , win typex.Window ) ([]element , int ) {
15311567 var toProcess []element
15321568 dnt := ss .pendingByKeys [key ]
15331569 var notYet []element
15341570
1535- rb := RunBundle {StageID : ss .ID , BundleID : "agg-" + em .nextBundID (), Watermark : ss .input }
1536-
15371571 // Look at all elements for this key, and only for this window.
15381572 for dnt .elements .Len () > 0 {
15391573 e := heap .Pop (& dnt .elements ).(element )
@@ -1564,6 +1598,18 @@ func (ss *stageState) buildTriggeredBundle(em *ElementManager, key string, win t
15641598 heap .Init (& dnt .elements )
15651599 }
15661600
1601+ return toProcess , accumulationDiff
1602+ }
1603+
1604+ // startTriggeredBundle must be called with the stage.mu lock held.
1605+ // When in discarding mode, returns 0.
1606+ // When in accumulating mode, returns the number of fired elements to maintain a correct pending count.
1607+ func (ss * stageState ) startTriggeredBundle (em * ElementManager , key string , win typex.Window ) int {
1608+ toProcess , accumulationDiff := ss .buildTriggeredBundle (em , key , win )
1609+ if len (toProcess ) == 0 {
1610+ return accumulationDiff
1611+ }
1612+
15671613 if ss .inprogressKeys == nil {
15681614 ss .inprogressKeys = set [string ]{}
15691615 }
@@ -1575,6 +1621,7 @@ func (ss *stageState) buildTriggeredBundle(em *ElementManager, key string, win t
15751621 },
15761622 }
15771623
1624+ rb := RunBundle {StageID : ss .ID , BundleID : "agg-" + em .nextBundID (), Watermark : ss .input }
15781625 ss .makeInProgressBundle (
15791626 func () string { return rb .BundleID },
15801627 toProcess ,
@@ -1927,6 +1974,18 @@ func (ss *stageState) startProcessingTimeBundle(em *ElementManager, emNow mtime.
19271974 ss .mu .Lock ()
19281975 defer ss .mu .Unlock ()
19291976
1977+ toProcess , minTs , newKeys , holdsInBundle , stillSchedulable := ss .kind .buildProcessingTimeBundle (ss , em , emNow )
1978+
1979+ if len (toProcess ) == 0 {
1980+ // If we have nothing
1981+ return "" , false , stillSchedulable
1982+ }
1983+ bundID := ss .makeInProgressBundle (genBundID , toProcess , minTs , newKeys , holdsInBundle , nil )
1984+ slog .Debug ("started a processing time bundle" , "stageID" , ss .ID , "bundleID" , bundID , "size" , len (toProcess ), "emNow" , emNow )
1985+ return bundID , true , stillSchedulable
1986+ }
1987+
1988+ func (* statefulStageKind ) buildProcessingTimeBundle (ss * stageState , em * ElementManager , emNow mtime.Time ) (elementHeap , mtime.Time , set [string ], map [mtime.Time ]int , bool ) {
19301989 // TODO: Determine if it's possible and a good idea to treat all EventTime processing as a MinTime
19311990 // Special Case for ProcessingTime handling.
19321991 // Eg. Always queue EventTime elements at minTime.
@@ -1986,19 +2045,92 @@ func (ss *stageState) startProcessingTimeBundle(em *ElementManager, emNow mtime.
19862045 for _ , v := range notYet {
19872046 ss .processingTimeTimers .Persist (v .firing , v .timer , notYetHolds )
19882047 em .processTimeEvents .Schedule (v .firing , ss .ID )
2048+ em .wakeUpAt (v .firing )
19892049 }
19902050
19912051 // Add a refresh if there are still processing time events to process.
19922052 stillSchedulable := (nextTime < emNow && nextTime != mtime .MaxTimestamp || len (notYet ) > 0 )
19932053
1994- if len (toProcess ) == 0 {
1995- // If we have nothing
1996- return "" , false , stillSchedulable
2054+ return toProcess , minTs , newKeys , holdsInBundle , stillSchedulable
2055+ }
2056+
2057+ func (* ordinaryStageKind ) buildProcessingTimeBundle (ss * stageState , em * ElementManager , emNow mtime.Time ) (elementHeap , mtime.Time , set [string ], map [mtime.Time ]int , bool ) {
2058+ slog .Error ("ordinary stages can't have processing time elements" )
2059+ return nil , mtime .MinTimestamp , nil , nil , false
2060+ }
2061+
2062+ func (* aggregateStageKind ) buildProcessingTimeBundle (ss * stageState , em * ElementManager , emNow mtime.Time ) (elementHeap , mtime.Time , set [string ], map [mtime.Time ]int , bool ) {
2063+ var toProcess []element
2064+ minTs := mtime .MaxTimestamp
2065+ holdsInBundle := map [mtime.Time ]int {}
2066+
2067+ var notYet []fireElement
2068+
2069+ nextTime := ss .processingTimeTimers .Peek ()
2070+ keyCounts := map [string ]int {}
2071+ newKeys := set [string ]{}
2072+
2073+ for nextTime <= emNow {
2074+ elems := ss .processingTimeTimers .FireAt (nextTime )
2075+ for _ , e := range elems {
2076+ // Check if we're already executing this timer's key.
2077+ if ss .inprogressKeys .present (string (e .keyBytes )) {
2078+ notYet = append (notYet , fireElement {firing : nextTime , timer : e })
2079+ continue
2080+ }
2081+
2082+ // If we are set to have OneKeyPerBundle, and we already have a key for this bundle, we process it later.
2083+ if len (keyCounts ) > 0 && OneKeyPerBundle {
2084+ notYet = append (notYet , fireElement {firing : nextTime , timer : e })
2085+ continue
2086+ }
2087+ // If we are set to have OneElementPerKey, and we already have an element for this key we set this to process later.
2088+ if v := keyCounts [string (e .keyBytes )]; v > 0 && OneElementPerKey {
2089+ notYet = append (notYet , fireElement {firing : nextTime , timer : e })
2090+ continue
2091+ }
2092+ keyCounts [string (e .keyBytes )]++
2093+ newKeys .insert (string (e .keyBytes ))
2094+ if e .timestamp < minTs {
2095+ minTs = e .timestamp
2096+ }
2097+ // TODO: how to deal with watermark holds for this implicit processing time timer
2098+ // holdsInBundle[e.holdTimestamp]++
2099+
2100+ state := ss .state [LinkID {}][e .window ][string (e .keyBytes )]
2101+ endOfWindowReached := e .window .MaxTimestamp () < ss .input
2102+ ready := ss .strat .IsTriggerReady (triggerInput {
2103+ newElementCount : 0 ,
2104+ endOfWindowReached : endOfWindowReached ,
2105+ emNow : emNow ,
2106+ }, & state )
2107+
2108+ if ready {
2109+ // We're going to process this trigger!
2110+ elems , _ := ss .buildTriggeredBundle (em , string (e .keyBytes ), e .window )
2111+ toProcess = append (toProcess , elems ... )
2112+ }
2113+ }
2114+
2115+ nextTime = ss .processingTimeTimers .Peek ()
2116+ if nextTime == mtime .MaxTimestamp {
2117+ // Escape the loop if there are no more events.
2118+ break
2119+ }
19972120 }
1998- bundID := ss .makeInProgressBundle (genBundID , toProcess , minTs , newKeys , holdsInBundle , nil )
19992121
2000- slog .Debug ("started a processing time bundle" , "stageID" , ss .ID , "bundleID" , bundID , "size" , len (toProcess ), "emNow" , emNow )
2001- return bundID , true , stillSchedulable
2122+ // Reschedule unfired timers.
2123+ notYetHolds := map [mtime.Time ]int {}
2124+ for _ , v := range notYet {
2125+ ss .processingTimeTimers .Persist (v .firing , v .timer , notYetHolds )
2126+ em .processTimeEvents .Schedule (v .firing , ss .ID )
2127+ em .wakeUpAt (v .firing )
2128+ }
2129+
2130+ // Add a refresh if there are still processing time events to process.
2131+ stillSchedulable := (nextTime < emNow && nextTime != mtime .MaxTimestamp || len (notYet ) > 0 )
2132+
2133+ return toProcess , minTs , newKeys , holdsInBundle , stillSchedulable
20022134}
20032135
20042136// makeInProgressBundle is common code to store a set of elements as a bundle in progress.
@@ -2329,3 +2461,17 @@ func (em *ElementManager) ProcessingTimeNow() (ret mtime.Time) {
23292461func rebaseProcessingTime (localNow , scheduled mtime.Time ) mtime.Time {
23302462 return localNow + (scheduled - mtime .Now ())
23312463}
2464+
2465+ // wakeUpAt schedules a wakeup signal for the bundle processing loop.
2466+ // This is used for processing time timers to ensure the loop re-evaluates
2467+ // stages when a processing time timer is expected to fire.
2468+ func (em * ElementManager ) wakeUpAt (t mtime.Time ) {
2469+ if em .testStreamHandler == nil && em .config .EnableRTC {
2470+ // only create this goroutine if we have real-time clock enabled and the pipeline does not have TestStream.
2471+ go func (fireAt time.Time ) {
2472+ time .AfterFunc (time .Until (fireAt ), func () {
2473+ em .refreshCond .Broadcast ()
2474+ })
2475+ }(t .ToTime ())
2476+ }
2477+ }
0 commit comments