More robust updates
This commit is contained in:
parent
af4e90357f
commit
9cc210140e
@ -91,22 +91,7 @@ def treeinspos(rootmod: list[FileEntry], relpath: PurePosixPath, relfile: int):
|
|||||||
isfile = 0
|
isfile = 0
|
||||||
level = 0
|
level = 0
|
||||||
i = 0
|
i = 0
|
||||||
iteration_count = 0
|
|
||||||
|
|
||||||
for i, rel, entry in treeiter(rootmod):
|
for i, rel, entry in treeiter(rootmod):
|
||||||
iteration_count += 1
|
|
||||||
|
|
||||||
# Detect potential infinite loops in treeinspos
|
|
||||||
if iteration_count % 1000 == 0:
|
|
||||||
logger.debug(
|
|
||||||
f"DEBUG: treeinspos iteration {iteration_count}, i={i}, rel={rel}, entry.name={entry.name}, level={level}, entry.level={entry.level}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if iteration_count > 10000: # Emergency brake for infinite loops
|
|
||||||
logger.error(
|
|
||||||
f"ERROR: treeinspos potential infinite loop! iteration={iteration_count}, relpath={relpath}, i={i}, level={level}"
|
|
||||||
)
|
|
||||||
break
|
|
||||||
|
|
||||||
if entry.level > level:
|
if entry.level > level:
|
||||||
# We haven't found item at level, skip subdirectories
|
# We haven't found item at level, skip subdirectories
|
||||||
@ -152,7 +137,7 @@ def treeinspos(rootmod: list[FileEntry], relpath: PurePosixPath, relfile: int):
|
|||||||
logger.debug(f"DEBUG: treeinspos RETURN: cmp > 0, returning i={i}")
|
logger.debug(f"DEBUG: treeinspos RETURN: cmp > 0, returning i={i}")
|
||||||
return i
|
return i
|
||||||
if cmp < 0:
|
if cmp < 0:
|
||||||
logger.debug(f"DEBUG: treeinspos CONTINUE: cmp < 0")
|
logger.debug("DEBUG: treeinspos CONTINUE: cmp < 0")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
logger.debug(f"DEBUG: treeinspos INCREMENT_LEVEL: level {level} -> {level + 1}")
|
logger.debug(f"DEBUG: treeinspos INCREMENT_LEVEL: level {level} -> {level + 1}")
|
||||||
@ -166,9 +151,7 @@ def treeinspos(rootmod: list[FileEntry], relpath: PurePosixPath, relfile: int):
|
|||||||
logger.debug(f"DEBUG: treeinspos FOR_ELSE: incrementing i from {i} to {i + 1}")
|
logger.debug(f"DEBUG: treeinspos FOR_ELSE: incrementing i from {i} to {i + 1}")
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(f"DEBUG: treeinspos EXIT: returning i={i}")
|
||||||
f"DEBUG: treeinspos EXIT: returning i={i}, iterations={iteration_count}"
|
|
||||||
)
|
|
||||||
return i
|
return i
|
||||||
|
|
||||||
|
|
||||||
@ -336,6 +319,10 @@ def format_update(old, new):
|
|||||||
update = []
|
update = []
|
||||||
keep_count = 0
|
keep_count = 0
|
||||||
iteration_count = 0
|
iteration_count = 0
|
||||||
|
# Precompute index maps to allow deterministic tie-breaking when both
|
||||||
|
# candidates exist in both sequences but are not equal (rename/move cases)
|
||||||
|
old_pos = {e: i for i, e in enumerate(old)}
|
||||||
|
new_pos = {e: i for i, e in enumerate(new)}
|
||||||
|
|
||||||
while oidx < len(old) and nidx < len(new):
|
while oidx < len(old) and nidx < len(new):
|
||||||
iteration_count += 1
|
iteration_count += 1
|
||||||
@ -411,18 +398,38 @@ def format_update(old, new):
|
|||||||
update.append(UpdIns(insert_items))
|
update.append(UpdIns(insert_items))
|
||||||
|
|
||||||
if not modified:
|
if not modified:
|
||||||
logger.error(
|
# Tie-break: both items exist in both lists but don't match here.
|
||||||
f"ERROR: format_update INFINITE_LOOP: nidx={nidx}, oidx={oidx}, old_len={len(old)}, new_len={len(new)}"
|
# Decide whether to delete old[oidx] first or insert new[nidx] first
|
||||||
)
|
# based on which alignment is closer.
|
||||||
logger.error(
|
if oidx >= len(old) or nidx >= len(new):
|
||||||
f"ERROR: old[oidx]={old[oidx].name if oidx < len(old) else 'OUT_OF_BOUNDS'}"
|
break
|
||||||
)
|
cur_old = old[oidx]
|
||||||
logger.error(
|
cur_new = new[nidx]
|
||||||
f"ERROR: new[nidx]={new[nidx].name if nidx < len(new) else 'OUT_OF_BOUNDS'}"
|
|
||||||
)
|
pos_old_in_new = new_pos.get(cur_old)
|
||||||
raise Exception(
|
pos_new_in_old = old_pos.get(cur_new)
|
||||||
f"Infinite loop in diff {nidx=} {oidx=} {len(old)=} {len(new)=}"
|
|
||||||
)
|
# Default distances if not present (shouldn't happen if in remain sets)
|
||||||
|
dist_del = (pos_old_in_new - nidx) if pos_old_in_new is not None else 1
|
||||||
|
dist_ins = (pos_new_in_old - oidx) if pos_new_in_old is not None else 1
|
||||||
|
|
||||||
|
# Prefer the operation with smaller forward distance; tie => delete
|
||||||
|
if dist_del <= dist_ins:
|
||||||
|
# Delete current old item
|
||||||
|
oremain.discard(cur_old)
|
||||||
|
update.append(UpdDel(1))
|
||||||
|
oidx += 1
|
||||||
|
logger.debug(
|
||||||
|
f"DEBUG: format_update TIEBREAK_DEL: oidx->{oidx}, cur_old={cur_old.name}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Insert current new item
|
||||||
|
nremain.discard(cur_new)
|
||||||
|
update.append(UpdIns([cur_new]))
|
||||||
|
nidx += 1
|
||||||
|
logger.debug(
|
||||||
|
f"DEBUG: format_update TIEBREAK_INS: nidx->{nidx}, cur_new={cur_new.name}"
|
||||||
|
)
|
||||||
|
|
||||||
# Diff any remaining
|
# Diff any remaining
|
||||||
if keep_count > 0:
|
if keep_count > 0:
|
||||||
@ -547,18 +554,43 @@ def watcher_inotify(loop):
|
|||||||
)
|
)
|
||||||
t0 = time.perf_counter()
|
t0 = time.perf_counter()
|
||||||
logger.debug("DEBUG: inotify CALLING format_update")
|
logger.debug("DEBUG: inotify CALLING format_update")
|
||||||
update = format_update(state.root, rootmod)
|
try:
|
||||||
logger.debug("DEBUG: inotify format_update COMPLETED")
|
update = format_update(state.root, rootmod)
|
||||||
t1 = time.perf_counter()
|
logger.debug("DEBUG: inotify format_update COMPLETED")
|
||||||
with state.lock:
|
t1 = time.perf_counter()
|
||||||
logger.debug("DEBUG: inotify BROADCASTING update")
|
with state.lock:
|
||||||
broadcast(update, loop)
|
logger.debug("DEBUG: inotify BROADCASTING update")
|
||||||
state.root = rootmod
|
broadcast(update, loop)
|
||||||
logger.debug("DEBUG: inotify BROADCAST completed, state updated")
|
state.root = rootmod
|
||||||
t2 = time.perf_counter()
|
logger.debug("DEBUG: inotify BROADCAST completed, state updated")
|
||||||
logger.debug(
|
t2 = time.perf_counter()
|
||||||
f"Format update took {t1 - t0:.1f}s, broadcast {t2 - t1:.1f}s"
|
logger.debug(
|
||||||
)
|
f"Format update took {t1 - t0:.1f}s, broadcast {t2 - t1:.1f}s"
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
logger.exception(
|
||||||
|
"format_update failed; falling back to full rescan"
|
||||||
|
)
|
||||||
|
# Fallback: full rescan and try diff again; last resort send full root
|
||||||
|
try:
|
||||||
|
fresh = walk(PurePosixPath())
|
||||||
|
try:
|
||||||
|
update = format_update(state.root, fresh)
|
||||||
|
with state.lock:
|
||||||
|
broadcast(update, loop)
|
||||||
|
state.root = fresh
|
||||||
|
logger.debug("Fallback diff succeeded after full rescan")
|
||||||
|
except Exception:
|
||||||
|
logger.exception(
|
||||||
|
"Fallback diff failed; sending full root snapshot"
|
||||||
|
)
|
||||||
|
with state.lock:
|
||||||
|
broadcast(format_root(fresh), loop)
|
||||||
|
state.root = fresh
|
||||||
|
except Exception:
|
||||||
|
logger.exception(
|
||||||
|
"Full rescan failed; dropping this batch of updates"
|
||||||
|
)
|
||||||
|
|
||||||
del i # Free the inotify object
|
del i # Free the inotify object
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user