Uh oh!
There was an error while loading. Please reload this page.
Fix fdsan violation in StdoutLogger::Stop() on Android API 29+ - #17
Conversation
StdoutLogger::Start() calls fdopen(fd[0], "r") to create a FILE* for each reader thread. Per POSIX, fdopen() transfers ownership of the file descriptor to the FILE*; the caller must use fclose() — not close() — to release it. StdoutLogger::Stop() was calling close(fd_stdout[0]) and close(fd_stderr[0]) directly. On Android API 29+, fdsan enforces ownership tracking and aborts with SIGABRT when a raw close() is called on a descriptor owned by a FILE*: Fatal signal 6 (SIGABRT) Abort message: 'fdsan: attempted to close file descriptor 94, expected to be unowned, actually owned by FILE* 0x6e357db180' Fix: in Stop(), only close the write ends (fd[1]). Closing fd[1] sends EOF through the pipe, causing getline() in the reader thread to return -1, which breaks the loop and calls fclose(stream) — correctly closing fd[0]. Remove the explicit close(fd[0]) / close(fd[1]) calls entirely.
Replace the local PATCH_COMMAND workaround with a direct reference to the fix commit on the fork (PR: BabylonJS/AndroidExtensions#17). Remove patches/AndroidExtensions/StdoutLogger.cpp. Once the PR is merged upstream, revert GIT_REPOSITORY back to BabylonJS/AndroidExtensions and update GIT_TAG to the merge commit. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR aims to prevent Android API 29+ fdsan aborts by avoiding close() on pipe read-ends that have been transferred to FILE* ownership via fdopen() in StdoutLogger.
Changes:
- Updates
StdoutLogger::Stop()to close only the pipe write-ends and stop directly closing pipe read-ends. - Adds explanatory comments about
fdopen()ownership and fdsan behavior on Android API 29+.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
bghgary
left a comment
There was a problem hiding this comment.
[Approved by Copilot on behalf of @bghgary]
LGTM.
A few pre-existing issues in this file, not introduced here and not blocking, but worth tracking as follow-ups:
- Detached reader threads are never joined —
Stop()returns while they may still be draining. - Original
stdout/stderrfds aren't saved, so writes afterStop()hit a closed pipe (EPIPE). pthread_createerror check compares against-1, but it returns an errno code on failure; the error path also leaks the pipe fds.pipe()'s return value is ignored too.
Uh oh!
There was an error while loading. Please reload this page.
Problem
StdoutLogger::Start()callsfdopen(fd[0], "r")to hand each pipe read-end to a reader thread.fdopen()transfers ownership of the file descriptor to theFILE*; it must be released withfclose(), notclose().StdoutLogger::Stop()was callingclose(fd_stdout[0])andclose(fd_stderr[0])directly. On Android API 29+, fdsan tracks descriptor ownership and aborts withSIGABRTwhen a rawclose()is called on a descriptor owned by aFILE*:Fix
In
Stop(), only close the write ends (fd[1]). Closingfd[1]sends EOF through the pipe;getline()in the reader thread returns-1, exits the loop, and callsfclose(stream)— which correctly releases the read end. The explicitclose(fd[0])calls are removed entirely.