Bluetooth: Add a timeout in async_fd_watcher
Add a timeout to the select call and a callback.
Add unit tests for async_fd_watcher.
Test: unit tests pass
Change-Id: I0076fd52e79aac0d2a9fcceb90aae318d5f0757b
diff --git a/bluetooth/1.0/default/Android.bp b/bluetooth/1.0/default/Android.bp
index 32e5328..a99cd9e 100644
--- a/bluetooth/1.0/default/Android.bp
+++ b/bluetooth/1.0/default/Android.bp
@@ -38,7 +38,9 @@
cc_test_host {
name: "bluetooth-vendor-interface-unit-tests",
srcs: [
+ "async_fd_watcher.cc",
"bluetooth_address.cc",
+ "test/async_fd_watcher_unittest.cc",
"test/bluetooth_address_test.cc",
"test/properties.cc",
],
diff --git a/bluetooth/1.0/default/async_fd_watcher.cc b/bluetooth/1.0/default/async_fd_watcher.cc
index 636b4b6..9cd86f1 100644
--- a/bluetooth/1.0/default/async_fd_watcher.cc
+++ b/bluetooth/1.0/default/async_fd_watcher.cc
@@ -50,6 +50,20 @@
return 0;
}
+int AsyncFdWatcher::ConfigureTimeout(
+ const std::chrono::milliseconds timeout,
+ const TimeoutCallback& on_timeout_callback) {
+ // Add timeout and callback
+ {
+ std::unique_lock<std::mutex> guard(timeout_mutex_);
+ timeout_cb_ = on_timeout_callback;
+ timeout_ms_ = timeout;
+ }
+
+ notifyThread();
+ return 0;
+}
+
void AsyncFdWatcher::StopWatchingFileDescriptor() { stopThread(); }
AsyncFdWatcher::~AsyncFdWatcher() {}
@@ -86,6 +100,11 @@
read_fd_ = -1;
}
+ {
+ std::unique_lock<std::mutex> guard(timeout_mutex_);
+ timeout_cb_ = nullptr;
+ }
+
return 0;
}
@@ -104,21 +123,37 @@
FD_SET(notification_listen_fd_, &read_fds);
FD_SET(read_fd_, &read_fds);
- // Wait until there is data available to read on some FD
- int nfds = std::max(notification_listen_fd_, read_fd_);
- int retval = select(nfds + 1, &read_fds, NULL, NULL, NULL);
- if (retval <= 0) continue; // there was some error or a timeout
+ struct timeval timeout;
+ struct timeval* timeout_ptr = NULL;
+ if (timeout_ms_ > std::chrono::milliseconds(0)) {
+ timeout.tv_sec = timeout_ms_.count() / 1000;
+ timeout.tv_usec = (timeout_ms_.count() % 1000) * 1000;
+ timeout_ptr = &timeout;
+ }
- // Read data from the notification FD
+ // Wait until there is data available to read on some FD.
+ int nfds = std::max(notification_listen_fd_, read_fd_);
+ int retval = select(nfds + 1, &read_fds, NULL, NULL, timeout_ptr);
+
+ // There was some error.
+ if (retval < 0) continue;
+
+ // Timeout.
+ if (retval == 0) {
+ std::unique_lock<std::mutex> guard(timeout_mutex_);
+ if (timeout_ms_ > std::chrono::milliseconds(0) && timeout_cb_)
+ timeout_cb_();
+ continue;
+ }
+
+ // Read data from the notification FD.
if (FD_ISSET(notification_listen_fd_, &read_fds)) {
char buffer[] = {0};
TEMP_FAILURE_RETRY(read(notification_listen_fd_, buffer, 1));
+ continue;
}
- // Make sure we're still running
- if (!running_) break;
-
- // Invoke the data ready callback if appropriate
+ // Invoke the data ready callback if appropriate.
if (FD_ISSET(read_fd_, &read_fds)) {
std::unique_lock<std::mutex> guard(internal_mutex_);
if (cb_) cb_(read_fd_);
diff --git a/bluetooth/1.0/default/async_fd_watcher.h b/bluetooth/1.0/default/async_fd_watcher.h
index 1e4da8c..d6e112f 100644
--- a/bluetooth/1.0/default/async_fd_watcher.h
+++ b/bluetooth/1.0/default/async_fd_watcher.h
@@ -26,6 +26,7 @@
namespace implementation {
using ReadCallback = std::function<void(int)>;
+using TimeoutCallback = std::function<void(void)>;
class AsyncFdWatcher {
public:
@@ -34,6 +35,8 @@
int WatchFdForNonBlockingReads(int file_descriptor,
const ReadCallback& on_read_fd_ready_callback);
+ int ConfigureTimeout(const std::chrono::milliseconds timeout,
+ const TimeoutCallback& on_timeout_callback);
void StopWatchingFileDescriptor();
private:
@@ -48,11 +51,14 @@
std::atomic_bool running_{false};
std::thread thread_;
std::mutex internal_mutex_;
+ std::mutex timeout_mutex_;
int read_fd_;
int notification_listen_fd_;
int notification_write_fd_;
ReadCallback cb_;
+ TimeoutCallback timeout_cb_;
+ std::chrono::milliseconds timeout_ms_;
};
diff --git a/bluetooth/1.0/default/test/async_fd_watcher_unittest.cc b/bluetooth/1.0/default/test/async_fd_watcher_unittest.cc
new file mode 100644
index 0000000..c21acb8
--- /dev/null
+++ b/bluetooth/1.0/default/test/async_fd_watcher_unittest.cc
@@ -0,0 +1,295 @@
+//
+// Copyright 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "async_fd_watcher.h"
+#include <gtest/gtest.h>
+#include <cstdint>
+#include <cstring>
+#include <vector>
+
+#include <netdb.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <utils/Log.h>
+
+namespace android {
+namespace hardware {
+namespace bluetooth {
+namespace V1_0 {
+namespace implementation {
+
+class AsyncFdWatcherSocketTest : public ::testing::Test {
+ public:
+ static const uint16_t kPort = 6111;
+ static const size_t kBufferSize = 16;
+
+ bool CheckBufferEquals() {
+ return strcmp(server_buffer_, client_buffer_) == 0;
+ }
+
+ protected:
+ int StartServer() {
+ ALOGD("%s", __func__);
+ struct sockaddr_in serv_addr;
+ int fd = socket(AF_INET, SOCK_STREAM, 0);
+ EXPECT_FALSE(fd < 0);
+
+ memset(&serv_addr, 0, sizeof(serv_addr));
+ serv_addr.sin_family = AF_INET;
+ serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ serv_addr.sin_port = htons(kPort);
+ int reuse_flag = 1;
+ EXPECT_FALSE(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse_flag,
+ sizeof(reuse_flag)) < 0);
+ EXPECT_FALSE(bind(fd, (sockaddr*)&serv_addr, sizeof(serv_addr)) <
+ 0);
+
+ ALOGD("%s before listen", __func__);
+ listen(fd, 1);
+ return fd;
+ }
+
+ int AcceptConnection(int fd) {
+ ALOGD("%s", __func__);
+ struct sockaddr_in cli_addr;
+ memset(&cli_addr, 0, sizeof(cli_addr));
+ socklen_t clilen = sizeof(cli_addr);
+
+ int connection_fd = accept(fd, (struct sockaddr*)&cli_addr, &clilen);
+ EXPECT_FALSE(connection_fd < 0);
+
+ return connection_fd;
+ }
+
+ void ReadIncomingMessage(int fd) {
+ ALOGD("%s", __func__);
+ int n = TEMP_FAILURE_RETRY(read(fd, server_buffer_, kBufferSize - 1));
+ EXPECT_FALSE(n < 0);
+
+ if (n == 0) // got EOF
+ ALOGD("%s: EOF", __func__);
+ else
+ ALOGD("%s: Got something", __func__);
+ n = write(fd, "1", 1);
+ }
+
+ void SetUp() override {
+ ALOGD("%s", __func__);
+ memset(server_buffer_, 0, kBufferSize);
+ memset(client_buffer_, 0, kBufferSize);
+ }
+
+ void ConfigureServer() {
+ socket_fd_ = StartServer();
+
+ conn_watcher_.WatchFdForNonBlockingReads(socket_fd_, [this](int fd) {
+ int connection_fd = AcceptConnection(fd);
+ ALOGD("%s: Conn_watcher fd = %d", __func__, fd);
+
+ conn_watcher_.ConfigureTimeout(std::chrono::seconds(0), [this]() { bool connection_timeout_cleared = false; ASSERT_TRUE(connection_timeout_cleared); });
+
+ ALOGD("%s: 3", __func__);
+ async_fd_watcher_.WatchFdForNonBlockingReads(
+ connection_fd, [this](int fd) { ReadIncomingMessage(fd); });
+
+ // Time out if it takes longer than a second.
+ SetTimeout(std::chrono::seconds(1));
+ });
+ conn_watcher_.ConfigureTimeout(std::chrono::seconds(1), [this]() { bool connection_timeout = true; ASSERT_FALSE(connection_timeout); });
+ }
+
+ void CleanUpServer() {
+ async_fd_watcher_.StopWatchingFileDescriptor();
+ conn_watcher_.StopWatchingFileDescriptor();
+ close(socket_fd_);
+ }
+
+ void TearDown() override {
+ ALOGD("%s 3", __func__);
+ EXPECT_TRUE(CheckBufferEquals());
+ }
+
+ void OnTimeout() {
+ ALOGD("%s", __func__);
+ timed_out_ = true;
+ }
+
+ void ClearTimeout() {
+ ALOGD("%s", __func__);
+ timed_out_ = false;
+ }
+
+ bool TimedOut() {
+ ALOGD("%s %d", __func__, timed_out_? 1 : 0);
+ return timed_out_;
+ }
+
+ void SetTimeout(std::chrono::milliseconds timeout_ms) {
+ ALOGD("%s", __func__);
+ async_fd_watcher_.ConfigureTimeout(timeout_ms, [this]() { OnTimeout(); });
+ ClearTimeout();
+ }
+
+ int ConnectClient() {
+ ALOGD("%s", __func__);
+ int socket_cli_fd = socket(AF_INET, SOCK_STREAM, 0);
+ EXPECT_FALSE(socket_cli_fd < 0);
+
+ struct sockaddr_in serv_addr;
+ memset((void*)&serv_addr, 0, sizeof(serv_addr));
+ serv_addr.sin_family = AF_INET;
+ serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ serv_addr.sin_port = htons(kPort);
+
+ int result =
+ connect(socket_cli_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
+ EXPECT_FALSE(result < 0);
+
+ return socket_cli_fd;
+ }
+
+ void WriteFromClient(int socket_cli_fd) {
+ ALOGD("%s", __func__);
+ strcpy(client_buffer_, "1");
+ int n = write(socket_cli_fd, client_buffer_, strlen(client_buffer_));
+ EXPECT_TRUE(n > 0);
+ }
+
+ void AwaitServerResponse(int socket_cli_fd) {
+ ALOGD("%s", __func__);
+ int n = read(socket_cli_fd, client_buffer_, 1);
+ ALOGD("%s done", __func__);
+ EXPECT_TRUE(n > 0);
+ }
+
+ private:
+ AsyncFdWatcher async_fd_watcher_;
+ AsyncFdWatcher conn_watcher_;
+ int socket_fd_;
+ char server_buffer_[kBufferSize];
+ char client_buffer_[kBufferSize];
+ bool timed_out_;
+};
+
+// Use a single AsyncFdWatcher to signal a connection to the server socket.
+TEST_F(AsyncFdWatcherSocketTest, Connect) {
+ int socket_fd = StartServer();
+
+ AsyncFdWatcher conn_watcher;
+ conn_watcher.WatchFdForNonBlockingReads(socket_fd, [this](int fd) {
+ int connection_fd = AcceptConnection(fd);
+ close(connection_fd);
+ });
+
+ // Fail if the client doesn't connect within 1 second.
+ conn_watcher.ConfigureTimeout(std::chrono::seconds(1), [this]() {
+ bool connection_timeout = true;
+ ASSERT_FALSE(connection_timeout);
+ });
+
+ ConnectClient();
+ conn_watcher.StopWatchingFileDescriptor();
+ close(socket_fd);
+}
+
+// Use a single AsyncFdWatcher to signal a connection to the server socket.
+TEST_F(AsyncFdWatcherSocketTest, TimedOutConnect) {
+ int socket_fd = StartServer();
+ bool timed_out = false;
+ bool* timeout_ptr = &timed_out;
+
+ AsyncFdWatcher conn_watcher;
+ conn_watcher.WatchFdForNonBlockingReads(socket_fd, [this](int fd) {
+ int connection_fd = AcceptConnection(fd);
+ close(connection_fd);
+ });
+
+ // Set the timeout flag after 100ms.
+ conn_watcher.ConfigureTimeout(std::chrono::milliseconds(100), [this, timeout_ptr]() { *timeout_ptr = true; });
+ EXPECT_FALSE(timed_out);
+ sleep(1);
+ EXPECT_TRUE(timed_out);
+ conn_watcher.StopWatchingFileDescriptor();
+ close(socket_fd);
+}
+
+// Use two AsyncFdWatchers to set up a server socket.
+TEST_F(AsyncFdWatcherSocketTest, ClientServer) {
+ ConfigureServer();
+ int socket_cli_fd = ConnectClient();
+
+ WriteFromClient(socket_cli_fd);
+
+ AwaitServerResponse(socket_cli_fd);
+
+ close(socket_cli_fd);
+ CleanUpServer();
+}
+
+// Use two AsyncFdWatchers to set up a server socket, which times out.
+TEST_F(AsyncFdWatcherSocketTest, TimeOutTest) {
+ ConfigureServer();
+ int socket_cli_fd = ConnectClient();
+
+ while (!TimedOut()) sleep(1);
+
+ close(socket_cli_fd);
+ CleanUpServer();
+}
+
+// Use two AsyncFdWatchers to set up a server socket, which times out.
+TEST_F(AsyncFdWatcherSocketTest, RepeatedTimeOutTest) {
+ ConfigureServer();
+ int socket_cli_fd = ConnectClient();
+ ClearTimeout();
+
+ // Time out when there are no writes.
+ EXPECT_FALSE(TimedOut());
+ sleep(2);
+ EXPECT_TRUE(TimedOut());
+ ClearTimeout();
+
+ // Don't time out when there is a write.
+ WriteFromClient(socket_cli_fd);
+ AwaitServerResponse(socket_cli_fd);
+ EXPECT_FALSE(TimedOut());
+ ClearTimeout();
+
+ // Time out when the write is late.
+ sleep(2);
+ WriteFromClient(socket_cli_fd);
+ AwaitServerResponse(socket_cli_fd);
+ EXPECT_TRUE(TimedOut());
+ ClearTimeout();
+
+ // Time out when there is a pause after a write.
+ WriteFromClient(socket_cli_fd);
+ sleep(2);
+ AwaitServerResponse(socket_cli_fd);
+ EXPECT_TRUE(TimedOut());
+ ClearTimeout();
+
+ close(socket_cli_fd);
+ CleanUpServer();
+}
+
+} // namespace implementation
+} // namespace V1_0
+} // namespace bluetooth
+} // namespace hardware
+} // namespace android