Index: src/OFEpollKernelEventObserver.m ================================================================== --- src/OFEpollKernelEventObserver.m +++ src/OFEpollKernelEventObserver.m @@ -128,13 +128,19 @@ objectForKey: (void *)((intptr_t)fd + 1)]; events &= ~removeEvents; if (events == 0) { if (epoll_ctl(_epfd, EPOLL_CTL_DEL, fd, NULL) == -1) - @throw [OFObserveFailedException - exceptionWithObserver: self - errNo: errno]; + /* + * When an async connect fails, it seems the socket is + * automatically removed from epoll, meaning ENOENT is + * returned when we try to remove it after it failed. + */ + if (errno != ENOENT) + @throw [OFObserveFailedException + exceptionWithObserver: self + errNo: errno]; [_FDToEvents removeObjectForKey: (void *)((intptr_t)fd + 1)]; } else { struct epoll_event event; Index: src/OFRunLoop.m ================================================================== --- src/OFRunLoop.m +++ src/OFRunLoop.m @@ -702,13 +702,32 @@ port: 0 socket: object errNo: errNo]; if ([_delegate respondsToSelector: - @selector(of_socketDidConnect:exception:)]) - [_delegate of_socketDidConnect: object - exception: exception]; + @selector(of_socketDidConnect:exception:)]) { + /* + * Make sure we only call the delegate once we removed the + * socket from the kernel event observer. This is necessary as + * otherwise we could try to connect to the next address and it + * would not be re-registered with the kernel event observer, + * which is necessary for some kernel event observers (e.g. + * epoll) even if the fd of the new socket is the same. + */ + OFRunLoop *runLoop = [OFRunLoop currentRunLoop]; + OFTimer *timer = [OFTimer + timerWithTimeInterval: 0 + target: _delegate + selector: @selector(of_socketDidConnect: + exception:) + object: object + object: exception + repeats: false]; + + [runLoop addTimer: timer + forMode: runLoop.currentMode]; + } return false; } @end # endif