Add magma InflightList.

It won't be provided by Fuchsia SDK.

Change-Id: Ic0c2666051f2c61b809805308db5170a64058ef3
diff --git a/src/intel/vulkan/BUILD.gn b/src/intel/vulkan/BUILD.gn
index 509eb5a..768498a 100644
--- a/src/intel/vulkan/BUILD.gn
+++ b/src/intel/vulkan/BUILD.gn
@@ -83,7 +83,6 @@
     ":anv_extensions",
     ":gen",
     "$magma_build_root/include:magma_abi",
-    "$magma_build_root/src/magma_util:inflight_list",
     "$mesa_build_root/src:sha1",
     "$mesa_build_root/include:c_compat",
     "$mesa_build_root/src/compiler/nir",
diff --git a/src/intel/vulkan/anv_magma_connection.cc b/src/intel/vulkan/anv_magma_connection.cc
index 7cd4d03..3cd526a 100644
--- a/src/intel/vulkan/anv_magma_connection.cc
+++ b/src/intel/vulkan/anv_magma_connection.cc
@@ -24,7 +24,7 @@
 #include "anv_magma.h"
 #include "common/gen_gem.h"
 #include "magma_sysmem.h"
-#include "magma_util/inflight_list.h"
+#include "util/inflight_list.h"
 #include "common/intel_log.h"
 #include <assert.h>
 #include <chrono>
@@ -93,7 +93,7 @@
 
 class Connection : public anv_connection {
 public:
-   Connection(magma_connection_t magma_connection)
+   Connection(magma_connection_t magma_connection) : inflight_list_(InflightList_Create())
    {
       anv_connection::connection = magma_connection;
    }
@@ -106,11 +106,12 @@
       }
 #endif // VK_USE_PLATFORM_FUCHSIA
       magma_release_connection(magma_connection());
+      InflightList_Destroy(inflight_list_);
    }
 
    magma_connection_t magma_connection() { return anv_connection::connection; }
 
-   magma::InflightList* inflight_list() { return &inflight_list_; }
+   InflightList* inflight_list() { return inflight_list_; }
 
 #if VK_USE_PLATFORM_FUCHSIA
    magma_status_t GetSysmemConnection(magma_sysmem_connection_t* sysmem_connection_out)
@@ -137,7 +138,7 @@
 #if VK_USE_PLATFORM_FUCHSIA
    magma_sysmem_connection_t sysmem_connection_{};
 #endif // #if VK_USE_PLATFORM_FUCHSIA
-   magma::InflightList inflight_list_;
+   InflightList* inflight_list_;
 };
 
 anv_connection* AnvMagmaCreateConnection(magma_connection_t connection)
@@ -161,21 +162,21 @@
 magma_status_t AnvMagmaConnectionWait(anv_connection* connection, uint64_t buffer_id,
                                       int64_t* timeout_ns)
 {
-   magma::InflightList* inflight_list = Connection::cast(connection)->inflight_list();
+   InflightList* inflight_list = Connection::cast(connection)->inflight_list();
    auto start = std::chrono::high_resolution_clock::now();
 
-   while (inflight_list->is_inflight(buffer_id) &&
+   while (InflightList_is_inflight(inflight_list, buffer_id) &&
           std::chrono::duration_cast<std::chrono::nanoseconds>(
               std::chrono::high_resolution_clock::now() - start)
                   .count() < *timeout_ns) {
 
       magma_connection_t magma_connection = Connection::cast(connection)->magma_connection();
 
-      magma::Status status = inflight_list->WaitForCompletion(magma_connection, *timeout_ns);
-      if (status.ok()) {
-         inflight_list->ServiceCompletions(magma_connection);
+      magma_status_t status = InflightList_WaitForCompletion(inflight_list, magma_connection, *timeout_ns);
+      if (status == MAGMA_STATUS_OK) {
+         InflightList_ServiceCompletions(inflight_list, magma_connection);
       } else {
-         return status.get();
+         return status;
       }
    }
    return MAGMA_STATUS_OK;
@@ -183,11 +184,11 @@
 
 int AnvMagmaConnectionIsBusy(anv_connection* connection, uint64_t buffer_id)
 {
-   magma::InflightList* inflight_list = Connection::cast(connection)->inflight_list();
+   InflightList* inflight_list = Connection::cast(connection)->inflight_list();
 
-   inflight_list->ServiceCompletions(Connection::cast(connection)->magma_connection());
+   InflightList_ServiceCompletions(inflight_list, Connection::cast(connection)->magma_connection());
 
-   return inflight_list->is_inflight(buffer_id) ? 1 : 0;
+   return InflightList_is_inflight(inflight_list, buffer_id) ? 1 : 0;
 }
 
 int AnvMagmaConnectionExec(anv_connection* connection, uint32_t context_id,
@@ -279,13 +280,13 @@
       resources.data(),
       semaphore_ids.data());
 
-   magma::InflightList* inflight_list = Connection::cast(connection)->inflight_list();
+   InflightList* inflight_list = Connection::cast(connection)->inflight_list();
 
    for (uint32_t i = 0; i < execbuf->buffer_count; i++) {
-      inflight_list->add(resources[i].buffer_id);
+      InflightList_add(inflight_list, resources[i].buffer_id);
    }
 
-   inflight_list->ServiceCompletions(Connection::cast(connection)->magma_connection());
+   InflightList_ServiceCompletions(inflight_list, Connection::cast(connection)->magma_connection());
 
    return 0;
 }
diff --git a/src/util/BUILD.gn b/src/util/BUILD.gn
index 7cd09cb..89b2987 100644
--- a/src/util/BUILD.gn
+++ b/src/util/BUILD.gn
@@ -41,6 +41,7 @@
     "futex.h",
     "half_float.h",
     "hash_table.h",
+    "inflight_list.h",
     "list.h",
     "macros.h",
     "mesa-sha1.h",
@@ -75,6 +76,7 @@
     ":gen",
     ":headers",
     "$mesa_build_root/include:c_compat",
+    "$magma_build_root/include:magma_abi",
   ]
 
   public_configs = [ ":util_public_config" ]
@@ -86,6 +88,7 @@
     "fast_idiv_by_const.c",
     "half_float.c",
     "hash_table.c",
+    "inflight_list.c",
     "mesa-sha1.c",
     "os_file.c",
     "os_time.c",
diff --git a/src/util/inflight_list.c b/src/util/inflight_list.c
new file mode 100644
index 0000000..5cd816d
--- /dev/null
+++ b/src/util/inflight_list.c
@@ -0,0 +1,123 @@
+/*
+ * Copyright © 2019 Google, LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include "inflight_list.h"
+
+struct InflightList* InflightList_Create()
+{
+   struct InflightList* list = (struct InflightList*)malloc(sizeof(struct InflightList));
+   list->wait_ = magma_wait_notification_channel;
+   list->read_ = magma_read_notification_channel;
+   u_vector_init(&list->buffers_, sizeof(uint64_t), sizeof(uint64_t) * 8 /* initial byte size */);
+   list->size_ = 0;
+   return list;
+}
+
+void InflightList_Destroy(struct InflightList* list)
+{
+   u_vector_finish(&list->buffers_);
+   free(list);
+}
+
+void InflightList_add(struct InflightList* list, uint64_t buffer_id)
+{
+   assert(buffer_id != 0);
+   *(uint64_t*)u_vector_add(&list->buffers_) = buffer_id;
+   list->size_ += 1;
+}
+
+bool InflightList_remove(struct InflightList* list, uint64_t buffer_id)
+{
+   bool foundit = false;
+   void* element;
+
+   // Find the buffer_id, mark it for removal
+   u_vector_foreach(element, &list->buffers_)
+   {
+      if (*(uint64_t*)element == buffer_id) {
+         *(uint64_t*)element = 0;
+         foundit = true;
+         break;
+      }
+   }
+
+   if (!foundit)
+      return false; // Not found
+
+   assert(list->size_ > 0);
+   list->size_ -= 1;
+
+   // Remove all marked nodes at the tail
+   const int length = u_vector_length(&list->buffers_);
+
+   for (int i = 0; i < length; i++) {
+      element = u_vector_tail(&list->buffers_);
+      assert(element);
+      if (*(uint64_t*)element == 0) {
+         u_vector_remove(&list->buffers_);
+      } else {
+         break;
+      }
+   }
+
+   return true;
+}
+
+uint32_t InflightList_size(struct InflightList* list) { return list->size_; }
+
+bool InflightList_is_inflight(struct InflightList* list, uint64_t buffer_id)
+{
+   void* element;
+   u_vector_foreach(element, &list->buffers_)
+   {
+      if (*(uint64_t*)element == buffer_id)
+         return true;
+   }
+   return false;
+}
+
+magma_status_t InflightList_WaitForCompletion(struct InflightList* list,
+                                              magma_connection_t connection, int64_t timeout_ns)
+{
+   return list->wait_(connection, timeout_ns);
+}
+
+void InflightList_ServiceCompletions(struct InflightList* list, magma_connection_t connection)
+{
+   uint64_t buffer_ids[8];
+   uint64_t bytes_available = 0;
+   while (true) {
+      magma_status_t status =
+          list->read_(connection, buffer_ids, sizeof(buffer_ids), &bytes_available);
+      if (status != MAGMA_STATUS_OK) {
+         return;
+      }
+      if (bytes_available == 0)
+         return;
+      assert(bytes_available % sizeof(uint64_t) == 0);
+      for (uint32_t i = 0; i < bytes_available / sizeof(uint64_t); i++) {
+         assert(InflightList_is_inflight(list, buffer_ids[i]));
+         InflightList_remove(list, buffer_ids[i]);
+      }
+   }
+}
diff --git a/src/util/inflight_list.h b/src/util/inflight_list.h
new file mode 100644
index 0000000..2b5d55b
--- /dev/null
+++ b/src/util/inflight_list.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright © 2019 Google, LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef INFLIGHT_LIST_H
+#define INFLIGHT_LIST_H
+
+#include "u_vector.h"
+
+#include <assert.h>
+#include <magma.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+// A convenience utility for maintaining a list of inflight command buffers,
+// by reading completed buffer ids from the magma notification channel.
+// Not threadsafe.
+
+typedef magma_status_t (*wait_notification_channel_t)(magma_connection_t connection,
+                                                      int64_t timeout_ns);
+
+typedef magma_status_t (*read_notification_channel_t)(magma_connection_t connection, void* buffer,
+                                                      uint64_t buffer_size,
+                                                      uint64_t* buffer_size_out);
+
+struct InflightList {
+   wait_notification_channel_t wait_;
+   read_notification_channel_t read_;
+   struct u_vector buffers_;
+   uint32_t size_;
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct InflightList* InflightList_Create(void);
+
+void InflightList_Destroy(struct InflightList* list);
+
+static inline void InflightList_inject_for_test(struct InflightList* list,
+                                                wait_notification_channel_t wait,
+                                                read_notification_channel_t read)
+{
+   list->wait_ = wait;
+   list->read_ = read;
+}
+
+// Add to the end of the list.
+void InflightList_add(struct InflightList* list, uint64_t buffer_id);
+
+// Remove from anywhere in the list.
+bool InflightList_remove(struct InflightList* list, uint64_t buffer_id);
+
+uint32_t InflightList_size(struct InflightList* list);
+
+// Wait for a completion; returns MAGMA_STATUS_OK if a completion was
+// received before |timeout_ms|.
+magma_status_t InflightList_WaitForCompletion(struct InflightList* list,
+                                              magma_connection_t connection, int64_t timeout_ns);
+
+bool InflightList_is_inflight(struct InflightList* list, uint64_t buffer_id);
+
+// Read all outstanding completions and update the inflight list.
+void InflightList_ServiceCompletions(struct InflightList* list, magma_connection_t connection);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // INFLIGHT_LIST_H
diff --git a/src/util/tests/inflight_list/BUILD.gn b/src/util/tests/inflight_list/BUILD.gn
new file mode 100644
index 0000000..bc0540a
--- /dev/null
+++ b/src/util/tests/inflight_list/BUILD.gn
@@ -0,0 +1,42 @@
+# Copyright 2019 Google, LLC
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice (including the next
+# paragraph) shall be included in all copies or substantial portions of the
+# Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+import("../../../../mesa.gni")
+
+mesa_source_set("inflight_list") {
+  testonly = true
+
+  sources = [
+    "test_inflight_list.cpp",
+  ]
+
+  deps = [ "$mesa_build_root/src/util" ]
+
+  if (is_fuchsia) {
+    deps += [
+      "$mesa_build_root/src/os",
+      "//third_party/googletest:gtest",
+      "//zircon/public/lib/fdio",
+      "//zircon/public/lib/zx",
+      "//zircon/system/fidl/fuchsia-io",
+    ]
+  }
+}
diff --git a/src/util/tests/inflight_list/test_inflight_list.cpp b/src/util/tests/inflight_list/test_inflight_list.cpp
new file mode 100644
index 0000000..2d26fb7
--- /dev/null
+++ b/src/util/tests/inflight_list/test_inflight_list.cpp
@@ -0,0 +1,165 @@
+/*
+ * Copyright © 2019 Google, LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include <assert.h>
+#include <lib/zx/channel.h>
+
+#include "util/inflight_list.h"
+#include "gtest/gtest.h"
+
+struct TestConnection : public magma_connection {
+   TestConnection() { zx::channel::create(0, &channel[0], &channel[1]); }
+
+   zx::channel channel[2];
+};
+
+static magma_status_t wait_notification_channel(magma_connection_t connection, int64_t timeout_ns)
+{
+   zx_signals_t pending;
+   zx_status_t status =
+       static_cast<TestConnection*>(connection)
+           ->channel[0]
+           .wait_one(ZX_CHANNEL_READABLE, zx::deadline_after(zx::nsec(timeout_ns)), &pending);
+   if (status != ZX_OK)
+      return MAGMA_STATUS_INTERNAL_ERROR;
+   assert(pending & ZX_CHANNEL_READABLE);
+   return MAGMA_STATUS_OK;
+}
+
+static magma_status_t read_notification_channel(magma_connection_t connection, void* buffer,
+                                                uint64_t buffer_size, uint64_t* buffer_size_out)
+{
+   uint32_t buffer_actual_size;
+   zx_status_t status = static_cast<TestConnection*>(connection)
+                            ->channel[0]
+                            .read(0, buffer, nullptr, buffer_size, 0, &buffer_actual_size, nullptr);
+   if (status == ZX_OK) {
+      *buffer_size_out = buffer_actual_size;
+      return MAGMA_STATUS_OK;
+   }
+   return MAGMA_STATUS_INTERNAL_ERROR;
+}
+
+const uint64_t kBufferIdBase = 0xaabbccdd00000000;
+
+class TestInflightList : public ::testing::Test {
+public:
+   void SetUp() override { list_ = InflightList_Create(); }
+
+   void TearDown() override { InflightList_Destroy(list_); }
+
+protected:
+   InflightList* list_;
+};
+
+TEST_F(TestInflightList, RemoveWhenEmpty)
+{
+   EXPECT_FALSE(InflightList_remove(list_, kBufferIdBase));
+   EXPECT_EQ(0u, InflightList_size(list_));
+}
+
+TEST_F(TestInflightList, RemoveFromTail)
+{
+   InflightList_add(list_, kBufferIdBase + 1);
+   EXPECT_TRUE(InflightList_is_inflight(list_, kBufferIdBase + 1));
+   InflightList_add(list_, kBufferIdBase + 2);
+   EXPECT_TRUE(InflightList_is_inflight(list_, kBufferIdBase + 2));
+   EXPECT_EQ(2u, InflightList_size(list_));
+
+   EXPECT_TRUE(InflightList_remove(list_, kBufferIdBase + 1));
+   EXPECT_FALSE(InflightList_is_inflight(list_, kBufferIdBase + 1));
+   EXPECT_EQ(1u, InflightList_size(list_));
+   EXPECT_TRUE(InflightList_remove(list_, kBufferIdBase + 2));
+   EXPECT_FALSE(InflightList_is_inflight(list_, kBufferIdBase + 2));
+   EXPECT_EQ(0u, InflightList_size(list_));
+   EXPECT_EQ(0u, u_vector_length(&list_->buffers_));
+}
+
+TEST_F(TestInflightList, RemoveFromHead)
+{
+   InflightList_add(list_, kBufferIdBase + 1);
+   EXPECT_TRUE(InflightList_is_inflight(list_, kBufferIdBase + 1));
+   InflightList_add(list_, kBufferIdBase + 2);
+   EXPECT_TRUE(InflightList_is_inflight(list_, kBufferIdBase + 2));
+   EXPECT_EQ(2u, InflightList_size(list_));
+
+   EXPECT_TRUE(InflightList_remove(list_, kBufferIdBase + 2));
+   EXPECT_FALSE(InflightList_is_inflight(list_, kBufferIdBase + 2));
+   EXPECT_EQ(1u, InflightList_size(list_));
+   EXPECT_EQ(2u, u_vector_length(&list_->buffers_)); // Entry was only marked for deletion
+   EXPECT_TRUE(InflightList_remove(list_, kBufferIdBase + 1));
+   EXPECT_FALSE(InflightList_is_inflight(list_, kBufferIdBase + 1));
+   EXPECT_EQ(0u, InflightList_size(list_));
+   EXPECT_EQ(0u, u_vector_length(&list_->buffers_));
+}
+
+TEST_F(TestInflightList, RemoveMiddle)
+{
+   InflightList_add(list_, kBufferIdBase + 1);
+   EXPECT_TRUE(InflightList_is_inflight(list_, kBufferIdBase + 1));
+   InflightList_add(list_, kBufferIdBase + 2);
+   EXPECT_TRUE(InflightList_is_inflight(list_, kBufferIdBase + 2));
+   InflightList_add(list_, kBufferIdBase + 3);
+   EXPECT_TRUE(InflightList_is_inflight(list_, kBufferIdBase + 3));
+   EXPECT_EQ(3u, InflightList_size(list_));
+
+   EXPECT_TRUE(InflightList_remove(list_, kBufferIdBase + 2));
+   EXPECT_FALSE(InflightList_is_inflight(list_, kBufferIdBase + 2));
+   EXPECT_EQ(2u, InflightList_size(list_));
+   EXPECT_EQ(3u, u_vector_length(&list_->buffers_)); // Entry was only marked for deletion
+}
+
+TEST_F(TestInflightList, RemoveDouble)
+{
+   InflightList_add(list_, kBufferIdBase);
+   EXPECT_TRUE(InflightList_is_inflight(list_, kBufferIdBase));
+   InflightList_add(list_, kBufferIdBase);
+   EXPECT_TRUE(InflightList_is_inflight(list_, kBufferIdBase));
+   EXPECT_EQ(2u, InflightList_size(list_));
+
+   EXPECT_TRUE(InflightList_remove(list_, kBufferIdBase));
+   EXPECT_TRUE(InflightList_is_inflight(list_, kBufferIdBase));
+   EXPECT_EQ(1u, InflightList_size(list_));
+
+   EXPECT_TRUE(InflightList_remove(list_, kBufferIdBase));
+   EXPECT_FALSE(InflightList_is_inflight(list_, kBufferIdBase));
+   EXPECT_EQ(0u, InflightList_size(list_));
+}
+
+TEST_F(TestInflightList, WaitAndService)
+{
+   InflightList_inject_for_test(list_, wait_notification_channel, read_notification_channel);
+
+   uint64_t buffer_id = 0xabab1234;
+   EXPECT_FALSE(InflightList_is_inflight(list_, buffer_id));
+   InflightList_add(list_, buffer_id);
+   EXPECT_TRUE(InflightList_is_inflight(list_, buffer_id));
+
+   TestConnection connection;
+   EXPECT_NE(MAGMA_STATUS_OK, InflightList_WaitForCompletion(list_, &connection, 100));
+   connection.channel[1].write(0, &buffer_id, sizeof(buffer_id), nullptr, 0);
+   EXPECT_EQ(MAGMA_STATUS_OK, InflightList_WaitForCompletion(list_, &connection, 100));
+
+   InflightList_ServiceCompletions(list_, &connection);
+   EXPECT_FALSE(InflightList_is_inflight(list_, buffer_id));
+}
diff --git a/tests/BUILD.gn b/tests/BUILD.gn
index 33cb32f..7261314 100644
--- a/tests/BUILD.gn
+++ b/tests/BUILD.gn
@@ -39,6 +39,7 @@
   deps = [
     "unit_tests",
     "$mesa_build_root/src/util/tests/os_dirent",
+    "$mesa_build_root/src/util/tests/inflight_list",
     "//third_party/googletest:gtest",
   ]
 }