Skip to content

Commit 1a726ae

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

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
@@ -423,13 +423,21 @@ func (d *StateStore) parseTTL(req *state.SetRequest) (*int64, error) {
423423
if err != nil {
424424
return nil, err
425425
}
426+
// Values <= 0 mean no TTL (never expires)
427+
if parsedVal <= 0 {
428+
return nil, nil
429+
}
426430
// DynamoDB expects an epoch timestamp in seconds.
427431
expirationTime := time.Now().Unix() + parsedVal
428432

429433
return &expirationTime, nil
430434
}
431435
// apply global TTL if no explicit TTL in request metadata
432436
if d.ttlInSeconds != nil {
437+
// Values <= 0 mean no TTL (never expires)
438+
if *d.ttlInSeconds <= 0 {
439+
return nil, nil
440+
}
433441
expirationTime := time.Now().Unix() + int64(*d.ttlInSeconds)
434442
return &expirationTime, nil
435443
}

state/aws/dynamodb/dynamodb_test.go

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

1290-
t.Run("Explicit TTL with value -1", func(t *testing.T) {
1290+
t.Run("Explicit TTL with value -1 means no expiration", func(t *testing.T) {
12911291
defaultTTL := 600
12921292
s := StateStore{
12931293
ttlAttributeName: "expiresAt",
@@ -1303,11 +1303,8 @@ func TestParseTTLWithDefault(t *testing.T) {
13031303

13041304
ttl, err := s.parseTTL(req)
13051305
require.NoError(t, err)
1306-
require.NotNil(t, ttl)
1307-
1308-
// -1 should result in immediate expiration (now + -1)
1309-
expectedTime := time.Now().Unix() - 1
1310-
assert.InDelta(t, expectedTime, *ttl, 2)
1306+
// -1 means never expire
1307+
assert.Nil(t, ttl)
13111308
})
13121309

13131310
t.Run("Default TTL with large value", func(t *testing.T) {
@@ -1350,7 +1347,7 @@ func TestParseTTLWithDefault(t *testing.T) {
13501347
assert.Contains(t, err.Error(), "invalid syntax")
13511348
})
13521349

1353-
t.Run("Explicit TTL overrides default in request with empty metadata", func(t *testing.T) {
1350+
t.Run("Explicit TTL with value 0 means no expiration", func(t *testing.T) {
13541351
defaultTTL := 1200
13551352
s := StateStore{
13561353
ttlAttributeName: "expiresAt",
@@ -1366,10 +1363,43 @@ func TestParseTTLWithDefault(t *testing.T) {
13661363

13671364
ttl, err := s.parseTTL(req)
13681365
require.NoError(t, err)
1369-
require.NotNil(t, ttl)
1370-
1371-
// Should use explicit value 0, not default
1372-
expectedTime := time.Now().Unix()
1373-
assert.InDelta(t, expectedTime, *ttl, 2)
1366+
// 0 means never expire, overriding default
1367+
assert.Nil(t, ttl)
1368+
})
1369+
1370+
t.Run("Default TTL with value 0 means no expiration", func(t *testing.T) {
1371+
defaultTTL := 0
1372+
s := StateStore{
1373+
ttlAttributeName: "expiresAt",
1374+
ttlInSeconds: &defaultTTL,
1375+
}
1376+
1377+
req := &state.SetRequest{
1378+
Key: "test-key",
1379+
Metadata: map[string]string{},
1380+
}
1381+
1382+
ttl, err := s.parseTTL(req)
1383+
require.NoError(t, err)
1384+
// Default of 0 means never expire
1385+
assert.Nil(t, ttl)
1386+
})
1387+
1388+
t.Run("Default TTL with negative value means no expiration", func(t *testing.T) {
1389+
defaultTTL := -1
1390+
s := StateStore{
1391+
ttlAttributeName: "expiresAt",
1392+
ttlInSeconds: &defaultTTL,
1393+
}
1394+
1395+
req := &state.SetRequest{
1396+
Key: "test-key",
1397+
Metadata: map[string]string{},
1398+
}
1399+
1400+
ttl, err := s.parseTTL(req)
1401+
require.NoError(t, err)
1402+
// Default of -1 means never expire
1403+
assert.Nil(t, ttl)
13741404
})
13751405
}

0 commit comments

Comments
 (0)