Ensure all wire child objects are destroyed before their device

Destroying a device will implicit destroy all its child objects.
Attempting to use a child object after results in a fatal error.

Bug: dawn:384
Change-Id: I43c27c92cacde759be83cca79ac890f41bac3927
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/37002
Commit-Queue: Austin Eng <enga@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Jiawei Shao <jiawei.shao@intel.com>
diff --git a/dawn_wire.json b/dawn_wire.json
index 505af37..4d1df0f 100644
--- a/dawn_wire.json
+++ b/dawn_wire.json
@@ -33,20 +33,20 @@
             { "name": "write flush info", "type": "uint8_t", "annotation": "const*", "length": "write flush info length", "skip_serialize": true}
         ],
         "device create buffer": [
-            { "name": "device", "type": "device" },
+            { "name": "device id", "type": "ObjectId" },
             { "name": "descriptor", "type": "buffer descriptor", "annotation": "const*" },
             { "name": "result", "type": "ObjectHandle", "handle_type": "buffer" },
             { "name": "handle create info length", "type": "uint64_t" },
             { "name": "handle create info", "type": "uint8_t", "annotation": "const*", "length": "handle create info length", "skip_serialize": true}
         ],
         "device create ready compute pipeline": [
-            { "name": "device", "type": "device" },
+            { "name": "device id", "type": "ObjectId" },
             { "name": "request serial", "type": "uint64_t" },
             { "name": "pipeline object handle", "type": "ObjectHandle", "handle_type": "compute pipeline"},
             { "name": "descriptor", "type": "compute pipeline descriptor", "annotation": "const*"}
         ],
         "device create ready render pipeline": [
-            { "name": "device", "type": "device" },
+            { "name": "device id", "type": "ObjectId" },
             { "name": "request serial", "type": "uint64_t" },
             { "name": "pipeline object handle", "type": "ObjectHandle", "handle_type": "render pipeline"},
             { "name": "descriptor", "type": "render pipeline descriptor", "annotation": "const*"}
diff --git a/generator/templates/dawn_wire/server/ServerBase.h b/generator/templates/dawn_wire/server/ServerBase.h
index 4c488ee..66193a4 100644
--- a/generator/templates/dawn_wire/server/ServerBase.h
+++ b/generator/templates/dawn_wire/server/ServerBase.h
@@ -32,7 +32,7 @@
       protected:
         void DestroyAllObjects(const DawnProcTable& procs) {
             //* Free all objects when the server is destroyed
-            {% for type in by_category["object"] %}
+            {% for type in by_category["object"] if type.name.get() != "device" %}
                 {
                     std::vector<{{as_cType(type.name)}}> handles = mKnown{{type.name.CamelCase()}}.AcquireAllHandles();
                     for ({{as_cType(type.name)}} handle : handles) {
@@ -40,6 +40,13 @@
                     }
                 }
             {% endfor %}
+            //* Release devices last because dawn_native requires this.
+            {
+                std::vector<WGPUDevice> handles = mKnownDevice.AcquireAllHandles();
+                for (WGPUDevice handle : handles) {
+                    procs.deviceRelease(handle);
+                }
+            }
         }
 
         {% for type in by_category["object"] %}
diff --git a/generator/templates/dawn_wire/server/ServerDoers.cpp b/generator/templates/dawn_wire/server/ServerDoers.cpp
index 08b407a..0c6ce42 100644
--- a/generator/templates/dawn_wire/server/ServerDoers.cpp
+++ b/generator/templates/dawn_wire/server/ServerDoers.cpp
@@ -77,9 +77,28 @@
                     if (data == nullptr) {
                         return false;
                     }
+                    if (data->device != nullptr) {
+                        auto* device = static_cast<ObjectData<WGPUDevice>*>(data->device);
+                        if (!UntrackDeviceChild(device, objectType, objectId)) {
+                            return false;
+                        }
+                    }
                     {% if type.name.CamelCase() in server_reverse_lookup_objects %}
                         {{type.name.CamelCase()}}ObjectIdTable().Remove(data->handle);
                     {% endif %}
+                    {% if type.name.get() == "device" %}
+                        //* TODO(crbug.com/dawn/384): This is a hack to make sure that all child objects
+                        //* are destroyed before their device. We should have a solution in
+                        //* Dawn native that makes all child objects internally null if their
+                        //* Device is destroyed.
+                        while (data->childObjectTypesAndIds.size() > 0) {
+                            ObjectType childObjectType;
+                            ObjectId childObjectId;
+                            std::tie(childObjectType, childObjectId) = UnpackObjectTypeAndId(
+                                *data->childObjectTypesAndIds.begin());
+                            DoDestroyObject(childObjectType, childObjectId);
+                        }
+                    {% endif %}
                     if (data->handle != nullptr) {
                         mProcs.{{as_varName(type.name, Name("release"))}}(data->handle);
                     }
diff --git a/generator/templates/dawn_wire/server/ServerHandlers.cpp b/generator/templates/dawn_wire/server/ServerHandlers.cpp
index 22bbc8c..1341e41 100644
--- a/generator/templates/dawn_wire/server/ServerHandlers.cpp
+++ b/generator/templates/dawn_wire/server/ServerHandlers.cpp
@@ -17,7 +17,6 @@
 
 namespace dawn_wire { namespace server {
     {% for command in cmd_records["command"] %}
-        {% set type = command.derived_object %}
         {% set method = command.derived_method %}
         {% set is_method = method != None %}
         {% set returns = is_method and method.return_type.name.canonical_case() != "void" %}
@@ -53,6 +52,24 @@
                     return false;
                 }
                 {{name}}Data->generation = cmd.{{name}}.generation;
+
+                //* TODO(crbug.com/dawn/384): This is a hack to make sure that all child objects
+                //* are destroyed before their device. The dawn_native device needs to track all child objects so
+                //* it can destroy them if the device is destroyed first.
+                {% if command.derived_object %}
+                    {% set type = command.derived_object %}
+                    {% if type.name.get() == "device" %}
+                        {{name}}Data->device = DeviceObjects().Get(cmd.selfId);
+                    {% else %}
+                        auto* selfData = {{type.name.CamelCase()}}Objects().Get(cmd.selfId);
+                        {{name}}Data->device = selfData->device;
+                    {% endif %}
+                    if ({{name}}Data->device != nullptr) {
+                        if (!TrackDeviceChild({{name}}Data->device, ObjectType::{{Type}}, cmd.{{name}}.id)) {
+                            return false;
+                        }
+                    }
+                {% endif %}
             {% endfor %}
 
             //* Do command
diff --git a/src/dawn_wire/WireServer.cpp b/src/dawn_wire/WireServer.cpp
index 0056d5b..723f691 100644
--- a/src/dawn_wire/WireServer.cpp
+++ b/src/dawn_wire/WireServer.cpp
@@ -32,8 +32,12 @@
         return mImpl->HandleCommands(commands, size);
     }
 
-    bool WireServer::InjectTexture(WGPUTexture texture, uint32_t id, uint32_t generation) {
-        return mImpl->InjectTexture(texture, id, generation);
+    bool WireServer::InjectTexture(WGPUTexture texture,
+                                   uint32_t id,
+                                   uint32_t generation,
+                                   uint32_t deviceId,
+                                   uint32_t deviceGeneration) {
+        return mImpl->InjectTexture(texture, id, generation, deviceId, deviceGeneration);
     }
 
     namespace server {
diff --git a/src/dawn_wire/client/Buffer.cpp b/src/dawn_wire/client/Buffer.cpp
index 2244ace..40b5759 100644
--- a/src/dawn_wire/client/Buffer.cpp
+++ b/src/dawn_wire/client/Buffer.cpp
@@ -66,7 +66,7 @@
         buffer->mSize = descriptor->size;
 
         DeviceCreateBufferCmd cmd;
-        cmd.device = ToAPI(device);
+        cmd.deviceId = device->id;
         cmd.descriptor = descriptor;
         cmd.result = ObjectHandle{buffer->id, bufferObjectAndSerial->generation};
         cmd.handleCreateInfoLength = writeHandleCreateInfoLength;
diff --git a/src/dawn_wire/client/Client.cpp b/src/dawn_wire/client/Client.cpp
index 51af5a6..0ca5f61 100644
--- a/src/dawn_wire/client/Client.cpp
+++ b/src/dawn_wire/client/Client.cpp
@@ -59,6 +59,9 @@
     void Client::DestroyAllObjects() {
         for (auto& objectList : mObjects) {
             ObjectType objectType = static_cast<ObjectType>(&objectList - mObjects.data());
+            if (objectType == ObjectType::Device) {
+                continue;
+            }
             while (!objectList.empty()) {
                 ObjectBase* object = objectList.head()->value();
 
@@ -69,6 +72,16 @@
                 FreeObject(objectType, object);
             }
         }
+
+        while (!mObjects[ObjectType::Device].empty()) {
+            ObjectBase* object = mObjects[ObjectType::Device].head()->value();
+
+            DestroyObjectCmd cmd;
+            cmd.objectType = ObjectType::Device;
+            cmd.objectId = object->id;
+            SerializeCommand(cmd);
+            FreeObject(ObjectType::Device, object);
+        }
     }
 
     WGPUDevice Client::GetDevice() {
@@ -85,6 +98,8 @@
         result.texture = ToAPI(allocation->object.get());
         result.id = allocation->object->id;
         result.generation = allocation->generation;
+        result.deviceId = FromAPI(device)->id;
+        result.deviceGeneration = DeviceAllocator().GetGeneration(FromAPI(device)->id);
         return result;
     }
 
diff --git a/src/dawn_wire/client/Device.cpp b/src/dawn_wire/client/Device.cpp
index af4cbfb..08a3e33 100644
--- a/src/dawn_wire/client/Device.cpp
+++ b/src/dawn_wire/client/Device.cpp
@@ -215,7 +215,7 @@
         }
 
         DeviceCreateReadyComputePipelineCmd cmd;
-        cmd.device = ToAPI(this);
+        cmd.deviceId = this->id;
         cmd.descriptor = descriptor;
 
         uint64_t serial = mCreateReadyPipelineRequestSerial++;
@@ -270,7 +270,7 @@
                             "GPU device disconnected", userdata);
         }
         DeviceCreateReadyRenderPipelineCmd cmd;
-        cmd.device = ToAPI(this);
+        cmd.deviceId = this->id;
         cmd.descriptor = descriptor;
 
         uint64_t serial = mCreateReadyPipelineRequestSerial++;
diff --git a/src/dawn_wire/server/ObjectStorage.h b/src/dawn_wire/server/ObjectStorage.h
index 9963158..74cc5a7 100644
--- a/src/dawn_wire/server/ObjectStorage.h
+++ b/src/dawn_wire/server/ObjectStorage.h
@@ -20,6 +20,7 @@
 
 #include <algorithm>
 #include <map>
+#include <unordered_set>
 
 namespace dawn_wire { namespace server {
 
@@ -32,6 +33,8 @@
         // Whether this object has been allocated, used by the KnownObjects queries
         // TODO(cwallez@chromium.org): make this an internal bit vector in KnownObjects.
         bool allocated;
+
+        ObjectDataBase<WGPUDevice>* device = nullptr;
     };
 
     // Stores what the backend knows about the type.
@@ -48,6 +51,26 @@
         BufferMapWriteState mapWriteState = BufferMapWriteState::Unmapped;
     };
 
+    // Pack the ObjectType and ObjectId as a single value for storage in
+    // an std::unordered_set. This lets us avoid providing our own hash and
+    // equality comparison operators.
+    inline uint64_t PackObjectTypeAndId(ObjectType type, ObjectId id) {
+        static_assert(sizeof(ObjectType) * 8 <= 32, "");
+        static_assert(sizeof(ObjectId) * 8 <= 32, "");
+        return (static_cast<uint64_t>(type) << 32) + id;
+    }
+
+    inline std::pair<ObjectType, ObjectId> UnpackObjectTypeAndId(uint64_t payload) {
+        ObjectType type = static_cast<ObjectType>(payload >> 32);
+        ObjectId id = payload & 0xFFFFFFFF;
+        return std::make_pair(type, id);
+    }
+
+    template <>
+    struct ObjectData<WGPUDevice> : public ObjectDataBase<WGPUDevice> {
+        std::unordered_set<uint64_t> childObjectTypesAndIds;
+    };
+
     // Keeps track of the mapping between client IDs and backend objects.
     template <typename T>
     class KnownObjects {
diff --git a/src/dawn_wire/server/Server.cpp b/src/dawn_wire/server/Server.cpp
index bb5644b..67a9dd5 100644
--- a/src/dawn_wire/server/Server.cpp
+++ b/src/dawn_wire/server/Server.cpp
@@ -66,15 +66,29 @@
         DestroyAllObjects(mProcs);
     }
 
-    bool Server::InjectTexture(WGPUTexture texture, uint32_t id, uint32_t generation) {
+    bool Server::InjectTexture(WGPUTexture texture,
+                               uint32_t id,
+                               uint32_t generation,
+                               uint32_t deviceId,
+                               uint32_t deviceGeneration) {
+        ObjectData<WGPUDevice>* device = DeviceObjects().Get(deviceId);
+        if (device == nullptr || device->generation != deviceGeneration) {
+            return false;
+        }
+
         ObjectData<WGPUTexture>* data = TextureObjects().Allocate(id);
         if (data == nullptr) {
             return false;
         }
 
+        if (!TrackDeviceChild(device, ObjectType::Texture, id)) {
+            return false;
+        }
+
         data->handle = texture;
         data->generation = generation;
         data->allocated = true;
+        data->device = device;
 
         // The texture is externally owned so it shouldn't be destroyed when we receive a destroy
         // message from the client. Add a reference to counterbalance the eventual release.
@@ -83,4 +97,25 @@
         return true;
     }
 
+    bool TrackDeviceChild(ObjectDataBase<WGPUDevice>* device, ObjectType type, ObjectId id) {
+        auto it = static_cast<ObjectData<WGPUDevice>*>(device)->childObjectTypesAndIds.insert(
+            PackObjectTypeAndId(type, id));
+        if (!it.second) {
+            // An object of this type and id already exists.
+            return false;
+        }
+        return true;
+    }
+
+    bool UntrackDeviceChild(ObjectDataBase<WGPUDevice>* device, ObjectType type, ObjectId id) {
+        auto& children = static_cast<ObjectData<WGPUDevice>*>(device)->childObjectTypesAndIds;
+        auto it = children.find(PackObjectTypeAndId(type, id));
+        if (it == children.end()) {
+            // An object of this type and id was already deleted.
+            return false;
+        }
+        children.erase(it);
+        return true;
+    }
+
 }}  // namespace dawn_wire::server
diff --git a/src/dawn_wire/server/Server.h b/src/dawn_wire/server/Server.h
index 867f29b..f45ed0d 100644
--- a/src/dawn_wire/server/Server.h
+++ b/src/dawn_wire/server/Server.h
@@ -161,7 +161,11 @@
         const volatile char* HandleCommandsImpl(const volatile char* commands,
                                                 size_t size) override;
 
-        bool InjectTexture(WGPUTexture texture, uint32_t id, uint32_t generation);
+        bool InjectTexture(WGPUTexture texture,
+                           uint32_t id,
+                           uint32_t generation,
+                           uint32_t deviceId,
+                           uint32_t deviceGeneration);
 
         template <typename T,
                   typename Enable = std::enable_if<std::is_base_of<CallbackUserdata, T>::value>>
@@ -215,6 +219,9 @@
         std::shared_ptr<bool> mIsAlive;
     };
 
+    bool TrackDeviceChild(ObjectDataBase<WGPUDevice>* device, ObjectType type, ObjectId id);
+    bool UntrackDeviceChild(ObjectDataBase<WGPUDevice>* device, ObjectType type, ObjectId id);
+
     std::unique_ptr<MemoryTransferService> CreateInlineMemoryTransferService();
 
 }}  // namespace dawn_wire::server
diff --git a/src/dawn_wire/server/ServerBuffer.cpp b/src/dawn_wire/server/ServerBuffer.cpp
index e842bb1..fdc850b 100644
--- a/src/dawn_wire/server/ServerBuffer.cpp
+++ b/src/dawn_wire/server/ServerBuffer.cpp
@@ -120,18 +120,27 @@
         return true;
     }
 
-    bool Server::DoDeviceCreateBuffer(WGPUDevice device,
+    bool Server::DoDeviceCreateBuffer(ObjectId deviceId,
                                       const WGPUBufferDescriptor* descriptor,
                                       ObjectHandle bufferResult,
                                       uint64_t handleCreateInfoLength,
                                       const uint8_t* handleCreateInfo) {
+        auto* device = DeviceObjects().Get(deviceId);
+        if (device == nullptr) {
+            return false;
+        }
+
         // Create and register the buffer object.
         auto* resultData = BufferObjects().Allocate(bufferResult.id);
         if (resultData == nullptr) {
             return false;
         }
         resultData->generation = bufferResult.generation;
-        resultData->handle = mProcs.deviceCreateBuffer(device, descriptor);
+        resultData->handle = mProcs.deviceCreateBuffer(device->handle, descriptor);
+        resultData->device = device;
+        if (!TrackDeviceChild(device, ObjectType::Buffer, bufferResult.id)) {
+            return false;
+        }
 
         // If the buffer isn't mapped at creation, we are done.
         if (!descriptor->mappedAtCreation) {
diff --git a/src/dawn_wire/server/ServerDevice.cpp b/src/dawn_wire/server/ServerDevice.cpp
index cd2eba7..8884dd1 100644
--- a/src/dawn_wire/server/ServerDevice.cpp
+++ b/src/dawn_wire/server/ServerDevice.cpp
@@ -59,23 +59,32 @@
     }
 
     bool Server::DoDeviceCreateReadyComputePipeline(
-        WGPUDevice cDevice,
+        ObjectId deviceId,
         uint64_t requestSerial,
         ObjectHandle pipelineObjectHandle,
         const WGPUComputePipelineDescriptor* descriptor) {
+        auto* device = DeviceObjects().Get(deviceId);
+        if (device == nullptr) {
+            return false;
+        }
+
         auto* resultData = ComputePipelineObjects().Allocate(pipelineObjectHandle.id);
         if (resultData == nullptr) {
             return false;
         }
 
         resultData->generation = pipelineObjectHandle.generation;
+        resultData->device = device;
+        if (!TrackDeviceChild(device, ObjectType::ComputePipeline, pipelineObjectHandle.id)) {
+            return false;
+        }
 
         auto userdata = MakeUserdata<CreateReadyPipelineUserData>();
         userdata->requestSerial = requestSerial;
         userdata->pipelineObjectID = pipelineObjectHandle.id;
 
         mProcs.deviceCreateReadyComputePipeline(
-            cDevice, descriptor,
+            device->handle, descriptor,
             ForwardToServer<decltype(&Server::OnCreateReadyComputePipelineCallback)>::Func<
                 &Server::OnCreateReadyComputePipelineCallback>(),
             userdata.release());
@@ -116,23 +125,32 @@
         SerializeCommand(cmd);
     }
 
-    bool Server::DoDeviceCreateReadyRenderPipeline(WGPUDevice cDevice,
+    bool Server::DoDeviceCreateReadyRenderPipeline(ObjectId deviceId,
                                                    uint64_t requestSerial,
                                                    ObjectHandle pipelineObjectHandle,
                                                    const WGPURenderPipelineDescriptor* descriptor) {
+        auto* device = DeviceObjects().Get(deviceId);
+        if (device == nullptr) {
+            return false;
+        }
+
         auto* resultData = RenderPipelineObjects().Allocate(pipelineObjectHandle.id);
         if (resultData == nullptr) {
             return false;
         }
 
         resultData->generation = pipelineObjectHandle.generation;
+        resultData->device = device;
+        if (!TrackDeviceChild(device, ObjectType::RenderPipeline, pipelineObjectHandle.id)) {
+            return false;
+        }
 
         auto userdata = MakeUserdata<CreateReadyPipelineUserData>();
         userdata->requestSerial = requestSerial;
         userdata->pipelineObjectID = pipelineObjectHandle.id;
 
         mProcs.deviceCreateReadyRenderPipeline(
-            cDevice, descriptor,
+            device->handle, descriptor,
             ForwardToServer<decltype(&Server::OnCreateReadyRenderPipelineCallback)>::Func<
                 &Server::OnCreateReadyRenderPipelineCallback>(),
             userdata.release());
diff --git a/src/include/dawn_wire/WireClient.h b/src/include/dawn_wire/WireClient.h
index 1a22fe5..8af02a97 100644
--- a/src/include/dawn_wire/WireClient.h
+++ b/src/include/dawn_wire/WireClient.h
@@ -34,6 +34,8 @@
         WGPUTexture texture;
         uint32_t id;
         uint32_t generation;
+        uint32_t deviceId;
+        uint32_t deviceGeneration;
     };
 
     struct DAWN_WIRE_EXPORT WireClientDescriptor {
diff --git a/src/include/dawn_wire/WireServer.h b/src/include/dawn_wire/WireServer.h
index 7627ab8..ad36f44 100644
--- a/src/include/dawn_wire/WireServer.h
+++ b/src/include/dawn_wire/WireServer.h
@@ -43,7 +43,12 @@
         const volatile char* HandleCommands(const volatile char* commands,
                                             size_t size) override final;
 
-        bool InjectTexture(WGPUTexture texture, uint32_t id, uint32_t generation);
+        // TODO(enga): Remove defaults after updating Chrome.
+        bool InjectTexture(WGPUTexture texture,
+                           uint32_t id,
+                           uint32_t generation,
+                           uint32_t deviceId = 1,
+                           uint32_t deviceGeneration = 0);
 
       private:
         std::unique_ptr<server::Server> mImpl;
diff --git a/src/tests/BUILD.gn b/src/tests/BUILD.gn
index 8fec31f..1c78506 100644
--- a/src/tests/BUILD.gn
+++ b/src/tests/BUILD.gn
@@ -218,6 +218,7 @@
     "unittests/wire/WireBasicTests.cpp",
     "unittests/wire/WireBufferMappingTests.cpp",
     "unittests/wire/WireCreateReadyPipelineTests.cpp",
+    "unittests/wire/WireDestroyObjectTests.cpp",
     "unittests/wire/WireDisconnectTests.cpp",
     "unittests/wire/WireErrorCallbackTests.cpp",
     "unittests/wire/WireExtensionTests.cpp",
diff --git a/src/tests/unittests/wire/WireDestroyObjectTests.cpp b/src/tests/unittests/wire/WireDestroyObjectTests.cpp
new file mode 100644
index 0000000..f5e16b7
--- /dev/null
+++ b/src/tests/unittests/wire/WireDestroyObjectTests.cpp
@@ -0,0 +1,45 @@
+// Copyright 2021 The Dawn Authors
+//
+// 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 "tests/unittests/wire/WireTest.h"
+
+using namespace testing;
+using namespace dawn_wire;
+
+class WireDestroyObjectTests : public WireTest {};
+
+// Test that destroying the device also destroys child objects.
+TEST_F(WireDestroyObjectTests, DestroyDeviceDestroysChildren) {
+    WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(device, nullptr);
+
+    WGPUCommandEncoder apiEncoder = api.GetNewCommandEncoder();
+    EXPECT_CALL(api, DeviceCreateCommandEncoder(apiDevice, nullptr)).WillOnce(Return(apiEncoder));
+
+    FlushClient();
+
+    // Release the device. It should cause the command encoder to be destroyed.
+    wgpuDeviceRelease(device);
+
+    Sequence s1, s2;
+    // The device and child objects should be released.
+    EXPECT_CALL(api, CommandEncoderRelease(apiEncoder)).InSequence(s1);
+    EXPECT_CALL(api, QueueRelease(apiQueue)).InSequence(s2);
+    EXPECT_CALL(api, DeviceRelease(apiDevice)).InSequence(s1, s2);
+
+    FlushClient();
+
+    // Using the command encoder should be an error.
+    wgpuCommandEncoderFinish(encoder, nullptr);
+    FlushClient(false);
+}
diff --git a/src/tests/unittests/wire/WireDisconnectTests.cpp b/src/tests/unittests/wire/WireDisconnectTests.cpp
index 4c99ced..f44df13 100644
--- a/src/tests/unittests/wire/WireDisconnectTests.cpp
+++ b/src/tests/unittests/wire/WireDisconnectTests.cpp
@@ -145,9 +145,10 @@
     DeleteClient();
 
     // Expect release on all objects created by the client.
-    EXPECT_CALL(api, DeviceRelease(apiDevice)).Times(1);
-    EXPECT_CALL(api, QueueRelease(apiQueue)).Times(1);
-    EXPECT_CALL(api, CommandEncoderRelease(apiCommandEncoder)).Times(1);
-    EXPECT_CALL(api, SamplerRelease(apiSampler)).Times(1);
+    Sequence s1, s2, s3;
+    EXPECT_CALL(api, QueueRelease(apiQueue)).Times(1).InSequence(s1);
+    EXPECT_CALL(api, CommandEncoderRelease(apiCommandEncoder)).Times(1).InSequence(s2);
+    EXPECT_CALL(api, SamplerRelease(apiSampler)).Times(1).InSequence(s3);
+    EXPECT_CALL(api, DeviceRelease(apiDevice)).Times(1).InSequence(s1, s2, s3);
     FlushClient();
 }
diff --git a/src/tests/unittests/wire/WireInjectTextureTests.cpp b/src/tests/unittests/wire/WireInjectTextureTests.cpp
index 9e327cf..c6a1f2c 100644
--- a/src/tests/unittests/wire/WireInjectTextureTests.cpp
+++ b/src/tests/unittests/wire/WireInjectTextureTests.cpp
@@ -34,7 +34,8 @@
 
     WGPUTexture apiTexture = api.GetNewTexture();
     EXPECT_CALL(api, TextureReference(apiTexture));
-    ASSERT_TRUE(GetWireServer()->InjectTexture(apiTexture, reservation.id, reservation.generation));
+    ASSERT_TRUE(GetWireServer()->InjectTexture(apiTexture, reservation.id, reservation.generation,
+                                               reservation.deviceId, reservation.deviceGeneration));
 
     wgpuTextureCreateView(reservation.texture, nullptr);
     WGPUTextureView apiDummyView = api.GetNewTextureView();
@@ -57,11 +58,13 @@
 
     WGPUTexture apiTexture = api.GetNewTexture();
     EXPECT_CALL(api, TextureReference(apiTexture));
-    ASSERT_TRUE(GetWireServer()->InjectTexture(apiTexture, reservation.id, reservation.generation));
+    ASSERT_TRUE(GetWireServer()->InjectTexture(apiTexture, reservation.id, reservation.generation,
+                                               reservation.deviceId, reservation.deviceGeneration));
 
     // ID already in use, call fails.
-    ASSERT_FALSE(
-        GetWireServer()->InjectTexture(apiTexture, reservation.id, reservation.generation));
+    ASSERT_FALSE(GetWireServer()->InjectTexture(apiTexture, reservation.id, reservation.generation,
+                                                reservation.deviceId,
+                                                reservation.deviceGeneration));
 }
 
 // Test that the server only borrows the texture and does a single reference-release
@@ -71,7 +74,8 @@
     // Injecting the texture adds a reference
     WGPUTexture apiTexture = api.GetNewTexture();
     EXPECT_CALL(api, TextureReference(apiTexture));
-    ASSERT_TRUE(GetWireServer()->InjectTexture(apiTexture, reservation.id, reservation.generation));
+    ASSERT_TRUE(GetWireServer()->InjectTexture(apiTexture, reservation.id, reservation.generation,
+                                               reservation.deviceId, reservation.deviceGeneration));
 
     // Releasing the texture removes a single reference.
     wgpuTextureRelease(reservation.texture);