Recover from EINVAL even if the error is untyped (#1598)

* Recover from EINVAL even if the error is untyped

* Handle fcntl EINVAL error

* Fix EINVAL test

* Rename and deprecate errors

Co-authored-by: Cory Benfield <lukasa@apple.com>
This commit is contained in:
Timothée Peignier 2020-08-12 07:05:42 -07:00 committed by GitHub
parent 8dfcb578fc
commit 0570d9a1cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 30 additions and 26 deletions

View File

@ -41,7 +41,7 @@ extension FileDescriptor {
} catch let error as IOError {
if error.errnoCode == EINVAL {
// Darwin seems to sometimes do this despite the docs claiming it can't happen
throw NIOFailedToSetSocketNonBlockingError()
throw NIOFcntlFailedError()
}
throw error
}

View File

@ -249,9 +249,12 @@ final class ServerSocketChannel: BaseSocketChannel<ServerSocket> {
}
override func shouldCloseOnReadError(_ err: Error) -> Bool {
if err is NIOFailedToSetSocketNonBlockingError {
// see https://github.com/apple/swift-nio/issues/1030
// on Darwin, fcntl(fd, F_SETFL, O_NONBLOCK) sometimes returns EINVAL...
if err is NIOFcntlFailedError {
// See:
// - https://github.com/apple/swift-nio/issues/1030
// - https://github.com/apple/swift-nio/issues/1598
// on Darwin, fcntl(fd, F_SETFL, O_NONBLOCK) or fcntl(fd, F_SETNOSIGPIPE)
// sometimes returns EINVAL...
return false
}
guard let err = err as? IOError else { return true }

View File

@ -89,8 +89,12 @@ extension BaseSocketProtocol {
assert(fd >= 0, "illegal file descriptor \(fd)")
do {
try Posix.fcntl(descriptor: fd, command: F_SETNOSIGPIPE, value: 1)
} catch {
_ = try? Posix.close(descriptor: fd) // don't care about failure here
} catch let error as IOError {
if error.errnoCode == EINVAL {
// Darwin seems to sometimes do this despite the docs claiming it can't happen
throw NIOFcntlFailedError()
}
try? Posix.close(descriptor: fd) // don't care about failure here
throw error
}
#endif

View File

@ -300,19 +300,7 @@ internal enum Posix {
addr: UnsafeMutablePointer<sockaddr>?,
len: UnsafeMutablePointer<socklen_t>?) throws -> CInt? {
let result: IOResult<CInt> = try syscall(blocking: true) {
let fd = sysAccept(descriptor, addr, len)
#if !os(Linux)
if fd != -1 {
do {
try Posix.fcntl(descriptor: fd, command: F_SETNOSIGPIPE, value: 1)
} catch {
_ = sysClose(fd) // don't care about failure here
throw error
}
}
#endif
return fd
return sysAccept(descriptor, addr, len)
}
if case .processed(let fd) = result {
@ -574,11 +562,18 @@ internal enum Posix {
}
}
/// `NIOFcntlFailedError` indicates that NIO was unable to perform an
/// operation on a socket.
///
/// This error should never happen, unfortunately, we have seen this happen on Darwin.
public struct NIOFcntlFailedError: Error {}
/// `NIOFailedToSetSocketNonBlockingError` indicates that NIO was unable to set a socket to non-blocking mode, either
/// when connecting a socket as a client or when accepting a socket as a server.
///
/// This error should never happen because a socket should always be able to be set to non-blocking mode. Unfortunately,
/// we have seen this happen on Darwin.
@available(*, deprecated, renamed: "NIOFcntlFailedError")
public struct NIOFailedToSetSocketNonBlockingError: Error {}
internal extension Posix {
@ -590,7 +585,7 @@ internal extension Posix {
} catch let error as IOError {
if error.errnoCode == EINVAL {
// Darwin seems to sometimes do this despite the docs claiming it can't happen
throw NIOFailedToSetSocketNonBlockingError()
throw NIOFcntlFailedError()
}
throw error
}

View File

@ -594,9 +594,9 @@ public final class SocketChannelTest : XCTestCase {
serverChannel.read()
// Wait for the server to have something
let result = try assertNoThrowWithValue(serverPromise.futureResult.wait())
XCTAssertEqual(result.errnoCode, EINVAL)
XCTAssertThrowsError(try serverPromise.futureResult.wait()) { error in
XCTAssert(error is NIOFcntlFailedError)
}
#endif
}
@ -665,13 +665,15 @@ public final class SocketChannelTest : XCTestCase {
}
func testServerChannelDoesNotBreakIfAcceptingFailsWithEINVAL() throws {
// regression test for https://github.com/apple/swift-nio/issues/1030
// regression test for:
// - https://github.com/apple/swift-nio/issues/1030
// - https://github.com/apple/swift-nio/issues/1598
class HandsOutMoodySocketsServerSocket: ServerSocket {
let shouldAcceptsFail: NIOAtomic<Bool> = .makeAtomic(value: true)
override func accept(setNonBlocking: Bool = false) throws -> Socket? {
XCTAssertTrue(setNonBlocking)
if self.shouldAcceptsFail.load() {
throw NIOFailedToSetSocketNonBlockingError()
throw NIOFcntlFailedError()
} else {
return try Socket(protocolFamily: .inet,
type: .stream,
@ -688,7 +690,7 @@ public final class SocketChannelTest : XCTestCase {
}
func errorCaught(context: ChannelHandlerContext, error: Error) {
XCTAssert(error is NIOFailedToSetSocketNonBlockingError, "unexpected error: \(error)")
XCTAssert(error is NIOFcntlFailedError, "unexpected error: \(error)")
}
}