LearnNewsExamplesServices
Frontmatter
id8801
titleImplement generic async destruction handling via Promise Rejection
stateClosed
labels
aiarchitectureperformancecore
assigneestobiu
createdAtJan 19, 2026, 10:29 AM
updatedAtJan 19, 2026, 11:17 AM
githubUrlhttps://github.com/neomjs/neo/issues/8801
authortobiu
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtJan 19, 2026, 11:17 AM

Implement generic async destruction handling via Promise Rejection

Closed v11.23.0 aiarchitectureperformancecore
tobiu
tobiu commented on Jan 19, 2026, 10:29 AM

Currently, core.Base.timeout promises hang forever if the component is destroyed before completion, causing memory leaks (async closures never GC'd). Manual isDestroyed checks after awaits are brittle.

Plan:

  1. Add Neo.DESTROYED constant.
  2. Add global unhandledrejection handler to suppress Neo.DESTROYED errors.
  3. Update core.Base:
    • Track timeout reject functions.
    • In destroy(), reject all pending timeouts with Neo.DESTROYED.
  4. Remove manual guards from MagicMoveText as a verification step.
tobiu added the ai label on Jan 19, 2026, 10:30 AM
tobiu added the architecture label on Jan 19, 2026, 10:30 AM
tobiu added the performance label on Jan 19, 2026, 10:30 AM
tobiu added the core label on Jan 19, 2026, 10:30 AM
tobiu assigned to @tobiu on Jan 19, 2026, 10:30 AM
tobiu referenced in commit 07b6933 - "feat: Implement generic async destruction handling via Promise Rejection (#8801)" on Jan 19, 2026, 11:16 AM
tobiu closed this issue on Jan 19, 2026, 11:17 AM
tobiu
tobiu Jan 19, 2026, 11:17 AM

Input from Gemini 3 Pro:

✦ I have implemented the generic async destruction handling and verified it with unit tests and by updating MagicMoveText.

Changes:

  1. src/Neo.mjs: Added Neo.isDestroyed symbol and a global unhandledrejection handler to suppress errors rejected with this symbol.
  2. src/core/Base.mjs:
    • Refactored #timeoutIds array to #timeouts Map (id -> reject).
    • Updated destroy() to reject all pending timeouts with Neo.isDestroyed.
    • Updated timeout() to register and deregister reject functions.
  3. src/component/MagicMoveText.mjs:
    • Removed manual if (me.isDestroyed) return; checks after await me.timeout(...).
    • Updated the catch block to explicitly ignore Neo.isDestroyed rejections to prevent unwanted retries.
  4. test/playwright/unit/core/BaseTimeout.spec.mjs: Added a comprehensive test suite to verify:
    • Timeout resolution.
    • Rejection on destruction.
    • Concurrent timeout handling.
    • Cleanup of completed timeouts.

Verification: The new test suite passed successfully, confirming that timeout() promises are now correctly rejected when an instance is destroyed, preventing memory leaks and zombie processes. The integration with MagicMoveText demonstrates the cleaner, guard-free usage pattern (for timeouts).

Next Steps: Other async methods like promiseUpdate and getDomRect currently do not auto-reject on destruction. This should be addressed in follow-up tasks to achieve full coverage.