diff --git a/internal/bucket/lifecycle/lifecycle.go b/internal/bucket/lifecycle/lifecycle.go
index fdaabd962..788027a77 100644
--- a/internal/bucket/lifecycle/lifecycle.go
+++ b/internal/bucket/lifecycle/lifecycle.go
@@ -471,9 +471,11 @@ func (lc Lifecycle) eval(obj ObjectOpts, now time.Time) Event {
if len(events) > 0 {
sort.Slice(events, func(i, j int) bool {
- if events[i].Due.Equal(events[j].Due) {
- // Prefer Expiration over Transition for both current
- // and noncurrent versions
+ // Prefer Expiration over Transition for both current
+ // and noncurrent versions when,
+ // - now is past the expected time to action
+ // - expected time to action is the same for both actions
+ if now.After(events[i].Due) && now.After(events[j].Due) || events[i].Due.Equal(events[j].Due) {
switch events[i].Action {
case DeleteAction, DeleteVersionAction:
return true
diff --git a/internal/bucket/lifecycle/lifecycle_test.go b/internal/bucket/lifecycle/lifecycle_test.go
index 88d3c5b3a..0e8aaa79a 100644
--- a/internal/bucket/lifecycle/lifecycle_test.go
+++ b/internal/bucket/lifecycle/lifecycle_test.go
@@ -539,6 +539,46 @@ func TestEval(t *testing.T) {
objectModTime: time.Now().UTC().Add(-15 * 24 * time.Hour),
expectedAction: DeleteAction,
},
+ {
+ inputConfig: `
+
+ Rule 1
+ Enabled
+
+
+ WARM-1
+ 30
+
+
+ 60
+
+
+ `,
+ objectName: "obj-1",
+ objectModTime: time.Now().UTC().Add(-90 * 24 * time.Hour),
+ expectedAction: DeleteAction,
+ },
+ {
+ inputConfig: `
+
+ Rule 2
+
+ Enabled
+
+ 60
+
+
+ WARM-1
+ 30
+
+
+ `,
+ objectName: "obj-1",
+ isNoncurrent: true,
+ objectModTime: time.Now().UTC().Add(-90 * 24 * time.Hour),
+ objectSuccessorModTime: time.Now().UTC().Add(-90 * 24 * time.Hour),
+ expectedAction: DeleteVersionAction,
+ },
}
for _, tc := range testCases {