@@ -3390,4 +3390,80 @@ func TestGarbageCollect(t *testing.T) {
33903390 require .NoError (t , err , "failed to list workflows after final GC" )
33913391 require .Equal (t , 0 , len (workflows ), "expected exactly 0 workflows after final GC" )
33923392 })
3393+
3394+ t .Run ("ThresholdAndCutoffTimestampInteraction" , func (t * testing.T ) {
3395+ // Reset database for clean test environment
3396+ resetTestDatabase (t , databaseURL )
3397+ dbosCtx := setupDBOS (t , false , true )
3398+
3399+ // Register the test workflow
3400+ RegisterWorkflow (dbosCtx , gcTestWorkflow )
3401+
3402+ // This test verifies that when both threshold and cutoff timestamp are provided,
3403+ // the more stringent (restrictive) one applies - i.e., the one that keeps more workflows
3404+
3405+ // Create 10 workflows with different timestamps
3406+ numWorkflows := 10
3407+ handles := make ([]WorkflowHandle [int ], numWorkflows )
3408+
3409+ for i := range numWorkflows {
3410+ handle , err := RunAsWorkflow (dbosCtx , gcTestWorkflow , i )
3411+ require .NoError (t , err , "failed to start workflow %d" , i )
3412+ handles [i ] = handle
3413+
3414+ // Add small delay to ensure distinct timestamps
3415+ time .Sleep (10 * time .Millisecond )
3416+ }
3417+
3418+ // Wait for all workflows to complete
3419+ for i , handle := range handles {
3420+ result , err := handle .GetResult ()
3421+ require .NoError (t , err , "failed to get result from workflow %d" , i )
3422+ require .Equal (t , i , result )
3423+ }
3424+
3425+ // Get timestamps for testing
3426+ workflows , err := ListWorkflows (dbosCtx , WithSortDesc (true ))
3427+ require .NoError (t , err , "failed to list workflows" )
3428+ require .Equal (t , numWorkflows , len (workflows ))
3429+
3430+ // Workflows are ordered newest first in ListWorkflows
3431+ var cutoff1 int64 // Will keep 5 newest when used as cutoff
3432+ var cutoff2 int64 // Will keep 8 newest when used as cutoff
3433+
3434+ cutoff1 = workflows [7 ].CreatedAt .UnixMilli () // 3rd oldest workflow
3435+ cutoff2 = workflows [1 ].CreatedAt .UnixMilli () // 9th oldest workflow
3436+
3437+ // Case 1: Threshold is more restrictive (higher/more recent cutoff)
3438+ // Threshold would keep 6 newest, timestamp would keep 8 newest
3439+ // Result: threshold wins (higher timestamp), only 6 workflows remain
3440+ threshold := 6
3441+ err = dbosCtx .(* dbosContext ).systemDB .garbageCollectWorkflows (dbosCtx , garbageCollectWorkflowsInput {
3442+ rowsThreshold : & threshold ,
3443+ cutoffEpochTimestampMs : & cutoff1 ,
3444+ })
3445+ require .NoError (t , err , "failed to garbage collect with threshold 6 and 7th newest timestamp" )
3446+
3447+ workflows , err = ListWorkflows (dbosCtx , WithSortDesc (true ))
3448+ require .NoError (t , err , "failed to list workflows after first GC" )
3449+ require .Equal (t , threshold , len (workflows ), "expected 6 workflows when threshold has more recent cutoff than timestamp" )
3450+
3451+ for i := 0 ; i < len (workflows )- threshold ; i ++ {
3452+ require .Equal (t , workflows [i ].ID , handles [i ].GetWorkflowID (), "expected workflow %d to remain" , i )
3453+ }
3454+
3455+ // Case2: Threshold is less restrictive (lower cutoff)
3456+ threshold = 3
3457+ err = dbosCtx .(* dbosContext ).systemDB .garbageCollectWorkflows (dbosCtx , garbageCollectWorkflowsInput {
3458+ rowsThreshold : & threshold ,
3459+ cutoffEpochTimestampMs : & cutoff2 ,
3460+ })
3461+ require .NoError (t , err , "failed to garbage collect with threshold 3 and 2nd newest timestamp" )
3462+
3463+ workflows , err = ListWorkflows (dbosCtx , WithSortDesc (true ))
3464+ require .NoError (t , err , "failed to list workflows after second GC" )
3465+ require .Equal (t , 2 , len (workflows ), "expected 2 workflows after second GC" )
3466+ require .Equal (t , workflows [0 ].ID , handles [numWorkflows - 1 ].GetWorkflowID (), "expected newest workflow to remain" )
3467+ require .Equal (t , workflows [1 ].ID , handles [numWorkflows - 2 ].GetWorkflowID (), "expected 2nd newest workflow to remain" )
3468+ })
33933469}
0 commit comments