Skip to content

Commit f855616

Browse files
jariy17jariy17
andauthored
fix: floor monotonic timestamps to milliseconds before comparison (#573)
Comparing at microsecond precision missed collisions: two events in the same millisecond but different microseconds passed the tie check yet collide once the service floors both to the same millisecond (AgentCore Memory stores and orders eventTimestamp at ms resolution). Floor the desired timestamp to ms first so same-ms events are detected as a tie and separated by 1ms. Co-authored-by: jariy17 <tjariy+jariy17@users.noreply.github.com>
1 parent a271ab4 commit f855616

2 files changed

Lines changed: 40 additions & 2 deletions

File tree

src/bedrock_agentcore/memory/integrations/strands/session_manager.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,12 @@ def _get_monotonic_timestamp(self, desired_timestamp: Optional[datetime] = None)
113113
if desired_timestamp is None:
114114
desired_timestamp = datetime.now(timezone.utc)
115115

116+
# Floor to milliseconds — the resolution AgentCore Memory actually stores
117+
# and orders eventTimestamp at. Comparing at microsecond precision would
118+
# miss two events that fall in the same millisecond but different
119+
# microseconds: they pass the tie check here yet collide once stored.
120+
desired_timestamp = desired_timestamp.replace(microsecond=(desired_timestamp.microsecond // 1000) * 1000)
121+
116122
with self._timestamp_lock:
117123
if self._last_timestamp is not None and desired_timestamp <= self._last_timestamp:
118124
# Break the tie at millisecond granularity (the service's resolution).

tests/bedrock_agentcore/memory/integrations/strands/test_agentcore_memory_session_manager.py

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3870,11 +3870,19 @@ def test_later_timestamp_is_not_bumped(self, session_manager):
38703870
assert session_manager._get_monotonic_timestamp(later) == later
38713871

38723872
def test_none_desired_uses_current_time(self, session_manager):
3873-
"""Passing None falls back to current UTC time (preserved behavior)."""
3873+
"""Passing None falls back to current UTC time (preserved behavior).
3874+
3875+
The result is floored to ms, so it can sit up to 999us below ``before``;
3876+
compare against the ms-floored bounds.
3877+
"""
3878+
3879+
def floor_ms(dt):
3880+
return dt.replace(microsecond=(dt.microsecond // 1000) * 1000)
3881+
38743882
before = datetime.now(timezone.utc)
38753883
result = session_manager._get_monotonic_timestamp(None)
38763884
after = datetime.now(timezone.utc)
3877-
assert before <= result <= after
3885+
assert floor_ms(before) <= result <= floor_ms(after)
38783886

38793887
def test_within_process_burst_stays_ordered_at_ms_resolution(self, session_manager):
38803888
"""A burst of same-instant events gets strictly increasing 1ms-spaced
@@ -3887,3 +3895,27 @@ def test_within_process_burst_stays_ordered_at_ms_resolution(self, session_manag
38873895
assert len(set(stamps)) == len(stamps) # no ties (ambiguous ordering)
38883896
# Whole burst stays within a few ms of the requested time, not seconds.
38893897
assert stamps[-1] - base == timedelta(milliseconds=4)
3898+
3899+
def test_returned_timestamps_are_floored_to_milliseconds(self, session_manager):
3900+
"""Timestamps are floored to ms — the sub-millisecond microseconds the
3901+
service would discard are dropped before comparison/storage."""
3902+
ts = datetime(2024, 1, 1, 12, 0, 0, 567890, tzinfo=timezone.utc) # 567.890 ms
3903+
result = session_manager._get_monotonic_timestamp(ts)
3904+
assert result == ts.replace(microsecond=567000)
3905+
assert result.microsecond % 1000 == 0
3906+
3907+
def test_same_millisecond_different_microseconds_is_a_tie(self, session_manager):
3908+
"""Two events in the same ms but different microseconds must be treated
3909+
as a collision and separated by 1ms — otherwise they collide once the
3910+
service floors both to the same millisecond."""
3911+
first = datetime(2024, 1, 1, 12, 0, 0, 500, tzinfo=timezone.utc) # 0.500 ms
3912+
second = datetime(2024, 1, 1, 12, 0, 0, 900, tzinfo=timezone.utc) # 0.900 ms
3913+
3914+
r1 = session_manager._get_monotonic_timestamp(first)
3915+
r2 = session_manager._get_monotonic_timestamp(second)
3916+
3917+
# Both floored to .000; the second is bumped to .001 rather than passing
3918+
# through as a false non-tie.
3919+
assert r1 == datetime(2024, 1, 1, 12, 0, 0, 0, tzinfo=timezone.utc)
3920+
assert r2 == datetime(2024, 1, 1, 12, 0, 0, 1000, tzinfo=timezone.utc)
3921+
assert r2 > r1

0 commit comments

Comments
 (0)