[dawn][wire] Make FillReservation release WGPU objects if released.

- Refactors code a bit to use templating in some places so we can
  use the types to choose accessors instead of via generator.
- Introduces WGPUTraits in server side to allow for any other future
  traits we may find useful to include.
- Fixes failing fuzzer case where we allocate an object, then release
  it before it is filled, causing a crash on FillReservation call.

Bug: b:334279466
Change-Id: Iaa26df7185cad27c214d5c1ff3e1b468f3c3fbdf
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/184460
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Loko Kung <lokokung@google.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/generator/dawn_json_generator.py b/generator/dawn_json_generator.py
index d37e34f..e605181 100644
--- a/generator/dawn_json_generator.py
+++ b/generator/dawn_json_generator.py
@@ -1268,6 +1268,11 @@
                     'dawn/wire/server/ServerPrototypes.inc',
                     'src/dawn/wire/server/ServerPrototypes_autogen.inc',
                     wire_params))
+            renders.append(
+                FileRender('dawn/wire/server/WGPUTraits.h',
+                           'src/dawn/wire/server/WGPUTraits_autogen.h',
+                           wire_params))
+
 
         if 'dawn_lpmfuzz_proto' in targets:
             params_dawn_wire = parse_json(loaded_json,
diff --git a/generator/templates/dawn/wire/ObjectType.h b/generator/templates/dawn/wire/ObjectType.h
index 5f53956..9eeab87 100644
--- a/generator/templates/dawn/wire/ObjectType.h
+++ b/generator/templates/dawn/wire/ObjectType.h
@@ -25,8 +25,8 @@
 //* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 //* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
-#ifndef DAWNWIRE_OBJECTTPYE_AUTOGEN_H_
-#define DAWNWIRE_OBJECTTPYE_AUTOGEN_H_
+#ifndef DAWNWIRE_OBJECTTYPE_AUTOGEN_H_
+#define DAWNWIRE_OBJECTTYPE_AUTOGEN_H_
 
 #include "dawn/common/ityp_array.h"
 
@@ -46,4 +46,4 @@
 } // namespace dawn::wire
 
 
-#endif  // DAWNWIRE_OBJECTTPYE_AUTOGEN_H_
+#endif  // DAWNWIRE_OBJECTTYPE_AUTOGEN_H_
diff --git a/generator/templates/dawn/wire/server/ServerBase.h b/generator/templates/dawn/wire/server/ServerBase.h
index 8ca45e0..54f2062 100644
--- a/generator/templates/dawn/wire/server/ServerBase.h
+++ b/generator/templates/dawn/wire/server/ServerBase.h
@@ -25,8 +25,10 @@
 //* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 //* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
-#ifndef DAWNWIRE_SERVER_SERVERBASE_H_
-#define DAWNWIRE_SERVER_SERVERBASE_H_
+#ifndef DAWNWIRE_SERVER_SERVERBASE_AUTOGEN_H_
+#define DAWNWIRE_SERVER_SERVERBASE_AUTOGEN_H_
+
+#include <tuple>
 
 #include "dawn/dawn_proc_table.h"
 #include "dawn/wire/ChunkedCommandHandler.h"
@@ -34,6 +36,7 @@
 #include "dawn/wire/WireCmd_autogen.h"
 #include "dawn/wire/WireDeserializeAllocator.h"
 #include "dawn/wire/server/ObjectStorage.h"
+#include "dawn/wire/server/WGPUTraits_autogen.h"
 
 namespace dawn::wire::server {
 
@@ -43,57 +46,65 @@
         ~ServerBase() override = default;
 
       protected:
+        template <typename T>
+        const KnownObjects<T>& Objects() const {
+            return std::get<KnownObjects<T>>(mKnown);
+        }
+        template <typename T>
+        KnownObjects<T>& Objects() {
+            return std::get<KnownObjects<T>>(mKnown);
+        }
+
+        template <typename T>
+        void Release(const DawnProcTable& procs, T handle) {
+            (procs.*WGPUTraits<T>::Release)(handle);
+        }
+
         void DestroyAllObjects(const DawnProcTable& procs) {
             //* Release devices first to force completion of any async work.
             {
-                std::vector<WGPUDevice> handles = mKnownDevice.AcquireAllHandles();
+                std::vector<WGPUDevice> handles = Objects<WGPUDevice>().AcquireAllHandles();
                 for (WGPUDevice handle : handles) {
-                    procs.deviceRelease(handle);
+                    Release(procs, handle);
                 }
             }
             //* Free all objects when the server is destroyed
             {% for type in by_category["object"] if type.name.get() != "device" %}
+                {% set cType = as_cType(type.name) %}
                 {
-                    std::vector<{{as_cType(type.name)}}> handles = mKnown{{type.name.CamelCase()}}.AcquireAllHandles();
-                    for ({{as_cType(type.name)}} handle : handles) {
-                        procs.{{as_varName(type.name, Name("release"))}}(handle);
+                    std::vector<{{cType}}> handles = Objects<{{cType}}>().AcquireAllHandles();
+                    for ({{cType}} handle : handles) {
+                        Release(procs, handle);
                     }
                 }
             {% endfor %}
         }
 
-        {% for type in by_category["object"] %}
-            const KnownObjects<{{as_cType(type.name)}}>& {{type.name.CamelCase()}}Objects() const {
-                return mKnown{{type.name.CamelCase()}};
-            }
-            KnownObjects<{{as_cType(type.name)}}>& {{type.name.CamelCase()}}Objects() {
-                return mKnown{{type.name.CamelCase()}};
-            }
-        {% endfor %}
-
       private:
         // Implementation of the ObjectIdResolver interface
         {% for type in by_category["object"] %}
-            WireResult GetFromId(ObjectId id, {{as_cType(type.name)}}* out) const final {
-                return mKnown{{type.name.CamelCase()}}.GetNativeHandle(id, out);
+            {% set cType = as_cType(type.name) %}
+            WireResult GetFromId(ObjectId id, {{cType}}* out) const final {
+                return Objects<{{cType}}>().GetNativeHandle(id, out);
             }
 
-            WireResult GetOptionalFromId(ObjectId id, {{as_cType(type.name)}}* out) const final {
+            WireResult GetOptionalFromId(ObjectId id, {{cType}}* out) const final {
                 if (id == 0) {
                     *out = nullptr;
                     return WireResult::Success;
                 }
-
                 return GetFromId(id, out);
             }
         {% endfor %}
 
         //* The list of known IDs for each object type.
-        {% for type in by_category["object"] %}
-            KnownObjects<{{as_cType(type.name)}}> mKnown{{type.name.CamelCase()}};
-        {% endfor %}
+        std::tuple<
+            {% for type in by_category["object"] %}
+                KnownObjects<{{as_cType(type.name)}}>{{ ", " if not loop.last else "" }}
+            {% endfor %}
+        > mKnown;
     };
 
 }  // namespace dawn::wire::server
 
-#endif  // DAWNWIRE_SERVER_SERVERBASE_H_
+#endif  // DAWNWIRE_SERVER_SERVERBASE_AUTOGEN_H_
diff --git a/generator/templates/dawn/wire/server/ServerDoers.cpp b/generator/templates/dawn/wire/server/ServerDoers.cpp
index 8f05ea4..d67795b 100644
--- a/generator/templates/dawn/wire/server/ServerDoers.cpp
+++ b/generator/templates/dawn/wire/server/ServerDoers.cpp
@@ -80,9 +80,10 @@
     WireResult Server::DoDestroyObject(ObjectType objectType, ObjectId objectId) {
         switch(objectType) {
             {% for type in by_category["object"] %}
+                {% set cType = as_cType(type.name) %}
                 case ObjectType::{{type.name.CamelCase()}}: {
-                    Reserved<WGPU{{type.name.CamelCase()}}> obj;
-                    WIRE_TRY({{type.name.CamelCase()}}Objects().Get(objectId, &obj));
+                    Reserved<{{cType}}> obj;
+                    WIRE_TRY(Objects<{{cType}}>().Get(objectId, &obj));
 
                     if (obj->state == AllocationState::Allocated) {
                         DAWN_ASSERT(obj->handle != nullptr);
@@ -91,9 +92,9 @@
                             //* they should not be forwarded if the device no longer exists on the wire.
                             ClearDeviceCallbacks(obj->handle);
                         {% endif %}
-                        mProcs.{{as_varName(type.name, Name("release"))}}(obj->handle);
+                        Release(mProcs, obj->handle);
                     }
-                    {{type.name.CamelCase()}}Objects().Free(objectId);
+                    Objects<{{cType}}>().Free(objectId);
                     return WireResult::Success;
                 }
             {% endfor %}
diff --git a/generator/templates/dawn/wire/server/ServerHandlers.cpp b/generator/templates/dawn/wire/server/ServerHandlers.cpp
index 7b8110b..0b788f7 100644
--- a/generator/templates/dawn/wire/server/ServerHandlers.cpp
+++ b/generator/templates/dawn/wire/server/ServerHandlers.cpp
@@ -50,18 +50,19 @@
             //* Allocate any result objects
             {% for member in command.members if member.is_return_value -%}
                 {{ assert(member.handle_type) }}
-                {% set Type = member.handle_type.name.CamelCase() %}
+                {% set cType = as_cType(member.handle_type.name) %}
                 {% set name = as_varName(member.name) %}
-                Reserved<WGPU{{Type}}> {{name}}Data;
-                WIRE_TRY({{Type}}Objects().Allocate(&{{name}}Data, cmd.{{name}}));
+                Reserved<{{cType}}> {{name}}Data;
+                WIRE_TRY(Objects<{{cType}}>().Allocate(&{{name}}Data, cmd.{{name}}));
                 {{name}}Data->generation = cmd.{{name}}.generation;
             {%- endfor %}
 
             //* Get any input objects
             {% for member in command.members if member.id_type != None -%}
+                {% set cType = as_cType(member.id_type.name) %}
                 {% set name = as_varName(member.name) %}
-                Known<WGPU{{member.id_type.name.CamelCase()}}> {{name}}Handle;
-                WIRE_TRY({{member.id_type.name.CamelCase()}}Objects().Get(cmd.{{name}}, &{{name}}Handle));
+                Known<{{cType}}> {{name}}Handle;
+                WIRE_TRY(Objects<{{cType}}>().Get(cmd.{{name}}, &{{name}}Handle));
             {% endfor %}
 
             //* Do command
@@ -123,7 +124,7 @@
         // After the server handles all the commands from the stream, we additionally run
         // ProcessEvents on all known Instances so that any work done on the server side can be
         // forwarded through to the client.
-        for (auto instance : InstanceObjects().GetAllHandles()) {
+        for (auto instance : Objects<WGPUInstance>().GetAllHandles()) {
             if (DoInstanceProcessEvents(instance) != WireResult::Success) {
                 return nullptr;
             }
diff --git a/generator/templates/dawn/wire/server/WGPUTraits.h b/generator/templates/dawn/wire/server/WGPUTraits.h
new file mode 100644
index 0000000..c404caf
--- /dev/null
+++ b/generator/templates/dawn/wire/server/WGPUTraits.h
@@ -0,0 +1,48 @@
+//* Copyright 2024 The Dawn & Tint Authors
+//*
+//* Redistribution and use in source and binary forms, with or without
+//* modification, are permitted provided that the following conditions are met:
+//*
+//* 1. Redistributions of source code must retain the above copyright notice, this
+//*    list of conditions and the following disclaimer.
+//*
+//* 2. Redistributions in binary form must reproduce the above copyright notice,
+//*    this list of conditions and the following disclaimer in the documentation
+//*    and/or other materials provided with the distribution.
+//*
+//* 3. Neither the name of the copyright holder nor the names of its
+//*    contributors may be used to endorse or promote products derived from
+//*    this software without specific prior written permission.
+//*
+//* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+//* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+//* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+//* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+//* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+//* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+//* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+//* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+//* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+//* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef DAWNWIRE_SERVER_WGPUTRAITS_AUTOGEN_H_
+#define DAWNWIRE_SERVER_WGPUTRAITS_AUTOGEN_H_
+
+#include "dawn/dawn_proc_table.h"
+
+namespace dawn::wire::server {
+
+template <typename T>
+struct WGPUTraits;
+
+{% for type in by_category["object"] %}
+    {% set cType = as_cType(type.name) %}
+    template <>
+    struct WGPUTraits<{{cType}}> {
+        static constexpr auto Release = &DawnProcTable::{{as_varName(type.name, Name("release"))}};
+    };
+{% endfor %}
+
+}  // namespace dawn::wire::server
+
+#endif  // DAWNWIRE_SERVER_WGPUTRAITS_AUTOGEN_H_
diff --git a/src/dawn/wire/BUILD.gn b/src/dawn/wire/BUILD.gn
index b46b246..a915ec9 100644
--- a/src/dawn/wire/BUILD.gn
+++ b/src/dawn/wire/BUILD.gn
@@ -58,6 +58,7 @@
     "src/dawn/wire/server/ServerDoers_autogen.cpp",
     "src/dawn/wire/server/ServerHandlers_autogen.cpp",
     "src/dawn/wire/server/ServerPrototypes_autogen.inc",
+    "src/dawn/wire/server/WGPUTraits_autogen.h",
   ]
 }
 
diff --git a/src/dawn/wire/client/Adapter.cpp b/src/dawn/wire/client/Adapter.cpp
index fffeaf4..846fc86 100644
--- a/src/dawn/wire/client/Adapter.cpp
+++ b/src/dawn/wire/client/Adapter.cpp
@@ -69,23 +69,21 @@
 
   private:
     void CompleteImpl(FutureID futureID, EventCompletionType completionType) override {
-        Device* device = mDevice.ExtractAsDangling();
         if (completionType == EventCompletionType::Shutdown) {
             mStatus = WGPURequestDeviceStatus_InstanceDropped;
             mMessage = "A valid external Instance reference no longer exists.";
         }
+
+        Device* device = mDevice.ExtractAsDangling();
         if (mCallback) {
-            Device* outputDevice = device;
-            if (mStatus != WGPURequestDeviceStatus_Success) {
-                outputDevice = nullptr;
-            }
-            mCallback(mStatus, ToAPI(outputDevice), mMessage ? mMessage->c_str() : nullptr,
-                      mUserdata.ExtractAsDangling());
+            // Callback needs to happen before device lost handling to ensure resolution order.
+            mCallback(mStatus, ToAPI(mStatus == WGPURequestDeviceStatus_Success ? device : nullptr),
+                      mMessage ? mMessage->c_str() : nullptr, mUserdata.ExtractAsDangling());
         }
 
         if (mStatus != WGPURequestDeviceStatus_Success) {
-            // If there was an error, we may need to call the device lost callback and reclaim the
-            // device allocation, otherwise the device is returned to the user who owns it.
+            // If there was an error and we didn't return a device, we need to call the device lost
+            // callback and reclaim the device allocation.
             if (mStatus == WGPURequestDeviceStatus_InstanceDropped) {
                 device->HandleDeviceLost(WGPUDeviceLostReason_InstanceDropped,
                                          "A valid external Instance reference no longer exists.");
@@ -93,9 +91,12 @@
                 device->HandleDeviceLost(WGPUDeviceLostReason_FailedCreation,
                                          "Device failed at creation.");
             }
+        }
+
+        if (mCallback == nullptr) {
+            // If there's no callback, clean up the resources.
             device->Release();
-        } else if (!mCallback) {
-            device->Release();
+            mUserdata.ExtractAsDangling();
         }
     }
 
diff --git a/src/dawn/wire/client/Client.h b/src/dawn/wire/client/Client.h
index 0d57c02..0140680 100644
--- a/src/dawn/wire/client/Client.h
+++ b/src/dawn/wire/client/Client.h
@@ -71,10 +71,6 @@
         return object;
     }
 
-    template <typename T>
-    void Free(T* obj) {
-        Free(obj, ObjectTypeToTypeEnum<T>);
-    }
     void Free(ObjectBase* obj, ObjectType type);
 
     template <typename T>
@@ -115,6 +111,11 @@
   private:
     void DestroyAllObjects();
 
+    template <typename T>
+    void Free(T* obj) {
+        Free(obj, ObjectTypeToTypeEnum<T>);
+    }
+
 #include "dawn/wire/client/ClientPrototypes_autogen.inc"
 
     ChunkedCommandSerializer mSerializer;
diff --git a/src/dawn/wire/client/Device.cpp b/src/dawn/wire/client/Device.cpp
index 47b909c..459d7a2 100644
--- a/src/dawn/wire/client/Device.cpp
+++ b/src/dawn/wire/client/Device.cpp
@@ -122,25 +122,22 @@
 
   private:
     void CompleteImpl(FutureID futureID, EventCompletionType completionType) override {
+        if (mCallback == nullptr) {
+            // If there's no callback, just clean up the resources.
+            mPipeline.ExtractAsDangling()->Release();
+            mUserdata.ExtractAsDangling();
+            return;
+        }
+
         if (completionType == EventCompletionType::Shutdown) {
             mStatus = WGPUCreatePipelineAsyncStatus_InstanceDropped;
             mMessage = "A valid external Instance reference no longer exists.";
         }
 
-        // By default, we are initialized to a success state, and on shutdown we just return success
-        // so we don't need to handle it specifically.
         Pipeline* pipeline = mPipeline.ExtractAsDangling();
-        if (mStatus != WGPUCreatePipelineAsyncStatus_Success) {
-            // If there was an error we need to reclaim the pipeline allocation.
-            pipeline->GetClient()->Free(pipeline);
-            pipeline = nullptr;
-        }
-        if (mCallback) {
-            mCallback(mStatus, ToAPI(pipeline), mMessage ? mMessage->c_str() : nullptr,
-                      mUserdata.ExtractAsDangling());
-        } else if (pipeline != nullptr) {
-            pipeline->Release();
-        }
+        mCallback(mStatus,
+                  ToAPI(mStatus == WGPUCreatePipelineAsyncStatus_Success ? pipeline : nullptr),
+                  mMessage ? mMessage->c_str() : nullptr, mUserdata.ExtractAsDangling());
     }
 
     using Callback = decltype(std::declval<CallbackInfo>().callback);
diff --git a/src/dawn/wire/client/Instance.cpp b/src/dawn/wire/client/Instance.cpp
index 7b5b05a..19bdf82 100644
--- a/src/dawn/wire/client/Instance.cpp
+++ b/src/dawn/wire/client/Instance.cpp
@@ -76,23 +76,21 @@
 
   private:
     void CompleteImpl(FutureID futureID, EventCompletionType completionType) override {
+        if (mCallback == nullptr) {
+            // If there's no callback, just clean up the resources.
+            mAdapter.ExtractAsDangling()->Release();
+            mUserdata.ExtractAsDangling();
+            return;
+        }
+
         if (completionType == EventCompletionType::Shutdown) {
             mStatus = WGPURequestAdapterStatus_InstanceDropped;
             mMessage = "A valid external Instance reference no longer exists.";
         }
+
         Adapter* adapter = mAdapter.ExtractAsDangling();
-        if (mStatus != WGPURequestAdapterStatus_Success && adapter != nullptr) {
-            // If there was an error, we may need to reclaim the adapter allocation, otherwise the
-            // adapter is returned to the user who owns it.
-            adapter->GetClient()->Free(adapter);
-            adapter = nullptr;
-        }
-        if (mCallback) {
-            mCallback(mStatus, ToAPI(adapter), mMessage ? mMessage->c_str() : nullptr,
-                      mUserdata.ExtractAsDangling());
-        } else if (adapter != nullptr) {
-            adapter->Release();
-        }
+        mCallback(mStatus, ToAPI(mStatus == WGPURequestAdapterStatus_Success ? adapter : nullptr),
+                  mMessage ? mMessage->c_str() : nullptr, mUserdata.ExtractAsDangling());
     }
 
     WGPURequestAdapterCallback mCallback;
diff --git a/src/dawn/wire/server/ObjectStorage.h b/src/dawn/wire/server/ObjectStorage.h
index 5ad842a..9074dc1 100644
--- a/src/dawn/wire/server/ObjectStorage.h
+++ b/src/dawn/wire/server/ObjectStorage.h
@@ -194,13 +194,19 @@
         return WireResult::Success;
     }
 
-    Known<T> FillReservation(ObjectId id, T handle) {
+    WireResult FillReservation(ObjectId id, T handle, Known<T>* known = nullptr) {
         DAWN_ASSERT(id < mKnown.size());
         Data* data = &mKnown[id];
-        DAWN_ASSERT(data->state == AllocationState::Reserved);
+
+        if (data->state != AllocationState::Reserved) {
+            return WireResult::FatalError;
+        }
         data->handle = handle;
         data->state = AllocationState::Allocated;
-        return {id, data};
+        if (known != nullptr) {
+            *known = {id, data};
+        }
+        return WireResult::Success;
     }
 
     // Allocates the data for a given ID and returns it in result.
@@ -292,9 +298,11 @@
         return WireResult::Success;
     }
 
-    Known<WGPUDevice> FillReservation(ObjectId id, WGPUDevice handle) {
-        Known<WGPUDevice> result = KnownObjectsBase<WGPUDevice>::FillReservation(id, handle);
-        mKnownSet.insert(result->handle);
+    WireResult FillReservation(ObjectId id, WGPUDevice handle, Known<WGPUDevice>* known = nullptr) {
+        auto result = KnownObjectsBase<WGPUDevice>::FillReservation(id, handle, known);
+        if (result == WireResult::Success) {
+            mKnownSet.insert((*known)->handle);
+        }
         return result;
     }
 
diff --git a/src/dawn/wire/server/Server.cpp b/src/dawn/wire/server/Server.cpp
index f7c0a26..c835a3f 100644
--- a/src/dawn/wire/server/Server.cpp
+++ b/src/dawn/wire/server/Server.cpp
@@ -55,7 +55,7 @@
 Server::~Server() {
     // Un-set the error and lost callbacks since we cannot forward them
     // after the server has been destroyed.
-    for (WGPUDevice device : DeviceObjects().GetAllHandles()) {
+    for (WGPUDevice device : Objects<WGPUDevice>().GetAllHandles()) {
         ClearDeviceCallbacks(device);
     }
     DestroyAllObjects(mProcs);
@@ -66,13 +66,13 @@
                                  const Handle& deviceHandle) {
     DAWN_ASSERT(texture != nullptr);
     Known<WGPUDevice> device;
-    WIRE_TRY(DeviceObjects().Get(deviceHandle.id, &device));
+    WIRE_TRY(Objects<WGPUDevice>().Get(deviceHandle.id, &device));
     if (device->generation != deviceHandle.generation) {
         return WireResult::FatalError;
     }
 
     Reserved<WGPUTexture> data;
-    WIRE_TRY(TextureObjects().Allocate(&data, handle));
+    WIRE_TRY(Objects<WGPUTexture>().Allocate(&data, handle));
 
     data->handle = texture;
     data->generation = handle.generation;
@@ -90,13 +90,13 @@
                                    const Handle& deviceHandle) {
     DAWN_ASSERT(swapchain != nullptr);
     Known<WGPUDevice> device;
-    WIRE_TRY(DeviceObjects().Get(deviceHandle.id, &device));
+    WIRE_TRY(Objects<WGPUDevice>().Get(deviceHandle.id, &device));
     if (device->generation != deviceHandle.generation) {
         return WireResult::FatalError;
     }
 
     Reserved<WGPUSwapChain> data;
-    WIRE_TRY(SwapChainObjects().Allocate(&data, handle));
+    WIRE_TRY(Objects<WGPUSwapChain>().Allocate(&data, handle));
 
     data->handle = swapchain;
     data->generation = handle.generation;
@@ -112,7 +112,7 @@
 WireResult Server::InjectInstance(WGPUInstance instance, const Handle& handle) {
     DAWN_ASSERT(instance != nullptr);
     Reserved<WGPUInstance> data;
-    WIRE_TRY(InstanceObjects().Allocate(&data, handle));
+    WIRE_TRY(Objects<WGPUInstance>().Allocate(&data, handle));
 
     data->handle = instance;
     data->generation = handle.generation;
@@ -127,7 +127,7 @@
 
 WGPUDevice Server::GetDevice(uint32_t id, uint32_t generation) {
     Known<WGPUDevice> device;
-    if (DeviceObjects().Get(id, &device) != WireResult::Success ||
+    if (Objects<WGPUDevice>().Get(id, &device) != WireResult::Success ||
         device->generation != generation) {
         return nullptr;
     }
@@ -135,7 +135,7 @@
 }
 
 bool Server::IsDeviceKnown(WGPUDevice device) const {
-    return DeviceObjects().IsKnown(device);
+    return Objects<WGPUDevice>().IsKnown(device);
 }
 
 void Server::SetForwardingDeviceCallbacks(Known<WGPUDevice> device) {
diff --git a/src/dawn/wire/server/Server.h b/src/dawn/wire/server/Server.h
index f908b3d..ba1b07f 100644
--- a/src/dawn/wire/server/Server.h
+++ b/src/dawn/wire/server/Server.h
@@ -210,6 +210,15 @@
         mSerializer->SerializeCommand(cmd, std::forward<Extensions>(es)...);
     }
 
+    template <typename T>
+    WireResult FillReservation(ObjectId id, T handle, Known<T>* known = nullptr) {
+        auto result = Objects<T>().FillReservation(id, handle, known);
+        if (result == WireResult::FatalError) {
+            Release(mProcs, handle);
+        }
+        return result;
+    }
+
     void SetForwardingDeviceCallbacks(Known<WGPUDevice> device);
     void ClearDeviceCallbacks(WGPUDevice device);
 
diff --git a/src/dawn/wire/server/ServerAdapter.cpp b/src/dawn/wire/server/ServerAdapter.cpp
index 4eb4c93..7bd5c53 100644
--- a/src/dawn/wire/server/ServerAdapter.cpp
+++ b/src/dawn/wire/server/ServerAdapter.cpp
@@ -39,7 +39,7 @@
                                           WGPUFuture deviceLostFuture,
                                           const WGPUDeviceDescriptor* descriptor) {
     Reserved<WGPUDevice> device;
-    WIRE_TRY(DeviceObjects().Allocate(&device, deviceHandle, AllocationState::Reserved));
+    WIRE_TRY(Objects<WGPUDevice>().Allocate(&device, deviceHandle, AllocationState::Reserved));
 
     auto userdata = MakeUserdata<RequestDeviceUserdata>();
     userdata->eventManager = eventManager;
@@ -114,7 +114,12 @@
     cmd.limits = &limits;
 
     // Assign the handle and allocated status if the device is created successfully.
-    Known<WGPUDevice> reservation = DeviceObjects().FillReservation(data->deviceObjectId, device);
+    Known<WGPUDevice> reservation;
+    if (FillReservation(data->deviceObjectId, device, &reservation) == WireResult::FatalError) {
+        cmd.status = WGPURequestDeviceStatus_Unknown;
+        cmd.message = "Destroyed before request was fulfilled.";
+        SerializeCommand(cmd);
+    }
     DAWN_ASSERT(reservation.data != nullptr);
     reservation->info->server = this;
     reservation->info->self = reservation.AsHandle();
diff --git a/src/dawn/wire/server/ServerBuffer.cpp b/src/dawn/wire/server/ServerBuffer.cpp
index 56be933..5f4b6ff 100644
--- a/src/dawn/wire/server/ServerBuffer.cpp
+++ b/src/dawn/wire/server/ServerBuffer.cpp
@@ -37,7 +37,7 @@
 
 WireResult Server::PreHandleBufferUnmap(const BufferUnmapCmd& cmd) {
     Known<WGPUBuffer> buffer;
-    WIRE_TRY(BufferObjects().Get(cmd.selfId, &buffer));
+    WIRE_TRY(Objects<WGPUBuffer>().Get(cmd.selfId, &buffer));
 
     if (buffer->mappedAtCreation && !(buffer->usage & WGPUMapMode_Write)) {
         // This indicates the writeHandle is for mappedAtCreation only. Destroy on unmap
@@ -54,7 +54,7 @@
 WireResult Server::PreHandleBufferDestroy(const BufferDestroyCmd& cmd) {
     // Destroying a buffer does an implicit unmapping.
     Known<WGPUBuffer> buffer;
-    WIRE_TRY(BufferObjects().Get(cmd.selfId, &buffer));
+    WIRE_TRY(Objects<WGPUBuffer>().Get(cmd.selfId, &buffer));
 
     // The buffer was destroyed. Clear the Read/WriteHandle.
     buffer->readHandle = nullptr;
@@ -114,7 +114,7 @@
                                         const uint8_t* writeHandleCreateInfo) {
     // Create and register the buffer object.
     Reserved<WGPUBuffer> buffer;
-    WIRE_TRY(BufferObjects().Allocate(&buffer, bufferHandle));
+    WIRE_TRY(Objects<WGPUBuffer>().Allocate(&buffer, bufferHandle));
     buffer->handle = mProcs.deviceCreateBuffer(device->handle, descriptor);
     buffer->usage = descriptor->usage;
     buffer->mappedAtCreation = descriptor->mappedAtCreation;
@@ -216,7 +216,7 @@
 void Server::OnBufferMapAsyncCallback(MapUserdata* data, WGPUBufferMapAsyncStatus status) {
     // Skip sending the callback if the buffer has already been destroyed.
     Known<WGPUBuffer> buffer;
-    if (BufferObjects().Get(data->buffer.id, &buffer) != WireResult::Success ||
+    if (Objects<WGPUBuffer>().Get(data->buffer.id, &buffer) != WireResult::Success ||
         buffer->generation != data->buffer.generation) {
         return;
     }
diff --git a/src/dawn/wire/server/ServerDevice.cpp b/src/dawn/wire/server/ServerDevice.cpp
index 8c20356..f481838 100644
--- a/src/dawn/wire/server/ServerDevice.cpp
+++ b/src/dawn/wire/server/ServerDevice.cpp
@@ -29,24 +29,6 @@
 
 namespace dawn::wire::server {
 
-namespace {
-
-template <ObjectType objectType, typename Pipeline>
-void HandleCreatePipelineAsyncCallback(KnownObjects<Pipeline>* knownObjects,
-                                       WGPUCreatePipelineAsyncStatus status,
-                                       Pipeline pipeline,
-                                       CreatePipelineAsyncUserData* data) {
-    if (status == WGPUCreatePipelineAsyncStatus_Success) {
-        knownObjects->FillReservation(data->pipelineObjectID, pipeline);
-    } else {
-        // Otherwise, free the ObjectId which will make it unusable.
-        knownObjects->Free(data->pipelineObjectID);
-        DAWN_ASSERT(pipeline == nullptr);
-    }
-}
-
-}  // anonymous namespace
-
 void Server::OnUncapturedError(ObjectHandle device, WGPUErrorType type, const char* message) {
     ReturnDeviceUncapturedErrorCallbackCmd cmd;
     cmd.device = device;
@@ -110,8 +92,8 @@
     ObjectHandle pipelineObjectHandle,
     const WGPUComputePipelineDescriptor* descriptor) {
     Reserved<WGPUComputePipeline> pipeline;
-    WIRE_TRY(ComputePipelineObjects().Allocate(&pipeline, pipelineObjectHandle,
-                                               AllocationState::Reserved));
+    WIRE_TRY(Objects<WGPUComputePipeline>().Allocate(&pipeline, pipelineObjectHandle,
+                                                     AllocationState::Reserved));
 
     auto userdata = MakeUserdata<CreatePipelineAsyncUserData>();
     userdata->device = device.AsHandle();
@@ -129,15 +111,16 @@
                                                   WGPUCreatePipelineAsyncStatus status,
                                                   WGPUComputePipeline pipeline,
                                                   const char* message) {
-    HandleCreatePipelineAsyncCallback<ObjectType::ComputePipeline>(&ComputePipelineObjects(),
-                                                                   status, pipeline, data);
-
     ReturnDeviceCreateComputePipelineAsyncCallbackCmd cmd;
     cmd.eventManager = data->eventManager;
     cmd.future = data->future;
     cmd.status = status;
     cmd.message = message;
 
+    if (FillReservation(data->pipelineObjectID, pipeline) == WireResult::FatalError) {
+        cmd.status = WGPUCreatePipelineAsyncStatus_Unknown;
+        cmd.message = "Destroyed before request was fulfilled.";
+    }
     SerializeCommand(cmd);
 }
 
@@ -148,8 +131,8 @@
     ObjectHandle pipelineObjectHandle,
     const WGPURenderPipelineDescriptor* descriptor) {
     Reserved<WGPURenderPipeline> pipeline;
-    WIRE_TRY(RenderPipelineObjects().Allocate(&pipeline, pipelineObjectHandle,
-                                              AllocationState::Reserved));
+    WIRE_TRY(Objects<WGPURenderPipeline>().Allocate(&pipeline, pipelineObjectHandle,
+                                                    AllocationState::Reserved));
 
     auto userdata = MakeUserdata<CreatePipelineAsyncUserData>();
     userdata->device = device.AsHandle();
@@ -167,15 +150,16 @@
                                                  WGPUCreatePipelineAsyncStatus status,
                                                  WGPURenderPipeline pipeline,
                                                  const char* message) {
-    HandleCreatePipelineAsyncCallback<ObjectType::RenderPipeline>(&RenderPipelineObjects(), status,
-                                                                  pipeline, data);
-
     ReturnDeviceCreateRenderPipelineAsyncCallbackCmd cmd;
     cmd.eventManager = data->eventManager;
     cmd.future = data->future;
     cmd.status = status;
     cmd.message = message;
 
+    if (FillReservation(data->pipelineObjectID, pipeline) == WireResult::FatalError) {
+        cmd.status = WGPUCreatePipelineAsyncStatus_Unknown;
+        cmd.message = "Destroyed before request was fulfilled.";
+    }
     SerializeCommand(cmd);
 }
 
diff --git a/src/dawn/wire/server/ServerInstance.cpp b/src/dawn/wire/server/ServerInstance.cpp
index 255ab3f..ca939c7 100644
--- a/src/dawn/wire/server/ServerInstance.cpp
+++ b/src/dawn/wire/server/ServerInstance.cpp
@@ -39,7 +39,7 @@
                                             ObjectHandle adapterHandle,
                                             const WGPURequestAdapterOptions* options) {
     Reserved<WGPUAdapter> adapter;
-    WIRE_TRY(AdapterObjects().Allocate(&adapter, adapterHandle, AllocationState::Reserved));
+    WIRE_TRY(Objects<WGPUAdapter>().Allocate(&adapter, adapterHandle, AllocationState::Reserved));
 
     auto userdata = MakeUserdata<RequestAdapterUserdata>();
     userdata->eventManager = eventManager;
@@ -63,15 +63,18 @@
     cmd.message = message;
 
     if (status != WGPURequestAdapterStatus_Success) {
-        // Free the ObjectId which will make it unusable.
-        AdapterObjects().Free(data->adapterObjectId);
         DAWN_ASSERT(adapter == nullptr);
         SerializeCommand(cmd);
         return;
     }
 
     // Assign the handle and allocated status if the adapter is created successfully.
-    AdapterObjects().FillReservation(data->adapterObjectId, adapter);
+    if (FillReservation(data->adapterObjectId, adapter) == WireResult::FatalError) {
+        cmd.status = WGPURequestAdapterStatus_Unknown;
+        cmd.message = "Destroyed before request was fulfilled.";
+        SerializeCommand(cmd);
+        return;
+    }
 
     // Query and report the adapter supported features.
     std::vector<WGPUFeatureName> features;