libpdx_uds: Improve client connection logic

Handle the case where the service created a socket file but not bound the
socket to it, or not listening for incoming requests yet. Also, if the service
has crashed and left the socket file behind, need to make sure we reconnect
once the service is restarted.

Bug: None
Test: `m -j32` succeeds, device boots and CubeSea app works on Sailfish
Change-Id: I2039cfca6faccd5d1d4b725e454075669484b880
(cherry picked from commit d074fc416a7a90e76a0b28a870080c1dd30079a1)
diff --git a/libs/vr/libpdx_uds/client_channel_factory.cpp b/libs/vr/libpdx_uds/client_channel_factory.cpp
index 1879127..f059453 100644
--- a/libs/vr/libpdx_uds/client_channel_factory.cpp
+++ b/libs/vr/libpdx_uds/client_channel_factory.cpp
@@ -6,10 +6,16 @@
 #include <sys/un.h>
 #include <unistd.h>
 
+#include <chrono>
+#include <thread>
+
 #include <uds/channel_manager.h>
 #include <uds/client_channel.h>
 #include <uds/ipc_helper.h>
 
+using std::chrono::duration_cast;
+using std::chrono::steady_clock;
+
 namespace android {
 namespace pdx {
 namespace uds {
@@ -41,13 +47,11 @@
 
 Status<std::unique_ptr<pdx::ClientChannel>> ClientChannelFactory::Connect(
     int64_t timeout_ms) const {
-  auto status = WaitForEndpoint(endpoint_path_, timeout_ms);
-  if (!status)
-    return ErrorStatus(status.error());
+  Status<void> status;
 
   LocalHandle socket_fd{socket(AF_UNIX, SOCK_STREAM, 0)};
   if (!socket_fd) {
-    ALOGE("ClientChannelFactory::Connect: socket error %s", strerror(errno));
+    ALOGE("ClientChannelFactory::Connect: socket error: %s", strerror(errno));
     return ErrorStatus(errno);
   }
 
@@ -56,16 +60,55 @@
   strncpy(remote.sun_path, endpoint_path_.c_str(), sizeof(remote.sun_path));
   remote.sun_path[sizeof(remote.sun_path) - 1] = '\0';
 
-  int ret = RETRY_EINTR(connect(
-      socket_fd.Get(), reinterpret_cast<sockaddr*>(&remote), sizeof(remote)));
-  if (ret == -1) {
-    ALOGE(
-        "ClientChannelFactory::Connect: Failed to initialize connection when "
-        "connecting %s",
-        strerror(errno));
-    return ErrorStatus(errno);
-  }
+  bool use_timeout = (timeout_ms >= 0);
+  auto now = steady_clock::now();
+  auto time_end = now + std::chrono::milliseconds{timeout_ms};
 
+  bool connected = false;
+  while (!connected) {
+    int64_t timeout = -1;
+    if (use_timeout) {
+      auto remaining = time_end - now;
+      timeout = duration_cast<std::chrono::milliseconds>(remaining).count();
+      if (timeout < 0)
+        return ErrorStatus(ETIMEDOUT);
+    }
+    ALOGD("ClientChannelFactory: Waiting for endpoint at %s", remote.sun_path);
+    status = WaitForEndpoint(endpoint_path_, timeout);
+    if (!status)
+      return ErrorStatus(status.error());
+
+    ALOGD("ClientChannelFactory: Connecting to %s", remote.sun_path);
+    int ret = RETRY_EINTR(connect(
+        socket_fd.Get(), reinterpret_cast<sockaddr*>(&remote), sizeof(remote)));
+    if (ret == -1) {
+      ALOGD("ClientChannelFactory: Connect error %d: %s", errno,
+            strerror(errno));
+      if (errno == ECONNREFUSED) {
+        // Connection refused can be the result of connecting too early (the
+        // service socket is created but not being listened to yet).
+        ALOGD("ClientChannelFactory: Connection refused, waiting...");
+        using namespace std::literals::chrono_literals;
+        std::this_thread::sleep_for(100ms);
+      } else if (errno != ENOENT && errno != ENOTDIR) {
+        // ENOENT/ENOTDIR might mean that the socket file/directory containing
+        // it has been just deleted. Try to wait for its creation and do not
+        // return an error immediately.
+        ALOGE(
+            "ClientChannelFactory::Connect: Failed to initialize connection "
+            "when connecting: %s",
+            strerror(errno));
+        return ErrorStatus(errno);
+      }
+    } else {
+      connected = true;
+    }
+    if (use_timeout)
+      now = steady_clock::now();
+  }  // while (!connected)
+
+  ALOGD("ClientChannelFactory: Connected successfully to %s...",
+        remote.sun_path);
   RequestHeader<BorrowedHandle> request;
   InitRequest(&request, opcodes::CHANNEL_OPEN, 0, 0, false);
   status = SendData(socket_fd.Get(), request);