Skip to content

Commit b136dea

Browse files
committed
Global TTL of 0 or lower should never expire
Signed-off-by: Zeynel Koca <[email protected]>
1 parent c54b31c commit b136dea

File tree

2 files changed

+50
-12
lines changed

2 files changed

+50
-12
lines changed

state/aws/dynamodb/dynamodb.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,13 +429,21 @@ func (d *StateStore) parseTTL(req *state.SetRequest) (*int64, error) {
429429
if err != nil {
430430
return nil, err
431431
}
432+
// Values <= 0 mean no TTL (never expires)
433+
if parsedVal <= 0 {
434+
return nil, nil
435+
}
432436
// DynamoDB expects an epoch timestamp in seconds.
433437
expirationTime := time.Now().Unix() + parsedVal
434438

435439
return &expirationTime, nil
436440
}
437441
// apply global TTL if no explicit TTL in request metadata
438442
if d.ttlInSeconds != nil {
443+
// Values <= 0 mean no TTL (never expires)
444+
if *d.ttlInSeconds <= 0 {
445+
return nil, nil
446+
}
439447
expirationTime := time.Now().Unix() + int64(*d.ttlInSeconds)
440448
return &expirationTime, nil
441449
}

state/aws/dynamodb/dynamodb_test.go

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,7 +1089,7 @@ func TestParseTTLWithDefault(t *testing.T) {
10891089
assert.Nil(t, ttl) // Should return nil when TTL not enabled
10901090
})
10911091

1092-
t.Run("Explicit TTL with value -1", func(t *testing.T) {
1092+
t.Run("Explicit TTL with value -1 means no expiration", func(t *testing.T) {
10931093
defaultTTL := 600
10941094
s := StateStore{
10951095
ttlAttributeName: "expiresAt",
@@ -1105,11 +1105,8 @@ func TestParseTTLWithDefault(t *testing.T) {
11051105

11061106
ttl, err := s.parseTTL(req)
11071107
require.NoError(t, err)
1108-
require.NotNil(t, ttl)
1109-
1110-
// -1 should result in immediate expiration (now + -1)
1111-
expectedTime := time.Now().Unix() - 1
1112-
assert.InDelta(t, expectedTime, *ttl, 2)
1108+
// -1 means never expire
1109+
assert.Nil(t, ttl)
11131110
})
11141111

11151112
t.Run("Default TTL with large value", func(t *testing.T) {
@@ -1152,7 +1149,7 @@ func TestParseTTLWithDefault(t *testing.T) {
11521149
assert.Contains(t, err.Error(), "invalid syntax")
11531150
})
11541151

1155-
t.Run("Explicit TTL overrides default in request with empty metadata", func(t *testing.T) {
1152+
t.Run("Explicit TTL with value 0 means no expiration", func(t *testing.T) {
11561153
defaultTTL := 1200
11571154
s := StateStore{
11581155
ttlAttributeName: "expiresAt",
@@ -1168,10 +1165,43 @@ func TestParseTTLWithDefault(t *testing.T) {
11681165

11691166
ttl, err := s.parseTTL(req)
11701167
require.NoError(t, err)
1171-
require.NotNil(t, ttl)
1172-
1173-
// Should use explicit value 0, not default
1174-
expectedTime := time.Now().Unix()
1175-
assert.InDelta(t, expectedTime, *ttl, 2)
1168+
// 0 means never expire, overriding default
1169+
assert.Nil(t, ttl)
1170+
})
1171+
1172+
t.Run("Default TTL with value 0 means no expiration", func(t *testing.T) {
1173+
defaultTTL := 0
1174+
s := StateStore{
1175+
ttlAttributeName: "expiresAt",
1176+
ttlInSeconds: &defaultTTL,
1177+
}
1178+
1179+
req := &state.SetRequest{
1180+
Key: "test-key",
1181+
Metadata: map[string]string{},
1182+
}
1183+
1184+
ttl, err := s.parseTTL(req)
1185+
require.NoError(t, err)
1186+
// Default of 0 means never expire
1187+
assert.Nil(t, ttl)
1188+
})
1189+
1190+
t.Run("Default TTL with negative value means no expiration", func(t *testing.T) {
1191+
defaultTTL := -1
1192+
s := StateStore{
1193+
ttlAttributeName: "expiresAt",
1194+
ttlInSeconds: &defaultTTL,
1195+
}
1196+
1197+
req := &state.SetRequest{
1198+
Key: "test-key",
1199+
Metadata: map[string]string{},
1200+
}
1201+
1202+
ttl, err := s.parseTTL(req)
1203+
require.NoError(t, err)
1204+
// Default of -1 means never expire
1205+
assert.Nil(t, ttl)
11761206
})
11771207
}

0 commit comments

Comments
 (0)