Skip to content

Commit 7d0b78d

Browse files
committed
[android] Stop leaking FDs in parent test process.
In the Android paths of the spawnChild function, the parent was creating a pipe that was never closed, which led to FD starvation. In some tests with a lots of expected crashes, the childs will not spawn anymore since the linker would not have enough descriptors to open the shared libraries, while in other tests which closed the child descriptors as part of the last test, the parent process will hang waiting those descriptors to be closed, which will never had happened. The solution is implement the missing parts of the code, which tried to read from the pipe in the parent side (using select and read, taking pieces from other parts of the code). This should match the fork/execv path used by Android and Haiku to the spawn code used by the rest of the platforms. This change fixes StdlibUnittest/Stdin.swift, stdlib/InputStream.swift.gyb, stdlib/Collection/FlattenCollection.swift.gyb and stdlib/Collection/LazyFilterCollection.swift.gyb, which were the last 4 tests failing in Android AArch64.
1 parent 7311886 commit 7d0b78d

File tree

1 file changed

+39
-0
lines changed

1 file changed

+39
-0
lines changed

stdlib/private/SwiftPrivateLibcExtras/Subprocess.swift

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,10 @@ public func spawnChild(_ args: [String])
217217
if pid == 0 {
218218
// pid of 0 means we are now in the child process.
219219
// Capture the output before executing the program.
220+
close(childStdout.readFD)
221+
close(childStdin.writeFD)
222+
close(childStderr.readFD)
223+
close(childToParentPipe.readFD)
220224
dup2(childStdout.writeFD, STDOUT_FILENO)
221225
dup2(childStdin.readFD, STDIN_FILENO)
222226
dup2(childStderr.writeFD, STDERR_FILENO)
@@ -261,6 +265,41 @@ public func spawnChild(_ args: [String])
261265

262266
// Close the pipe when we're done writing the error.
263267
close(childToParentPipe.writeFD)
268+
} else {
269+
close(childToParentPipe.writeFD)
270+
271+
// Figure out if the child’s call to execve was successful or not.
272+
var readfds = _stdlib_fd_set()
273+
readfds.set(childToParentPipe.readFD)
274+
var writefds = _stdlib_fd_set()
275+
var errorfds = _stdlib_fd_set()
276+
errorfds.set(childToParentPipe.readFD)
277+
278+
var ret: CInt
279+
repeat {
280+
ret = _stdlib_select(&readfds, &writefds, &errorfds, nil)
281+
} while ret == -1 && errno == EINTR
282+
if ret <= 0 {
283+
fatalError("select() returned an error: \(errno)")
284+
}
285+
286+
if readfds.isset(childToParentPipe.readFD) || errorfds.isset(childToParentPipe.readFD) {
287+
var childErrno: CInt = 0
288+
let readResult: ssize_t = withUnsafeMutablePointer(to: &childErrno) {
289+
return read(childToParentPipe.readFD, $0, MemoryLayout.size(ofValue: $0.pointee))
290+
}
291+
if readResult == 0 {
292+
// We read an EOF indicating that the child's call to execve was successful.
293+
} else if readResult < 0 {
294+
fatalError("read() returned error: \(errno)")
295+
} else {
296+
// We read an error from the child.
297+
print(String(cString: strerror(childErrno)))
298+
preconditionFailure("execve() failed")
299+
}
300+
}
301+
302+
close(childToParentPipe.readFD)
264303
}
265304
#else
266305
var fileActions = _make_posix_spawn_file_actions_t()

0 commit comments

Comments
 (0)