mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-13 08:02:47 +00:00
fix: keep the row baseline a read that started earlier would rewind
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
899899b284
commit
b33f70e77f
@@ -319,6 +319,9 @@ class Entry<V> {
|
||||
const editsAtStart = editedBefore ?? this.edits
|
||||
return this.run(async () => {
|
||||
const key = this.key
|
||||
// Whether anything could have written the row while this read was in flight: only an
|
||||
// item already on screen has a row this entry is keeping.
|
||||
const held = this.loaded
|
||||
let res: ItemLoad<V>
|
||||
try {
|
||||
if (this.retired) return superseded
|
||||
@@ -344,8 +347,13 @@ class Entry<V> {
|
||||
}
|
||||
this.deployed = this.withPatched(snapshot(res.deployed))
|
||||
this.origin = res.deployed !== undefined ? 'deployed' : 'draft'
|
||||
this.row = serialize(res.draft) ?? null
|
||||
this.ports.seedSync(key, res.draftSavedAt)
|
||||
// A row written while this read was in flight is newer than the one it read, and the
|
||||
// syncer has already sent it: reseeding from this response would rewind `last_sync`
|
||||
// past that, and every write after it is refused as a conflict.
|
||||
if (!(held && keepValue)) {
|
||||
this.row = serialize(res.draft) ?? null
|
||||
this.ports.seedSync(key, res.draftSavedAt)
|
||||
}
|
||||
this.loaded = true
|
||||
if (!keepValue) {
|
||||
this.pristine = this.settles && this.origin === 'deployed' && res.draft === undefined
|
||||
|
||||
@@ -32,6 +32,8 @@ function fakeRows() {
|
||||
const hints = new Map<string, boolean>()
|
||||
/** Paths whose parked payload was dropped. */
|
||||
const dropped: string[] = []
|
||||
/** Every `last_sync` baseline the syncer was given, in order. */
|
||||
const seeds: { path: string; at: string | undefined }[] = []
|
||||
/** What a write leaves queued, and what a flush sends: the syncer debounces. */
|
||||
const queued = new Map<string, unknown>()
|
||||
const sent = new Map<string, unknown>()
|
||||
@@ -55,7 +57,7 @@ function fakeRows() {
|
||||
conflicts.delete(key.path)
|
||||
writes.push({ path: key.path, value })
|
||||
},
|
||||
seedSync: () => {},
|
||||
seedSync: (key, at) => void seeds.push({ path: key.path, at }),
|
||||
conflicted: (key) => conflicts.has(key.path),
|
||||
failure: (key) => failures.get(key.path),
|
||||
dropPending: (key) => void dropped.push(key.path),
|
||||
@@ -68,6 +70,7 @@ function fakeRows() {
|
||||
failing,
|
||||
hints,
|
||||
dropped,
|
||||
seeds,
|
||||
sent,
|
||||
hold: (until: Promise<void>) => void (holdFlush = until)
|
||||
}
|
||||
@@ -458,6 +461,39 @@ describe('item store: outside writes', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('does not rewind the row baseline to what a read that started earlier saw', async () => {
|
||||
const rows = fakeRows()
|
||||
const read = deferred<ItemLoad<Res>>()
|
||||
let reads = 0
|
||||
const { item } = await open(
|
||||
rows,
|
||||
adapter(() => {
|
||||
reads++
|
||||
return reads > 1
|
||||
? read.promise
|
||||
: Promise.resolve({ deployed: deployedRes, draftSavedAt: 'T1' })
|
||||
})
|
||||
)
|
||||
expect(rows.seeds).toEqual([{ path: 'u/me/r', at: 'T1' }])
|
||||
|
||||
const reloading = item.reload()
|
||||
await settle()
|
||||
// The chat writes while the read is out; its row goes, and the syncer moves past T1.
|
||||
item.applyExternal({ ...deployedRes, description: 'from the chat' })
|
||||
read.resolve({ deployed: deployedRes, draft: deployedRes, draftSavedAt: 'T1' })
|
||||
await reloading
|
||||
|
||||
expect(item.value?.description).toBe('from the chat')
|
||||
// Reseeding T1 here would put the syncer behind the row it has already sent, and the
|
||||
// server refuses everything after that as a conflict.
|
||||
expect(rows.seeds).toEqual([{ path: 'u/me/r', at: 'T1' }])
|
||||
expect(item.status).not.toBe('conflicted')
|
||||
expect(rows.writes.at(-1)).toEqual({
|
||||
path: 'u/me/r',
|
||||
value: { ...deployedRes, description: 'from the chat' }
|
||||
})
|
||||
})
|
||||
|
||||
it('applies outside writes in order, each one a new revision and never settling', async () => {
|
||||
const rows = fakeRows()
|
||||
const { item } = await open(rows, adapter({ deployed: deployedRes }), 'u/me/r', {})
|
||||
|
||||
Reference in New Issue
Block a user