Note
Crash issues will be ignored and closed within a week if no logs are provided.
This is not a crash. It is a key-expiration logic bug. Crash logs do not apply.
The language of MMKV
Objective-C / Swift (also reproducible in C++ Core; affects all platforms that use key expiration)
The version of MMKV
v2.4.0 (latest on master as of May 2026). The bug is in Core expire-timestamp logic and likely affects older versions that support enableAutoKeyExpire as well.
Note: For versions older than the latest, please upgrade before posting any issue. We don't have much time for old-version tech support.
The platform of MMKV
iOS (also applicable to Android, macOS, POSIX, etc., since the bug is in Core/MMKV.cpp)
The installation of MMKV
Git clone / CocoaPods (integrated into the app via CocoaPods)
What's the issue?
When enableAutoKeyExpire is enabled, writing a key with a very long expireDuration (e.g. 100 years in seconds) causes the value to disappear immediately on read — as if the key never existed or was already expired.
Root cause: Expire metadata stores an absolute Unix timestamp (now + expireDuration) as uint32_t. When now + expireDuration exceeds UINT32_MAX, the value wraps to a timestamp far in the past (~1990). On read, MMKV treats it as expired and removes the key.
Reproduction (Objective-C):
MMKV *mmkv = [MMKV mmkvWithID:@"test_expire"];
[mmkv enableAutoKeyExpire:MMKVExpireNever]; // per-key expire only
uint32_t hundredYears = 100u * 365u * 24u * 3600u;
[mmkv setInt32:42 forKey:@"key" expireDuration:hundredYears];
int32_t value = [mmkv getInt32ForKey:@"key"];
// Expected: 42
// Actual: 0 (default); key is deleted as "expired"
Expected behavior: An excessively long expiration should behave like permanent storage (MMKVExpireNever / 0), not instant expiration.
Proposed fix: Before computing now + expireDuration, detect overflow (expireDuration > UINT32_MAX - now) and store ExpireNever (0) instead.
Approximate safe limit today: ~80 years before overflow; 100 years reliably fails in 2026.
Rationale: why overflow should mean “never expire”
expireDuration means “expire after N seconds from now.” Callers who pass a very large value (e.g. 100 years) usually intend long retention, not immediate invalidation.
| Behavior |
Effect |
Matches caller intent? |
| Overflow → wrapped past timestamp → treat as expired |
Key gone on next read |
No — opposite of “long TTL” |
Overflow → store ExpireNever (permanent) |
Key readable as normal |
Yes — closer to “as long as possible” |
When now + duration cannot be represented in uint32_t, treating the key as permanent is more reasonable than treating it as already expired: it avoids silent data loss and aligns with typical use of huge durations as “effectively never expire.”
What's the log of MMKV when that happened?
Set MMKV logging to MMKVLogInfo or higher. When reading the key back, typical logs look like:
[I] <MMKV_IO.cpp:2007::getDataWithoutMTimeForKey> deleting expired key [key] in mmkv [test_expire], due date 638059694
The due date is a wrapped timestamp (~year 1990), much smaller than current now (~1.78e9), which confirms overflow rather than a real past expiry.
Write path (for reference): Core/MMKV.cpp uses getCurrentTimeInSecond() + expireDuration without an overflow check; deletion on read is in getDataWithoutMTimeForKey in Core/MMKV_IO.cpp.
Note
Crash issues will be ignored and closed within a week if no logs are provided.
The language of MMKV
Objective-C / Swift (also reproducible in C++ Core; affects all platforms that use key expiration)
The version of MMKV
v2.4.0 (latest on
masteras of May 2026). The bug is in Core expire-timestamp logic and likely affects older versions that supportenableAutoKeyExpireas well.The platform of MMKV
iOS (also applicable to Android, macOS, POSIX, etc., since the bug is in
Core/MMKV.cpp)The installation of MMKV
Git clone / CocoaPods (integrated into the app via CocoaPods)
What's the issue?
When
enableAutoKeyExpireis enabled, writing a key with a very longexpireDuration(e.g. 100 years in seconds) causes the value to disappear immediately on read — as if the key never existed or was already expired.Root cause: Expire metadata stores an absolute Unix timestamp (
now + expireDuration) asuint32_t. Whennow + expireDurationexceedsUINT32_MAX, the value wraps to a timestamp far in the past (~1990). On read, MMKV treats it as expired and removes the key.Reproduction (Objective-C):
Expected behavior: An excessively long expiration should behave like permanent storage (
MMKVExpireNever/0), not instant expiration.Proposed fix: Before computing
now + expireDuration, detect overflow (expireDuration > UINT32_MAX - now) and storeExpireNever(0) instead.Approximate safe limit today: ~80 years before overflow; 100 years reliably fails in 2026.
Rationale: why overflow should mean “never expire”
expireDurationmeans “expire after N seconds from now.” Callers who pass a very large value (e.g. 100 years) usually intend long retention, not immediate invalidation.ExpireNever(permanent)When
now + durationcannot be represented inuint32_t, treating the key as permanent is more reasonable than treating it as already expired: it avoids silent data loss and aligns with typical use of huge durations as “effectively never expire.”What's the log of MMKV when that happened?
Set MMKV logging to
MMKVLogInfoor higher. When reading the key back, typical logs look like:The
due dateis a wrapped timestamp (~year 1990), much smaller than currentnow(~1.78e9), which confirms overflow rather than a real past expiry.Write path (for reference):
Core/MMKV.cppusesgetCurrentTimeInSecond() + expireDurationwithout an overflow check; deletion on read is ingetDataWithoutMTimeForKeyinCore/MMKV_IO.cpp.