From 15a46dc9a654feb7847040052daf23f3d794d9cf Mon Sep 17 00:00:00 2001 From: Day Dreamer <87640074+HugoXK@users.noreply.github.com> Date: Tue, 10 Nov 2026 08:22:04 +0800 Subject: [PATCH] tree: bound each reorganization job so a hung host call cannot block the queue A job whose host call never settles held the serial reorganization queue for ever: every later placement, repair and tidy waited on it and settled() never resolved (#2751). Give every queued job a deadline; when it passes, log the job id, drop the result and let the next job run. An exclusive() job reports the give-up with abandoned: true and its job id instead of hanging, and an abandoned placement clears its 'organizing' flag so adoption can retry it. --- changelog.d/2751-reorg-queue-bound.fixed.md | 1 + docs/subsystems/tree.md | 1 + plugins/tree/src/organize.js | 65 ++++++++++++-- .../tree/test/organize-queue-bound.test.js | 84 +++++++++++++++++++ 4 files changed, 146 insertions(+), 5 deletions(-) create mode 100644 changelog.d/2751-reorg-queue-bound.fixed.md create mode 100644 plugins/tree/test/organize-queue-bound.test.js diff --git a/changelog.d/2751-reorg-queue-bound.fixed.md b/changelog.d/2751-reorg-queue-bound.fixed.md new file mode 100644 index 000000000..26e635d43 --- /dev/null +++ b/changelog.d/2751-reorg-queue-bound.fixed.md @@ -0,0 +1 @@ +A tree reorganization no longer waits for ever on a host call that never answers: after 10 minutes the job is abandoned with its id in the log (`flybot-tree: reorganization job was abandoned after …`), later placements, repairs and the daily tidy still run, and `settled()` still resolves. An abandoned `exclusive()` job reports `abandoned: true` with its job id instead of hanging. See [#2751](https://github.com/ThinkFlowLab/FlyBot/issues/2751). diff --git a/docs/subsystems/tree.md b/docs/subsystems/tree.md index 885d00f17..a1970763c 100644 --- a/docs/subsystems/tree.md +++ b/docs/subsystems/tree.md @@ -76,6 +76,7 @@ The tree organizes itself by role instead of leaving Bots outside it or a parent - **Placement** of a Bot outside the tree: its namesake, else the tree Bot that leads a group it belongs to (most specific key first, then the shallower lead; an area only through a lead made for it, e.g. "Work Lead"), else the root. At the first of those with room it is placed. A full level is **regrouped**: the most specific group of two or more children (the newcomer counted) moves under a suitable existing member, or under a new lead. Inside a full lead of its own group with no finer group, the member closest to its role takes it (all members share the group). There is **no "least-loaded child" fallback**: a Bot that fits no group while every level it matches is full stays outside, a warning names it and the sidebar tags it "outside the tree"; the next attempt (any `bot.created`, startup) tries again. - **Leads.** A suitable existing lead is a member already marked as that group's lead, already coordinating that group (it has children and the key, project and domain only), or named for it ("Life", "FlyBot Lead") or as a lead ("Kitchen Manager", 负责人). It gets `lead { key, label }`. Only when none fits a new lead is created with a clear role (`