fix(runtime): restore overlay retry queue

This commit is contained in:
Kinneyzhang 2026-09-01 03:16:22 +08:00
parent aeb1b0f7c7
commit 4e75aaf0d3
2 changed files with 92 additions and 6 deletions

View File

@ -5734,6 +5734,16 @@ RENDERED-IDENTITIES names the Component render participants."
(setq owner (etaf--semantic-component-identity semantic))))))
owner))
(defun etaf--runtime-restore-component-overlay-retry
(runtime retry-effect-ids)
"Restore failed RUNTIME overlay work for RETRY-EFFECT-IDS."
(etaf--runtime-clear-dirty-effects runtime)
(dolist (effect-id retry-effect-ids)
(etaf--runtime-enqueue-effect runtime effect-id))
(etaf--runtime-dispose-created-candidate runtime)
(etaf--runtime-rollback-behaviors runtime)
(etaf--runtime-clear-candidate runtime))
(defun etaf--runtime-component-overlay (runtime)
"Publish RUNTIME dirty Component effects without traversing Root."
(cl-block etaf--runtime-component-overlay
@ -5749,7 +5759,10 @@ RENDERED-IDENTITIES names the Component render participants."
backend-component-identities
journal resource-journal candidate-generation semantic-candidate
participant)
(etaf--runtime-begin-component-overlay runtime old)
(let ((evaluation-completed-p nil))
(unwind-protect
(progn
(etaf--runtime-begin-component-overlay runtime old)
;; A source notification can race a generation promotion and leave an old
;; effect id in the FIFO. Keep the dirty-id set and FIFO coherent before
;; sorting; stale ids are retired by the candidate generation cleanup.
@ -5844,6 +5857,10 @@ RENDERED-IDENTITIES names the Component render participants."
(cl-pushnew effect-id
(etaf-runtime-candidate-removed-effect-ids runtime)
:test #'eql))))
(setq evaluation-completed-p t))
(unless evaluation-completed-p
(etaf--runtime-restore-component-overlay-retry
runtime retry-effect-ids))))
(setq range-changes
(append (nreverse
(etaf-runtime-candidate-eager-range-changes runtime))
@ -6044,11 +6061,8 @@ RENDERED-IDENTITIES names the Component render participants."
semantic-candidate))
(etaf--runtime-rollback-prearm runtime journal)
(etaf--runtime-rollback-resource-journal runtime resource-journal)
(etaf--runtime-clear-dirty-effects runtime)
(dolist (effect-id retry-effect-ids)
(etaf--runtime-enqueue-effect runtime effect-id))
(etaf--runtime-dispose-created-candidate runtime)
(etaf--runtime-clear-candidate runtime)
(etaf--runtime-restore-component-overlay-retry
runtime retry-effect-ids)
(signal (car err) (cdr err))))
(etaf--runtime-participant-commit participant)
(etaf--runtime-complete-generation runtime candidate-generation)

View File

@ -14,6 +14,8 @@
"ETAF scheduler projection test condition")
(define-error 'etaf-scheduler-test-body-condition
"ETAF scheduler body test condition")
(define-error 'etaf-scheduler-test-render-recovery-condition
"ETAF scheduler render recovery condition")
(etaf-define-component etaf-scheduler-test-pair (&key left right)
"Render reactive LEFT and RIGHT values."
@ -43,6 +45,16 @@
(signal 'etaf-scheduler-test-projection-condition '("projection")))
(etaf-node 'text nil (list (symbol-name status)))))
(etaf-define-component etaf-scheduler-test-render-recovery
(&key source fail)
"Render SOURCE unless FAIL requests a deterministic render error."
:render
(progn
(when (etaf-value fail)
(signal 'etaf-scheduler-test-render-recovery-condition
'("expected render failure" :payload recovery)))
(etaf-node 'text nil (list (format "Value %s" (etaf-value source))))))
(defun etaf-scheduler-test--text (buffer-name)
"Return BUFFER-NAME text without properties."
(with-current-buffer buffer-name
@ -494,6 +506,66 @@
(etaf-value (etaf-data-error controller)))))
(etaf-data-stop controller))))
(ert-deftest etaf-scheduler-render-failure-keeps-retry-fifo-coherent ()
"A failed Component evaluation retries and visibly recovers on new state."
(let* ((buffer-name " *etaf-scheduler-render-recovery*")
(source (etaf-ref "A"))
(fail (etaf-ref nil))
captured)
(unwind-protect
(progn
(etaf-mount
buffer-name
(etaf--view-call
'etaf-scheduler-test-render-recovery
(list :source source :fail fail) nil))
(setf (etaf-value source) "B")
(let* ((runtime (etaf-runtime-for-buffer buffer-name))
(generation (etaf-runtime-generation runtime))
(revision
(plist-get (ebox-buffer-update-report buffer-name)
:surface-revision))
(contents
(with-current-buffer buffer-name (buffer-string))))
(condition-case condition
(setf (etaf-value fail) t)
(etaf-scheduler-test-render-recovery-condition
(setq captured condition)))
(should
(equal captured
'(etaf-scheduler-test-render-recovery-condition
"expected render failure" :payload recovery)))
(should (= generation (etaf-runtime-generation runtime)))
(should (= revision
(plist-get (ebox-buffer-update-report buffer-name)
:surface-revision)))
(should (equal-including-properties
contents (with-current-buffer buffer-name
(buffer-string))))
(should
(equal (sort (hash-table-keys
(etaf-runtime-dirty-effect-ids runtime)) #'<)
(sort (copy-sequence
(etaf-runtime-dirty-effect-queue runtime)) #'<)))
(etaf-reactive-call-with-batch
(lambda ()
(setf (etaf-value fail) nil)
(setf (etaf-value source) "C")))
(should (equal "Value C"
(etaf-scheduler-test--text buffer-name)))
(should (= (1+ generation)
(etaf-runtime-generation runtime)))
(should (= (1+ revision)
(plist-get (ebox-buffer-update-report buffer-name)
:surface-revision)))
(should-not (hash-table-keys
(etaf-runtime-dirty-effect-ids runtime)))
(should-not (etaf-runtime-dirty-effect-queue runtime))))
(when-let* ((runtime (etaf-runtime-for-buffer buffer-name)))
(etaf-unmount runtime))
(when-let* ((buffer (get-buffer buffer-name)))
(kill-buffer buffer)))))
(ert-deftest etaf-scheduler-detached-route-does-not-enter-fan-out ()
"An invalidated Runtime route cannot enqueue work in its old context."
(let* ((buffer-name " *etaf-scheduler-stale-route*")