Skip to content

Commit 2346dea

Browse files
committed
fix: Keep checkpoint order for unchanged state values
1 parent 70df295 commit 2346dea

2 files changed

Lines changed: 35 additions & 4 deletions

File tree

‎cloudquery/sdk/stateclient/stateclient.py‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,9 @@ def set_key(self, key: str, value: str) -> None:
9191
if self.checkpoints and self.checkpoints.superseded(scope):
9292
return
9393
with self._lock:
94-
if self.mem.get(key) == value:
95-
# a pending change of the key summarizes this caller's resources too
96-
if key in self.changes:
97-
self.changes[key].add(scope)
94+
# with checkpoints, the value also covers the caller's resources, so later checkpoints
95+
# of the key must still follow them even if it is unchanged
96+
if self.checkpoints is None and self.mem.get(key) == value:
9897
return
9998
self.mem[key] = value
10099
self.changes.setdefault(key, set()).add(scope)

‎tests/scheduler/checkpoints.py‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import contextvars
2+
import queue
23
import random
34
import threading
45
import time
@@ -249,6 +250,37 @@ def test_setting_an_unchanged_value_adds_the_caller_to_the_pending_change():
249250
assert state.changes["a"] == {None, scope}
250251

251252

253+
def test_unchanged_values_order_later_checkpoints_after_the_callers_resources():
254+
scheduler = Scheduler(1)
255+
checkpoints = scheduler.checkpoints
256+
res = queue.Queue()
257+
checkpoints.start(res)
258+
state = MemoryStateClient(checkpoints=checkpoints)
259+
first, unfinished, last = (checkpoints.new_scope(None) for _ in range(3))
260+
child = checkpoints.new_scope(unfinished)
261+
262+
def flush_in(scope, value):
263+
with checkpoints.entered(scope):
264+
state.set_key("cursor", value)
265+
state.flush()
266+
267+
def sent():
268+
return [checkpoint_rows(res.get_nowait())["cursor"] for _ in range(res.qsize())]
269+
270+
try:
271+
flush_in(first, "1")
272+
assert sent() == ["1"]
273+
# the same value, but it also covers the resources of a resolver whose relations still run
274+
flush_in(unfinished, "1")
275+
flush_in(last, "2")
276+
assert sent() == [], "a later value must not overtake the relations it follows"
277+
checkpoints.resolver_finished(child)
278+
assert sent() == ["1", "2"]
279+
finally:
280+
checkpoints.stop()
281+
scheduler.shutdown()
282+
283+
252284
def test_failed_flush_keeps_the_changes():
253285
state = MemoryStateClient(checkpoints=None, fail_writes=True)
254286
state.set_key("a", "1")

0 commit comments

Comments
 (0)