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
|
||||
level = 0
|
||||
i = 0
|
||||
iteration_count = 0
|
||||
|
||||
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:
|
||||
# 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}")
|
||||
return i
|
||||
if cmp < 0:
|
||||
logger.debug(f"DEBUG: treeinspos CONTINUE: cmp < 0")
|
||||
logger.debug("DEBUG: treeinspos CONTINUE: cmp < 0")
|
||||
continue
|
||||
|
||||
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}")
|
||||
i += 1
|
||||
|
||||
logger.debug(
|
||||
f"DEBUG: treeinspos EXIT: returning i={i}, iterations={iteration_count}"
|
||||
)
|
||||
logger.debug(f"DEBUG: treeinspos EXIT: returning i={i}")
|
||||
return i
|
||||
|
||||
|
||||
@ -336,6 +319,10 @@ def format_update(old, new):
|
||||
update = []
|
||||
keep_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):
|
||||
iteration_count += 1
|
||||
@ -411,18 +398,38 @@ def format_update(old, new):
|
||||
update.append(UpdIns(insert_items))
|
||||
|
||||
if not modified:
|
||||
logger.error(
|
||||
f"ERROR: format_update INFINITE_LOOP: nidx={nidx}, oidx={oidx}, old_len={len(old)}, new_len={len(new)}"
|
||||
)
|
||||
logger.error(
|
||||
f"ERROR: old[oidx]={old[oidx].name if oidx < len(old) else 'OUT_OF_BOUNDS'}"
|
||||
)
|
||||
logger.error(
|
||||
f"ERROR: new[nidx]={new[nidx].name if nidx < len(new) else 'OUT_OF_BOUNDS'}"
|
||||
)
|
||||
raise Exception(
|
||||
f"Infinite loop in diff {nidx=} {oidx=} {len(old)=} {len(new)=}"
|
||||
)
|
||||
# Tie-break: both items exist in both lists but don't match here.
|
||||
# Decide whether to delete old[oidx] first or insert new[nidx] first
|
||||
# based on which alignment is closer.
|
||||
if oidx >= len(old) or nidx >= len(new):
|
||||
break
|
||||
cur_old = old[oidx]
|
||||
cur_new = new[nidx]
|
||||
|
||||
pos_old_in_new = new_pos.get(cur_old)
|
||||
pos_new_in_old = old_pos.get(cur_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
|
||||
if keep_count > 0:
|
||||
@ -547,18 +554,43 @@ def watcher_inotify(loop):
|
||||
)
|
||||
t0 = time.perf_counter()
|
||||
logger.debug("DEBUG: inotify CALLING format_update")
|
||||
update = format_update(state.root, rootmod)
|
||||
logger.debug("DEBUG: inotify format_update COMPLETED")
|
||||
t1 = time.perf_counter()
|
||||
with state.lock:
|
||||
logger.debug("DEBUG: inotify BROADCASTING update")
|
||||
broadcast(update, loop)
|
||||
state.root = rootmod
|
||||
logger.debug("DEBUG: inotify BROADCAST completed, state updated")
|
||||
t2 = time.perf_counter()
|
||||
logger.debug(
|
||||
f"Format update took {t1 - t0:.1f}s, broadcast {t2 - t1:.1f}s"
|
||||
)
|
||||
try:
|
||||
update = format_update(state.root, rootmod)
|
||||
logger.debug("DEBUG: inotify format_update COMPLETED")
|
||||
t1 = time.perf_counter()
|
||||
with state.lock:
|
||||
logger.debug("DEBUG: inotify BROADCASTING update")
|
||||
broadcast(update, loop)
|
||||
state.root = rootmod
|
||||
logger.debug("DEBUG: inotify BROADCAST completed, state updated")
|
||||
t2 = time.perf_counter()
|
||||
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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user