diff options
author | David Pursell <dpursell@google.com> | 2016-02-04 15:21:58 -0800 |
---|---|---|
committer | David Pursell <dpursell@google.com> | 2016-02-05 13:22:06 -0800 |
commit | c742a7f17475e22739c09a5b19862b8d1ec6050a (patch) | |
tree | 9b416525227029a3366064d0328e9ca8e8539a76 /fastboot/socket.cpp | |
parent | aae1eb2c4f10f3d2c49455eb37c4ae4b38ffa47d (diff) |
fastboot: add Socket timeout detection.
UDP fastboot will require re-transmission in the case of datagrams
getting lost. This CL adds Socket functionality to easily distinguish
between a normal timeout and a socket failure.
I also found some Windows docs that indicate sockets may become
invalid after a call to recv() times out. This has never occurred in
my testing, but to be safe this switches the timeout implementation
to use select() instead of SO_RCVTIMEO.
Bug: http://b/26154914
Change-Id: Id7b598f8aea5df1a3676d24702b489042d5f9e3a
Diffstat (limited to 'fastboot/socket.cpp')
-rw-r--r-- | fastboot/socket.cpp | 50 |
1 files changed, 36 insertions, 14 deletions
diff --git a/fastboot/socket.cpp b/fastboot/socket.cpp index d49f47ff2..14ecd937a 100644 --- a/fastboot/socket.cpp +++ b/fastboot/socket.cpp @@ -48,18 +48,6 @@ int Socket::Close() { return ret; } -bool Socket::SetReceiveTimeout(int timeout_ms) { - if (timeout_ms != receive_timeout_ms_) { - if (socket_set_receive_timeout(sock_, timeout_ms) == 0) { - receive_timeout_ms_ = timeout_ms; - return true; - } - return false; - } - - return true; -} - ssize_t Socket::ReceiveAll(void* data, size_t length, int timeout_ms) { size_t total = 0; @@ -82,6 +70,40 @@ int Socket::GetLocalPort() { return socket_get_local_port(sock_); } +// According to Windows setsockopt() documentation, if a Windows socket times out during send() or +// recv() the state is indeterminate and should not be used. Our UDP protocol relies on being able +// to re-send after a timeout, so we must use select() rather than SO_RCVTIMEO. +// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms740476(v=vs.85).aspx. +bool Socket::WaitForRecv(int timeout_ms) { + receive_timed_out_ = false; + + // In our usage |timeout_ms| <= 0 means block forever, so just return true immediately and let + // the subsequent recv() do the blocking. + if (timeout_ms <= 0) { + return true; + } + + // select() doesn't always check this case and will block for |timeout_ms| if we let it. + if (sock_ == INVALID_SOCKET) { + return false; + } + + fd_set read_set; + FD_ZERO(&read_set); + FD_SET(sock_, &read_set); + + timeval timeout; + timeout.tv_sec = timeout_ms / 1000; + timeout.tv_usec = (timeout_ms % 1000) * 1000; + + int result = TEMP_FAILURE_RETRY(select(sock_ + 1, &read_set, nullptr, nullptr, &timeout)); + + if (result == 0) { + receive_timed_out_ = true; + } + return result == 1; +} + // Implements the Socket interface for UDP. class UdpSocket : public Socket { public: @@ -127,7 +149,7 @@ bool UdpSocket::Send(std::vector<cutils_socket_buffer_t> buffers) { } ssize_t UdpSocket::Receive(void* data, size_t length, int timeout_ms) { - if (!SetReceiveTimeout(timeout_ms)) { + if (!WaitForRecv(timeout_ms)) { return -1; } @@ -206,7 +228,7 @@ bool TcpSocket::Send(std::vector<cutils_socket_buffer_t> buffers) { } ssize_t TcpSocket::Receive(void* data, size_t length, int timeout_ms) { - if (!SetReceiveTimeout(timeout_ms)) { + if (!WaitForRecv(timeout_ms)) { return -1; } |