blob: 8f6d833aae8618df0c52eafd3dc2bb31dbe0381f [file] [log] [blame]
// Copyright 2022 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/capture_mode/camera_video_frame_handler.h"
#include <iostream>
#include "ash/capture_mode/capture_mode_camera_controller.h"
#include "ash/capture_mode/capture_mode_controller.h"
#include "base/bind.h"
#include "base/check.h"
#include "base/logging.h"
#include "base/memory/shared_memory_mapping.h"
#include "base/memory/unsafe_shared_memory_region.h"
#include "base/system/sys_info.h"
#include "components/viz/common/gpu/context_lost_observer.h"
#include "components/viz/common/gpu/context_provider.h"
#include "gpu/command_buffer/client/gpu_memory_buffer_manager.h"
#include "gpu/command_buffer/client/shared_image_interface.h"
#include "gpu/command_buffer/common/context_result.h"
#include "gpu/command_buffer/common/gpu_memory_buffer_support.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "gpu/ipc/common/gpu_memory_buffer_impl_native_pixmap.h"
#include "gpu/ipc/common/gpu_memory_buffer_impl_shared_memory.h"
#include "media/base/media_switches.h"
#include "media/base/video_frame.h"
#include "media/base/video_types.h"
#include "media/capture/video_capture_types.h"
#include "mojo/public/cpp/system/buffer.h"
#include "ui/aura/env.h"
#include "ui/compositor/compositor.h"
#include "ui/gfx/buffer_types.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/gpu_memory_buffer.h"
#include "ui/ozone/public/client_native_pixmap_factory_ozone.h"
namespace ash {
namespace {
bool g_force_use_gpu_memory_buffer_for_test = false;
// A constant flag that describes which APIs the shared image mailboxes created
// for the video frame will be used with.
constexpr uint32_t kSharedImageUsage =
gpu::SHARED_IMAGE_USAGE_GLES2 | gpu::SHARED_IMAGE_USAGE_RASTER |
gpu::SHARED_IMAGE_USAGE_DISPLAY | gpu::SHARED_IMAGE_USAGE_SCANOUT;
// The usage of the GpuMemoryBuffer that backs the video frames on an actual
// device (of type `NATIVE_PIXMAP`). The buffer is going to be presented on the
// screen for rendering, will be used as a texture, and can be read by CPU and
// potentially a video encode accelerator.
constexpr gfx::BufferUsage kGpuMemoryBufferUsage =
gfx::BufferUsage::SCANOUT_VEA_CPU_READ;
// The usage of the GpuMemoryBuffer that backs the video frames in unittests,
// since the type of that buffer will be `SHARED_MEMORY_BUFFER` which doesn't
// support the above on-device usage.
constexpr gfx::BufferUsage kGpuMemoryBufferUsageForTest =
gfx::BufferUsage::SCANOUT_CPU_READ_WRITE;
// The only supported video pixel format used on devices is `PIXEL_FORMAT_NV12`.
// This maps to a buffer format of `YUV_420_BIPLANAR`.
constexpr gfx::BufferFormat kGpuMemoryBufferFormat =
gfx::BufferFormat::YUV_420_BIPLANAR;
// In unittests, the video pixel format used is `PIXEL_FORMAT_ARGB`, since the
// video frames are painted and verified manually using Skia. The buffer format
// used for this is `BGRA_8888`.
constexpr gfx::BufferFormat kGpuMemoryBufferFormatForTest =
gfx::BufferFormat::BGRA_8888;
gfx::BufferUsage GetBufferUsage() {
return g_force_use_gpu_memory_buffer_for_test ? kGpuMemoryBufferUsageForTest
: kGpuMemoryBufferUsage;
}
gfx::BufferFormat GetBufferFormat() {
return g_force_use_gpu_memory_buffer_for_test ? kGpuMemoryBufferFormatForTest
: kGpuMemoryBufferFormat;
}
ui::ContextFactory* GetContextFactory() {
return aura::Env::GetInstance()->context_factory();
}
bool IsGpuBufferTypeSupported(gfx::GpuMemoryBufferType type) {
return type == gfx::NATIVE_PIXMAP || type == gfx::SHARED_MEMORY_BUFFER;
}
// Adjusts the requested video capture `params` depending on whether we're
// running on an actual device or the linux-chromeos build.
void AdjustParamsForCurrentConfig(media::VideoCaptureParams* params) {
DCHECK(params);
// The default params are good enough when running on linux-chromeos.
if (!base::SysInfo::IsRunningOnChromeOS() &&
!g_force_use_gpu_memory_buffer_for_test) {
DCHECK_EQ(params->buffer_type,
media::VideoCaptureBufferType::kSharedMemory);
return;
}
// On an actual device, the camera HAL only supports NV12 pixel formats in a
// GPU memory buffer.
params->requested_format.pixel_format = media::PIXEL_FORMAT_NV12;
params->buffer_type = media::VideoCaptureBufferType::kGpuMemoryBuffer;
}
// Creates and returns a list of the buffer planes for each we'll need to create
// a shared image and store it in `GpuMemoryBufferHandleHolder::mailboxes_`.
std::vector<gfx::BufferPlane> CreateGpuBufferPlanes() {
std::vector<gfx::BufferPlane> planes;
if (base::FeatureList::IsEnabled(
media::kMultiPlaneVideoCaptureSharedImages)) {
planes.push_back(gfx::BufferPlane::Y);
planes.push_back(gfx::BufferPlane::UV);
} else {
planes.push_back(gfx::BufferPlane::DEFAULT);
}
return planes;
}
// Returns the buffer texture target used to create a `MailboxHolder` according
// to our GPU buffer usage, buffer format, and the given `context_capabilities`.
uint32_t CalculateBufferTextureTarget(
const gpu::Capabilities& context_capabilities) {
return gpu::GetBufferTextureTarget(GetBufferUsage(), GetBufferFormat(),
context_capabilities);
}
bool IsFatalError(media::VideoCaptureError error) {
switch (error) {
case media::VideoCaptureError::kCrosHalV3FailedToStartDeviceThread:
case media::VideoCaptureError::kCrosHalV3DeviceDelegateMojoConnectionError:
case media::VideoCaptureError::
kCrosHalV3DeviceDelegateFailedToOpenCameraDevice:
case media::VideoCaptureError::
kCrosHalV3DeviceDelegateFailedToInitializeCameraDevice:
case media::VideoCaptureError::
kCrosHalV3DeviceDelegateFailedToConfigureStreams:
case media::VideoCaptureError::kCrosHalV3BufferManagerFatalDeviceError:
return true;
default:
return false;
}
}
// -----------------------------------------------------------------------------
// SharedMemoryBufferHandleHolder:
// Defines an implementation for a `BufferHandleHolder` that can extract a video
// frame that is backed by a `kSharedMemory` buffer type. This implementation is
// used only when running on a linux-chromeos build (a.k.a. the emulator).
class SharedMemoryBufferHandleHolder : public BufferHandleHolder {
public:
explicit SharedMemoryBufferHandleHolder(
media::mojom::VideoBufferHandlePtr buffer_handle)
: region_(std::move(buffer_handle->get_unsafe_shmem_region())) {
DCHECK(buffer_handle->is_unsafe_shmem_region());
DCHECK(!base::SysInfo::IsRunningOnChromeOS());
}
SharedMemoryBufferHandleHolder(const SharedMemoryBufferHandleHolder&) =
delete;
SharedMemoryBufferHandleHolder& operator=(
const SharedMemoryBufferHandleHolder&) = delete;
~SharedMemoryBufferHandleHolder() override = default;
// BufferHandleHolder:
scoped_refptr<media::VideoFrame> OnFrameReadyInBuffer(
video_capture::mojom::ReadyFrameInBufferPtr buffer) override {
const size_t mapping_size = media::VideoFrame::AllocationSize(
buffer->frame_info->pixel_format, buffer->frame_info->coded_size);
if (!MaybeUpdateMapping(mapping_size))
return {};
auto& frame_info = buffer->frame_info;
auto frame = media::VideoFrame::WrapExternalData(
frame_info->pixel_format, frame_info->coded_size,
frame_info->visible_rect, frame_info->visible_rect.size(),
mapping_.GetMemoryAs<uint8_t>(), mapping_.size(),
frame_info->timestamp);
return frame;
}
private:
// Maps a new region with a size `new_mapping_size` bytes if no `mapping_` is
// available. Returns true if already mapped, or mapping is successful, false
// otherwise.
bool MaybeUpdateMapping(size_t new_mapping_size) {
if (mapping_.IsValid()) {
// TODO(https://crbug.com/1316812): What guarantees that this DCHECK will
// hold?
DCHECK_EQ(mapping_.size(), new_mapping_size);
return true;
}
mapping_ = region_.Map();
return mapping_.IsValid();
}
// The held shared memory region associated with this object.
base::UnsafeSharedMemoryRegion region_;
// Shared memory mapping associated with the held `region_`.
base::WritableSharedMemoryMapping mapping_;
};
// -----------------------------------------------------------------------------
// GpuMemoryBufferHandleHolder:
// Defines an implementation for a `BufferHandleHolder` that can extract a video
// frame that is backed by a `kGpuMemoryBuffer` buffer type.
class GpuMemoryBufferHandleHolder : public BufferHandleHolder,
public viz::ContextLostObserver {
public:
explicit GpuMemoryBufferHandleHolder(
media::mojom::VideoBufferHandlePtr buffer_handle)
: gpu_memory_buffer_handle_(
std::move(buffer_handle->get_gpu_memory_buffer_handle())),
buffer_planes_(CreateGpuBufferPlanes()),
client_native_pixmap_factory_(
ui::CreateClientNativePixmapFactoryOzone()),
context_provider_(
GetContextFactory()->SharedMainThreadContextProvider()),
buffer_texture_target_(CalculateBufferTextureTarget(
context_provider_->ContextCapabilities())) {
DCHECK(buffer_handle->is_gpu_memory_buffer_handle());
DCHECK(IsGpuBufferTypeSupported(gpu_memory_buffer_handle_.type));
DCHECK(context_provider_);
context_provider_->AddObserver(this);
}
GpuMemoryBufferHandleHolder(const GpuMemoryBufferHandleHolder&) = delete;
GpuMemoryBufferHandleHolder& operator=(const GpuMemoryBufferHandleHolder&) =
delete;
~GpuMemoryBufferHandleHolder() override {
if (!context_provider_)
return;
context_provider_->RemoveObserver(this);
gpu::SharedImageInterface* shared_image_interface =
context_provider_->SharedImageInterface();
DCHECK(shared_image_interface);
for (const auto& mb : mailboxes_) {
if (mb.IsZero() || !mb.IsSharedImage())
continue;
shared_image_interface->DestroySharedImage(release_sync_token_, mb);
}
}
// BufferHandleHolder:
scoped_refptr<media::VideoFrame> OnFrameReadyInBuffer(
video_capture::mojom::ReadyFrameInBufferPtr buffer) override {
if (!context_provider_) {
LOG(ERROR) << "GPU context lost.";
return {};
}
const auto& frame_info = buffer->frame_info;
if (!MaybeCreateSharedImages(frame_info)) {
LOG(ERROR) << "Failed to initialize GpuMemoryBufferHandleHolder.";
return {};
}
return WrapMailboxesInVideoFrame(frame_info);
}
// viz::ContextLostObserver:
void OnContextLost() override {
DCHECK(context_provider_);
context_provider_->RemoveObserver(this);
// Clear the mailboxes so that we can recreate the shared images.
should_create_shared_images_ = true;
for (auto& mb : mailboxes_)
mb.SetZero();
release_sync_token_ = gpu::SyncToken();
context_provider_ = GetContextFactory()->SharedMainThreadContextProvider();
if (context_provider_) {
context_provider_->AddObserver(this);
buffer_texture_target_ = CalculateBufferTextureTarget(
context_provider_->ContextCapabilities());
}
}
private:
// Creates and returns a new `GpuMemoryBuffer` from a cloned handle of our
// `gpu_memory_buffer_handle_`. The type of the buffer depends on the type of
// the handle and can only be either `NATIVE_PIXMAP` or
// `SHARED_MEMORY_BUFFER`.
std::unique_ptr<gfx::GpuMemoryBuffer> CreateGpuMemoryBufferFromHandle(
const gfx::Size& size) {
const auto buffer_format = GetBufferFormat();
const auto buffer_usage = GetBufferUsage();
if (gpu_memory_buffer_handle_.type == gfx::NATIVE_PIXMAP) {
return gpu::GpuMemoryBufferImplNativePixmap::CreateFromHandle(
client_native_pixmap_factory_.get(),
gpu_memory_buffer_handle_.Clone(), size, buffer_format, buffer_usage,
base::DoNothing());
}
DCHECK_EQ(gpu_memory_buffer_handle_.type, gfx::SHARED_MEMORY_BUFFER);
DCHECK(g_force_use_gpu_memory_buffer_for_test);
return gpu::GpuMemoryBufferImplSharedMemory::CreateFromHandle(
gpu_memory_buffer_handle_.Clone(), size, buffer_format, buffer_usage,
base::DoNothing());
}
// Initializes this holder by creating shared images and storing them in
// `mailboxes_`. These shared images are backed by a GpuMemoryBuffer whose
// handle is a clone of our `gpu_memory_buffer_handle_`. This operation should
// only be done the first ever time, or whenever the gpu context is lost.
// Returns true if shared images are already created or creation is
// successful. False otherwise.
bool MaybeCreateSharedImages(
const media::mojom::VideoFrameInfoPtr& frame_info) {
DCHECK(context_provider_);
if (!should_create_shared_images_)
return true;
// We clone our handle `gpu_memory_buffer_handle_` and use the cloned handle
// to create a new GpuMemoryBuffer which will be used to create the shared
// images. This way, the lifetime of our `gpu_memory_buffer_handle_` remains
// tied to the lieftime of this object (i.e. until `OnBufferRetired()` is
// called).
std::unique_ptr<gfx::GpuMemoryBuffer> gmb =
CreateGpuMemoryBufferFromHandle(frame_info->coded_size);
if (!gmb) {
LOG(ERROR) << "Failed to create a GpuMemoryBuffer.";
return false;
}
gpu::SharedImageInterface* shared_image_interface =
context_provider_->SharedImageInterface();
DCHECK(shared_image_interface);
gpu::GpuMemoryBufferManager* gmb_manager =
GetContextFactory()->GetGpuMemoryBufferManager();
for (size_t plane = 0; plane < buffer_planes_.size(); ++plane) {
mailboxes_[plane] = shared_image_interface->CreateSharedImage(
gmb.get(), gmb_manager, buffer_planes_[plane],
frame_info->color_space.value_or(gfx::ColorSpace()),
kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, kSharedImageUsage);
}
// Since this is the first time we create the shared images in `mailboxes_`,
// we need to guarantee that the mailboxes are created before they're used.
mailbox_holder_sync_token_ = shared_image_interface->GenVerifiedSyncToken();
should_create_shared_images_ = false;
return true;
}
// Wraps the shared images in `mailboxes_` in a video frame and returns it if
// wrapping was successful, or an empty refptr otherwise.
scoped_refptr<media::VideoFrame> WrapMailboxesInVideoFrame(
const media::mojom::VideoFrameInfoPtr& frame_info) {
DCHECK(!should_create_shared_images_);
if (frame_info->pixel_format !=
media::VideoPixelFormat::PIXEL_FORMAT_NV12 &&
frame_info->pixel_format !=
media::VideoPixelFormat::PIXEL_FORMAT_ARGB) {
LOG(ERROR) << "Unsupported pixel format";
return {};
}
// The camera GpuMemoryBuffer is backed by a DMA-buff, and doesn't use a
// pre-mapped shared memory region.
DCHECK(!frame_info->is_premapped);
gpu::MailboxHolder mailbox_holder_array[media::VideoFrame::kMaxPlanes];
for (size_t plane = 0; plane < buffer_planes_.size(); ++plane) {
DCHECK(!mailboxes_[plane].IsZero());
DCHECK(mailboxes_[plane].IsSharedImage());
mailbox_holder_array[plane] =
gpu::MailboxHolder(mailboxes_[plane], mailbox_holder_sync_token_,
buffer_texture_target_);
}
mailbox_holder_sync_token_.Clear();
auto frame = media::VideoFrame::WrapNativeTextures(
frame_info->pixel_format, mailbox_holder_array,
base::BindOnce(&GpuMemoryBufferHandleHolder::OnMailboxReleased,
weak_ptr_factory_.GetWeakPtr()),
frame_info->coded_size, frame_info->visible_rect,
frame_info->visible_rect.size(), frame_info->timestamp);
if (!frame) {
LOG(ERROR) << "Failed to create a video frame.";
return frame;
}
if (frame_info->color_space.has_value() &&
frame_info->color_space->IsValid()) {
frame->set_color_space(frame_info->color_space.value());
}
frame->metadata().allow_overlay = true;
frame->metadata().read_lock_fences_enabled = true;
frame->metadata().MergeMetadataFrom(frame_info->metadata);
return frame;
}
// Called when the video frame is destroyed.
void OnMailboxReleased(const gpu::SyncToken& release_sync_token) {
release_sync_token_ = release_sync_token;
}
// The held GPU buffer handle associated with this object.
const gfx::GpuMemoryBufferHandle gpu_memory_buffer_handle_;
// The buffer planes for each we need to create a shared image and store it in
// `mailboxes_`.
const std::vector<gfx::BufferPlane> buffer_planes_;
// Used to create a GPU memory buffer from its handle.
std::unique_ptr<gfx::ClientNativePixmapFactory> client_native_pixmap_factory_;
scoped_refptr<viz::ContextProvider> context_provider_;
// The texture target we use to create a `MailboxHolder`. This value is
// calculated for out GPU buffer format, and GPU buffer usage, and the current
// capabilities of `context_provider_`.
uint32_t buffer_texture_target_;
// Contains the shared images of the video frame planes created from the GPU
// memory buffer.
std::vector<gpu::Mailbox> mailboxes_{media::VideoFrame::kMaxPlanes};
// The sync token used when creating a `MailboxHolder`. This will be a
// verified sync token the first time we wrap a video frame around a mailbox.
gpu::SyncToken mailbox_holder_sync_token_;
// The release sync token of the above `mailboxes_`.
gpu::SyncToken release_sync_token_;
bool should_create_shared_images_ = true;
base::WeakPtrFactory<GpuMemoryBufferHandleHolder> weak_ptr_factory_{this};
};
} // namespace
// -----------------------------------------------------------------------------
// BufferHandleHolder:
BufferHandleHolder::~BufferHandleHolder() = default;
// static
std::unique_ptr<BufferHandleHolder> BufferHandleHolder::Create(
media::mojom::VideoBufferHandlePtr buffer_handle) {
if (buffer_handle->is_unsafe_shmem_region()) {
return std::make_unique<SharedMemoryBufferHandleHolder>(
std::move(buffer_handle));
}
DCHECK(buffer_handle->is_gpu_memory_buffer_handle());
return std::make_unique<GpuMemoryBufferHandleHolder>(
std::move(buffer_handle));
}
// -----------------------------------------------------------------------------
// CameraVideoFrameHandler:
CameraVideoFrameHandler::CameraVideoFrameHandler(
Delegate* delegate,
mojo::Remote<video_capture::mojom::VideoSource> camera_video_source,
const media::VideoCaptureFormat& capture_format)
: delegate_(delegate),
camera_video_source_remote_(std::move(camera_video_source)) {
DCHECK(delegate_);
DCHECK(camera_video_source_remote_);
camera_video_source_remote_.set_disconnect_handler(
base::BindOnce(&CameraVideoFrameHandler::OnFatalErrorOrDisconnection,
base::Unretained(this)));
media::VideoCaptureParams capture_params;
capture_params.requested_format = capture_format;
AdjustParamsForCurrentConfig(&capture_params);
camera_video_source_remote_->CreatePushSubscription(
video_frame_handler_receiver_.BindNewPipeAndPassRemote(), capture_params,
// The Camera app, or some other camera capture operation may already be
// running with certain settings. We don't want to reopen the camera
// device with our settings, since our requirements are usually low in
// terms of frame rate and size. So we'll use whatever settings available
// if any.
/*force_reopen_with_new_settings=*/false,
camera_video_stream_subsciption_remote_.BindNewPipeAndPassReceiver(),
base::BindOnce(
[](video_capture::mojom::CreatePushSubscriptionResultCodePtr
result_code,
const media::VideoCaptureParams& actual_params) {
if (result_code->is_error_code()) {
LOG(ERROR) << "Error in creating push subscription: "
<< static_cast<int>(result_code->get_error_code());
}
}));
}
CameraVideoFrameHandler::~CameraVideoFrameHandler() = default;
void CameraVideoFrameHandler::StartHandlingFrames() {
DCHECK(camera_video_stream_subsciption_remote_);
camera_video_stream_subsciption_remote_->Activate();
}
void CameraVideoFrameHandler::OnNewBuffer(
int buffer_id,
media::mojom::VideoBufferHandlePtr buffer_handle) {
const auto pair = buffer_map_.emplace(
buffer_id, BufferHandleHolder::Create(std::move(buffer_handle)));
DCHECK(pair.second);
}
void CameraVideoFrameHandler::OnFrameAccessHandlerReady(
mojo::PendingRemote<video_capture::mojom::VideoFrameAccessHandler>
pending_frame_access_handler) {
video_frame_access_handler_remote_.Bind(
std::move(pending_frame_access_handler));
}
void CameraVideoFrameHandler::OnFrameReadyInBuffer(
video_capture::mojom::ReadyFrameInBufferPtr buffer,
std::vector<video_capture::mojom::ReadyFrameInBufferPtr> scaled_buffers) {
DCHECK(video_frame_access_handler_remote_);
// Ignore scaled buffers for now.
for (auto& scaled_buffer : scaled_buffers) {
video_frame_access_handler_remote_->OnFinishedConsumingBuffer(
scaled_buffer->buffer_id);
}
scaled_buffers.clear();
const int buffer_id = buffer->buffer_id;
const auto& iter = buffer_map_.find(buffer_id);
DCHECK(iter != buffer_map_.end());
const auto& buffer_handle_holder = iter->second;
scoped_refptr<media::VideoFrame> frame =
buffer_handle_holder->OnFrameReadyInBuffer(std::move(buffer));
if (!frame) {
video_frame_access_handler_remote_->OnFinishedConsumingBuffer(buffer_id);
return;
}
frame->AddDestructionObserver(
base::BindOnce(&CameraVideoFrameHandler::OnVideoFrameGone,
weak_ptr_factory_.GetWeakPtr(), buffer_id));
delegate_->OnCameraVideoFrame(std::move(frame));
}
void CameraVideoFrameHandler::OnBufferRetired(int buffer_id) {
DCHECK(buffer_map_.contains(buffer_id));
buffer_map_.erase(buffer_id);
}
void CameraVideoFrameHandler::OnError(media::VideoCaptureError error) {
LOG(ERROR) << "Recieved error: " << static_cast<int>(error);
if (IsFatalError(error))
OnFatalErrorOrDisconnection();
}
void CameraVideoFrameHandler::OnFrameDropped(
media::VideoCaptureFrameDropReason reason) {
DLOG(ERROR) << "A camera video frame was dropped due to: "
<< static_cast<int>(reason);
}
void CameraVideoFrameHandler::OnNewCropVersion(uint32_t crop_version) {}
void CameraVideoFrameHandler::OnFrameWithEmptyRegionCapture() {}
void CameraVideoFrameHandler::OnLog(const std::string& message) {
DVLOG(1) << message;
}
void CameraVideoFrameHandler::OnStarted() {}
void CameraVideoFrameHandler::OnStartedUsingGpuDecode() {}
void CameraVideoFrameHandler::OnStopped() {}
// static
void CameraVideoFrameHandler::SetForceUseGpuMemoryBufferForTest(bool value) {
g_force_use_gpu_memory_buffer_for_test = value;
}
void CameraVideoFrameHandler::OnVideoFrameGone(int buffer_id) {
DCHECK(video_frame_access_handler_remote_);
video_frame_access_handler_remote_->OnFinishedConsumingBuffer(buffer_id);
}
void CameraVideoFrameHandler::OnFatalErrorOrDisconnection() {
buffer_map_.clear();
weak_ptr_factory_.InvalidateWeakPtrs();
video_frame_handler_receiver_.reset();
camera_video_source_remote_.reset();
camera_video_stream_subsciption_remote_.reset();
video_frame_access_handler_remote_.reset();
CaptureModeController::Get()->camera_controller()->OnFrameHandlerFatalError();
// `this` will be deleted soon after the above call. "Soon" here because the
// `camera_preview_widget_` which indirectly owns `this` is destroyed
// asynchronously when `Close()` is called on it.
}
} // namespace ash