Part of #968.
Problem
Compiled fs/promises is fake-async. Each method (FsReadFileAsync, FsWriteFileAsync, …) calls the corresponding sync implementation and wraps the result in Task.FromResult (see RuntimeEmitter.FsAsync.cs, BeginFsAsyncTryCatch → sync call → Task.FromResult; errors → Task.FromException). The returned promise is already-completed, so:
- the I/O runs synchronously on the calling thread — no overlap with other work;
- it diverges from the interpreter, which runs genuine
Task.Run background I/O and resumes via the event loop.
Scope
- Replace the sync-then-
FromResult shape with real backgrounding: run the op on the thread pool (Task.Run / the async BCL APIs File.ReadAllTextAsync etc.), return the in-flight Task, and resume continuations through $EventLoop with proper Ref/Unref so the loop stays alive until the op drains.
- Keep error mapping to Node shape (
err.code).
- Align with the callback-async work (sibling child) so both share one async-op wrapper.
Consistency goal: after this, compiled fs/promises, compiled callback-async, and the interpreter all use the same "real async op → event-loop resume" path. Worth doing alongside the callback child rather than after, so the wrapper is written once.
Approach B note: under the primitive:fs facade, the primitive exposes the real Task-returning ops (the interpreter already has FsAsyncHelpers.*Async); the TS facade just awaits them. This child then reduces to "make the fs primitive's async ops genuinely backgrounded."
Acceptance
- A compiled
fs/promises op overlaps with concurrent work (e.g. a long read does not block a setImmediate/timer that should interleave) — add a test asserting interleaving / non-blocking behavior.
- Identical results + error codes interpreted vs compiled.
- No regression in
dotnet test / Test262 / conformance.
Part of #968.
Problem
Compiled
fs/promisesis fake-async. Each method (FsReadFileAsync,FsWriteFileAsync, …) calls the corresponding sync implementation and wraps the result inTask.FromResult(seeRuntimeEmitter.FsAsync.cs,BeginFsAsyncTryCatch→ sync call →Task.FromResult; errors →Task.FromException). The returned promise is already-completed, so:Task.Runbackground I/O and resumes via the event loop.Scope
FromResultshape with real backgrounding: run the op on the thread pool (Task.Run/ the async BCL APIsFile.ReadAllTextAsyncetc.), return the in-flightTask, and resume continuations through$EventLoopwith properRef/Unrefso the loop stays alive until the op drains.err.code).Consistency goal: after this, compiled
fs/promises, compiled callback-async, and the interpreter all use the same "real async op → event-loop resume" path. Worth doing alongside the callback child rather than after, so the wrapper is written once.Approach B note: under the
primitive:fsfacade, the primitive exposes the realTask-returning ops (the interpreter already hasFsAsyncHelpers.*Async); the TS facade just awaits them. This child then reduces to "make the fs primitive's async ops genuinely backgrounded."Acceptance
fs/promisesop overlaps with concurrent work (e.g. a long read does not block asetImmediate/timer that should interleave) — add a test asserting interleaving / non-blocking behavior.dotnet test/ Test262 / conformance.