@@ -274,6 +274,12 @@ func ProcessTableConfigurations(tfmContext FlowMachineContext, tfContext FlowCon
274274}
275275
276276func ProcessFlowStatesForInterval (tfContext FlowContext , tfmContext FlowMachineContext , flowDefinitionContext * FlowLibraryContext , regionList []string ) int {
277+ // Acquire the pullOnceMu so that synchronous ExecuteFilteredPullOnce calls block
278+ // all async push/pull activity for this flow until they complete.
279+ mu := tfContext .GetPullOnceMu ()
280+ mu .Lock ()
281+ defer mu .Unlock ()
282+
277283 if tfContext .GetFlowStateState () == 3 {
278284 tfContext .SetRestart (false )
279285 tfmContext .SetPermissionUpdate (tfContext )
@@ -468,3 +474,66 @@ func ProcessFlowStatesForInterval(tfContext FlowContext, tfmContext FlowMachineC
468474 }
469475 return 0
470476}
477+
478+ // ExecuteFilteredPullOnce synchronously performs a pullonce for a flow filtered by
479+ // the given syncFilter value. It acquires the flow's pullOnceMu before setting state,
480+ // ensuring that no async push/pull activity races with the synchronous operation.
481+ func ExecuteFilteredPullOnce (tfmContext FlowMachineContext , tfContext FlowContext , syncFilter string ) {
482+ flowDefinitionContext := tfContext .GetFlowLibraryContext ()
483+ if flowDefinitionContext == nil {
484+ return
485+ }
486+
487+ // Acquire the mutex before mutating state so the async path cannot race with us.
488+ mu := tfContext .GetPullOnceMu ()
489+ mu .Lock ()
490+ defer mu .Unlock ()
491+
492+ tfContext .SetFlowSyncFilter (syncFilter )
493+ tfContext .SetFlowSyncMode ("pullonce" )
494+
495+ var tableIndexKey string
496+ if flowDefinitionContext .GetTableIndexColumnNames != nil {
497+ if keys := flowDefinitionContext .GetTableIndexColumnNames (); len (keys ) == 1 {
498+ tableIndexKey = keys [0 ]
499+ }
500+ }
501+ if len (tableIndexKey ) == 0 {
502+ tfmContext .Log ("ExecuteFilteredPullOnce: missing GetTableIndexColumnNames" , nil )
503+ return
504+ }
505+
506+ tableConfigurations , err := tableConfigurationFlowPullRemote (tfmContext , tfContext )
507+ if err != nil {
508+ tfmContext .Log ("ExecuteFilteredPullOnce: error pulling configurations" , err )
509+ return
510+ }
511+
512+ var filtered []map [string ]any
513+ for _ , filter := range tfContext .GetFlowSyncFilters () {
514+ for _ , table := range tableConfigurations {
515+ if filter == table [tableIndexKey ].(string ) {
516+ filtered = append (filtered , table )
517+ }
518+ }
519+ }
520+ tableConfigurations = filtered
521+
522+ for _ , table := range tableConfigurations {
523+ rows , _ := tfmContext .CallDBQuery (tfContext , flowDefinitionContext .GetTableConfigurationById (tfContext .GetFlowHeader ().SourceAlias , tfContext .GetFlowHeader ().FlowName (), table [tableIndexKey ].(string )), nil , false , "SELECT" , nil , "" )
524+ if len (rows ) == 0 {
525+ tfmContext .CallDBQuery (tfContext , flowDefinitionContext .GetTableConfigurationInsert (table , tfContext .GetFlowHeader ().SourceAlias , tfContext .GetFlowHeader ().FlowName ()), nil , true , "INSERT" , []FlowNameType {tfContext .GetFlowHeader ().FlowNameType ()}, "" )
526+ } else {
527+ for _ , value := range rows {
528+ if CompareRows (table , flowDefinitionContext .GetTableMapFromArray (value )) {
529+ continue
530+ }
531+ tfmContext .CallDBQuery (tfContext , flowDefinitionContext .GetTableConfigurationUpdate (table , tfContext .GetFlowHeader ().SourceAlias , tfContext .GetFlowHeader ().FlowName ()), nil , true , "UPDATE" , []FlowNameType {tfContext .GetFlowHeader ().FlowNameType ()}, "" )
532+ }
533+ }
534+ }
535+
536+ tfContext .SetFlowSyncFilter ("" )
537+ tfContext .SetFlowSyncMode ("pullcomplete" )
538+ tfContext .PushState ("flowStateReceiver" , tfContext .NewFlowStateUpdate ("2" , "pullcomplete" ))
539+ }
0 commit comments