Closed Bug 1979546 Opened 1 year ago Closed 1 year ago

Subprocess call can deadlock on Windows when WaitForMultipleObjects receives 64+ events (=16+ concurrent programs)

Categories

(Toolkit :: Async Tooling, defect, P1)

defect

Tracking

()

RESOLVED FIXED
143 Branch
Tracking Status
firefox-esr140 144+ fixed
firefox143 --- fixed

People

(Reporter: robwu, Assigned: robwu)

References

Details

(Whiteboard: [addons-jira])

Attachments

(2 external files)

Bug 1971449 reports a scenario with the following characteristics:

After auditing the relevant implementation in Firefox, I found a potential way for this to trigger this outcome.

  • The NativeMessaging implementation always writes two chunks as part of any write. This passes through write in subprocess_common.sys.mjs to write in subprocess_win.worker.js.
  • The completion is reported via onReady(), via poll.
  • In practice, this means that the maximum number of concurrent processes supported by the Subprocess module on Windows is 15:
    • 1 signal (always present, used to detect Firefox shutdown)
    • 15 processes
    • 45 pipes (stdin/stdout/stderr for each process - this could be lower if there are no pending reads or writes)
    • is 61 events out of the limit of 64.
    • If 16 or more processes are created concurrently, the number of events exceeds MAXIMUM_WAIT_OBJECTS and WaitForMultipleObjects will fail and therefore io.poll() will deadlock, because it is no longer able to be notified of changes to pipes and processes.

I created a unit test that successfully creates 15 processes, but fails at 16.

The MSDN documentation was suggesting something very complicated.

I found this article that may be a simpler path towards dealing with the limit: https://devblogs.microsoft.com/oldnewthing/?p=110169

I considered splitting the list of handles in chunks and using WaitForMultipleObjects on separate lists of handles. When there is any activity, this works reasonably well (we can set the timeout to 0). But if all pipes and processes are pending, we need to choose which 64 of them to wait for. While waiting for these, the thread is unaware of progress of the others. A way to overcome that is to lower the timeout, from 5000ms to something shorter, at the expense of waking up the subprocess thread more often. That's not ideal either.

I learned about the concept of I/O Completion Ports (IOCP) (https://learn.microsoft.com/en-us/windows/win32/fileio/i-o-completion-ports), which seems to fit the problem space here. We need the ability to be notified of file read/write completion, process termination and a way to unblock the waiting worker thread so it can process messages from the parent thread (Subprocess.sys.mjs).

IOCP appears to provide the desired functionality:

I'll give a try at implementing this.

For reference, the new library symbols from kernel32.dll that I will be importing in my patch.

See Also: → 1979728

The current WaitForMultipleObjects-based implementation causes a
deadlock if 16 or more processes are spawned, as explained in the bug.

This patch replaces the use of events and WaitForMultipleObjects with
I/O Completion Ports (IOCP) as the mechanism to monitor I/O progress
and process terminations. This is not subject to the
MAXIMUM_WAIT_OBJECTS (64) limit of WaitForMultipleObjects.

Severity: -- → S3
Priority: -- → P1
Pushed by rob@robwu.nl: https://github.com/mozilla-firefox/firefox/commit/abd041fb3165 https://hg.mozilla.org/integration/autoland/rev/561affffd311 Replace WaitForMultipleObjects with IOCP in Subprocess r=win-reviewers,extension-reviewers,rpl,gstoll
Pushed by sstanca@mozilla.com: https://github.com/mozilla-firefox/firefox/commit/c5edae39a0a8 https://hg.mozilla.org/integration/autoland/rev/d46bbed8e543 Revert "Bug 1979546 - Replace WaitForMultipleObjects with IOCP in Subprocess r=win-reviewers,extension-reviewers,rpl,gstoll" for causing devtools failures.

Reverted this because it was causing devtools failures.

Please also check these mochitests failures.

Flags: needinfo?(rob)
Depends on: 1982170

This is an issue in the test harness, I'll fix it in https://bugzilla.mozilla.org/show_bug.cgi?id=1982170#c1 and then reland.

Note that it's worth mentioning that I believe that there is a pre-existing issue that causes the timer to be scheduled. It is the third bullet point of my list of unresolved todos that I collected while looking into this bug:

I intended to fix these separately in a follow-up because they are pre-existing issues that have nothing to do with the bug reported here.

Flags: needinfo?(rob)

Removing bug 1982170 as a dependency; Although the specific crash is indeed a test-only issue, the fact that a timer was still running was indeed an issue with the patch. I fixed it and don't see the previously failing tests don't fail any more in the try push.

No longer depends on: 1982170
See Also: → 1982170
Pushed by rob@robwu.nl: https://github.com/mozilla-firefox/firefox/commit/90a9d079dc47 https://hg.mozilla.org/integration/autoland/rev/ec77192e6f06 Replace WaitForMultipleObjects with IOCP in Subprocess r=win-reviewers,extension-reviewers,rpl,gstoll
Status: ASSIGNED → RESOLVED
Closed: 1 year ago
Resolution: --- → FIXED
Target Milestone: --- → 143 Branch
Regressions: 1982950
QA Whiteboard: [qa-triage-done-c144/b143]

The current WaitForMultipleObjects-based implementation causes a
deadlock if 16 or more processes are spawned, as explained in the bug.

This patch replaces the use of events and WaitForMultipleObjects with
I/O Completion Ports (IOCP) as the mechanism to monitor I/O progress
and process terminations. This is not subject to the
MAXIMUM_WAIT_OBJECTS (64) limit of WaitForMultipleObjects.

Original Revision: https://phabricator.services.mozilla.com/D258968

Attachment #9516422 - Flags: approval-mozilla-esr140?

firefox-esr140 Uplift Approval Request

  • User impact if declined: Windows-only; When the Subprocess.sys.mjs module has 16 processes or more concurrent processes, the subprocess worker that manages them loses its ability to monitor the state of the processes and stdin/stdout/stderr pipes that it manages. The Subprocess module is used by DevTools (Browser Toolbox) and WebExtensions (Native Messaging), and we have received a report of extensions being broken for users (bug 1971449), with gigabytes of memory usage and over 1000 processes spawned.
  • Code covered by automated testing: yes
  • Fix verified in Nightly: yes
  • Needs manual QE test: no
  • Steps to reproduce for manual QE testing: QA not needed; any coverage that we'd get is already fully covered by unit tests.
  • Risk associated with taking this patch: medium
  • Explanation of risk level: Low, barely Medium risk. Medium because this is a significant but necessary refactor of the mechanism to monitor state changes to processes and pipes in the Subprocess.sys.mjs module. The risk is managed by the patches including extensive test coverage (that demonstrated the deadlock before, and verifies the fix after), and the patches has been on the train to release (143) without reported regressions or breakage. Furthermore, the changes only affect Subprocess.sys.mjs consumers (DevTools Browser Toolbox, and WebExtensions Native Messaging), so it does not impact Firefox users who do not use these features.
  • String changes made/needed: None
  • Is Android affected?: no
Flags: in-testsuite+
Attachment #9516422 - Flags: approval-mozilla-esr140? → approval-mozilla-esr140+
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: