Put the FX reclaimer to sleep on a project-state load instead of waking it
abandon() clears the wakeup where invalidate() raises it, so File > Revert cannot converge FX or spend park records against a model nothing re-read.
This commit is contained in:
@@ -222,6 +222,18 @@ FxReclaimQueue queueOf(const ViewModeModel& vm, const std::set<std::string>& liv
|
||||
return q;
|
||||
}
|
||||
|
||||
// One whole timer tick as the shell makes it: main.cpp's ONE pending() test, then
|
||||
// fxReclaimTick's pin, its re-derivation from the session's model whenever the queue is
|
||||
// stale, and the drain. The re-derivation is what makes the wakeup the load-bearing bit —
|
||||
// it reads whatever model is live right now, whether or not anything re-read it since.
|
||||
void timerTick(FxReclaimQueue& q, const ViewModeModel& vm, const std::set<std::string>& live,
|
||||
FakeHost& host) {
|
||||
if (!q.pending()) return;
|
||||
if (!q.beginTick(host.project)) return;
|
||||
if (q.stale()) q.adopt(derive(vm, live));
|
||||
q.run(host);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// -- 1. debounce ---------------------------------------------------------------
|
||||
@@ -1254,10 +1266,11 @@ static void testAStateLoadStopsTheDrainWhereItStandsRatherThanFinishingTheTrack(
|
||||
CHECK(spanEndsAfter(r)); // the tick's own close still fires...
|
||||
CHECK(!span.end()); // ...and finds nothing: the terminator got there first
|
||||
|
||||
// The wakeup stands and the work is owed again: going idle here would strand the
|
||||
// half-parked chain for the rest of the session.
|
||||
// The work is owed again, and the loop is ASLEEP until something re-reads the model —
|
||||
// the reapply a reload drives is what raises the wakeup, and a load that drives none
|
||||
// must raise nothing (testARevertBetweenTicksLeavesTheLoopAsleepOnAStaleModel).
|
||||
CHECK(q.stale());
|
||||
CHECK(q.pending());
|
||||
CHECK(!q.pending());
|
||||
|
||||
// ...and the stop belonged to that tick alone. The reloaded project's own drain writes.
|
||||
host.abandonAfterWrite = -1;
|
||||
@@ -1267,6 +1280,57 @@ static void testAStateLoadStopsTheDrainWhereItStandsRatherThanFinishingTheTrack(
|
||||
CHECK(host.offlineAt("{T2}", 0));
|
||||
}
|
||||
|
||||
static void testARevertBetweenTicksLeavesTheLoopAsleepOnAStaleModel() {
|
||||
// File > Revert is the load that re-reads NOTHING: it keeps the same ReaProject, so the
|
||||
// session's identity poll answers NoOp, no reload follows and therefore no reapply
|
||||
// does either. The terminator is the only thing that runs, so whether it raises the
|
||||
// wakeup decides the whole outcome. Online work is the sharp case — undebounced, so ONE
|
||||
// woken tick both writes FX and spends the record's FX half, against the very model the
|
||||
// user reverted away from. The contrast with the mid-drain terminator test above: there
|
||||
// the load arrives through a write's own message pump, here between two ticks entirely.
|
||||
ViewModeModel vm = parkedDesignLeaf({"{FX-A}", "{FX-B}"});
|
||||
vm.setActiveMode(kDesignModeId);
|
||||
vm.releaseSnapshotFlags("{T}");
|
||||
|
||||
FakeHost host;
|
||||
host.model = &vm;
|
||||
host.chains["{T}"] = offlineChain({"{FX-A}", "{FX-B}"});
|
||||
ReleaseFlush flush;
|
||||
host.flush = &flush;
|
||||
|
||||
FxReclaimQueue q;
|
||||
CHECK(q.beginTick(kProjA)); // where an earlier tick left it: pinned, nothing owed
|
||||
q.invalidate(); // applyMode, leaving the switch that brought {T} back
|
||||
CHECK(q.pending());
|
||||
|
||||
q.abandon();
|
||||
CHECK(!q.pending());
|
||||
CHECK(q.stale());
|
||||
CHECK(q.items().empty());
|
||||
CHECK(q.project() == kProjA); // the pin stays: it is what guards the debounce clock
|
||||
|
||||
// Timer ticks keep arriving. The queue is empty AND stale, so it is the wakeup alone
|
||||
// that keeps the drain out — timerTick re-derives from the stale model exactly as the
|
||||
// shell would, which is what a raise here would have handed the drain.
|
||||
for (int i = 0; i < 3; ++i) timerTick(q, vm, {"{T}"}, host);
|
||||
CHECK(host.writeLog.empty());
|
||||
CHECK(host.released.empty());
|
||||
CHECK(!flush.pendingFor(kProjA));
|
||||
// ...and the chain is untouched — still parked offline, as the revert left it.
|
||||
CHECK(host.offlineAt("{T}", 0) && host.offlineAt("{T}", 1));
|
||||
|
||||
// The contrast, so the assertions above are about the wakeup and not about this queue
|
||||
// being unable to converge: a load that DOES re-read the model reaches the reclaimer
|
||||
// through its reapply's invalidate, and the very same tick over the very same model
|
||||
// both writes and spends.
|
||||
q.invalidate();
|
||||
timerTick(q, vm, {"{T}"}, host);
|
||||
CHECK(host.writeLog.size() == 2);
|
||||
CHECK(!host.offlineAt("{T}", 0) && !host.offlineAt("{T}", 1));
|
||||
CHECK(host.released.count("{T}") == 1);
|
||||
CHECK(flush.pendingFor(kProjA));
|
||||
}
|
||||
|
||||
// -- 12. the direction the drain is moving ----------------------------------------------
|
||||
|
||||
static void testTheDirectionReadsOnlineWhileTheParksOwnOfflineWorkWaitsOutItsHold() {
|
||||
@@ -1413,6 +1477,7 @@ int main() {
|
||||
testADrainNoSwitchOpenedMintsOnePointOfItsOwn();
|
||||
|
||||
testAStateLoadStopsTheDrainWhereItStandsRatherThanFinishingTheTrack();
|
||||
testARevertBetweenTicksLeavesTheLoopAsleepOnAStaleModel();
|
||||
|
||||
testTheDirectionReadsOnlineWhileTheParksOwnOfflineWorkWaitsOutItsHold();
|
||||
testAnOfflineOnlyQueueReadsIdleUntilItsHoldMatures();
|
||||
|
||||
Reference in New Issue
Block a user