blob: cf7b8ac0c00d4808c3a766776fd375730fceac97 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <cstddef>
#include <cstdint>
#include <functional>
#include <vector>
#include "ipcz/buffer_id.h"
#include "ipcz/buffer_pool.h"
#include "ipcz/driver_memory.h"
#include "ipcz/driver_memory_mapping.h"
#include "ipcz/fragment_descriptor.h"
#include "ipcz/fragment_ref.h"
#include "ipcz/ipcz.h"
#include "ipcz/ref_counted_fragment.h"
#include "ipcz/router_link_state.h"
#include "ipcz/sublink_id.h"
#include "third_party/abseil-cpp/absl/container/flat_hash_map.h"
#include "third_party/abseil-cpp/absl/synchronization/mutex.h"
#include "third_party/abseil-cpp/absl/types/span.h"
#include "util/ref_counted.h"
namespace ipcz {
class Node;
class NodeLink;
// NodeLinkMemory owns and manages all shared memory resource allocation on a
// single NodeLink. Each end of a NodeLink has its own NodeLinkMemory instance
// cooperatively managing the same dynamic pool of memory, shared exclusively
// between the two endpoint nodes.
class NodeLinkMemory : public RefCounted {
// The maximum number of initial portals supported on ConnectNode() API calls.
// The first kMaxInitialPortals SublinkIds on a NodeLinkMemory will always be
// reserved for use by initial portals.
static constexpr size_t kMaxInitialPortals = 12;
// Sets a reference to the NodeLink using this NodeLinkMemory. This is called
// by the NodeLink itself before any other methods can be called on the
// NodeLinkMemory, and it's only reset to null once the NodeLink is
// deactivated. This link may be used to share information with the remote
// node, where another NodeLinkMemory is cooperatively managing the same
// memory pool as this one.
void SetNodeLink(Ref<NodeLink> link);
// Allocates a new DriverMemory object and initializes its contents to be
// suitable as the primary buffer of a new NodeLinkMemory. Returns the memory
// along with a mapping of it.
static DriverMemoryWithMapping AllocateMemory(const IpczDriver& driver);
// Constructs a new NodeLinkMemory with BufferId 0 (the primary buffer) mapped
// as `primary_buffer_memory`. The buffer must have been created and
// initialized by a prior call to AllocateMemory() above.
static Ref<NodeLinkMemory> Create(Ref<Node> node,
DriverMemoryMapping primary_buffer_memory);
// Returns true if and only if new parcels are allowed to try allocating their
// data buffer through this NodeLinkMemory.
bool allow_parcel_data_allocation() const {
return allow_parcel_data_allocation_;
// Returns a new BufferId which should still be unused by any buffer in this
// NodeLinkMemory's BufferPool, or that of its peer NodeLinkMemory. When
// allocating new a buffer to add to the BufferPool, its BufferId should be
// procured by calling this method.
BufferId AllocateNewBufferId();
// Returns the first of `count` newly allocated, contiguous sublink IDs for
// use on the corresponding NodeLink.
SublinkId AllocateSublinkIds(size_t count);
// Returns a ref to the RouterLinkState for the `i`th initial portal on the
// NodeLink, established by the Connect() call which created this link. Unlike
// other RouterLinkStates which are allocated dynamically, these have a fixed
// location within the NodeLinkMemory's primary buffer. The returned
// FragmentRef is unmanaged and will never free its underlying fragment.
FragmentRef<RouterLinkState> GetInitialRouterLinkState(size_t i);
// Resolves `descriptor` to a concrete Fragment. If the descriptor is null or
// describes a region of memory which exceeds the bounds of the identified
// buffer, this returns a null Fragment. If the descriptor's BufferId is not
// yet registered with this NodeLinkMemory, this returns a pending Fragment
// with the same BufferId and dimensions as `descriptor`.
Fragment GetFragment(const FragmentDescriptor& descriptor);
// Adopts an existing reference to a RefCountedFragment within `fragment`.
// This does NOT increment the ref count of the RefCountedFragment.
template <typename T>
FragmentRef<T> AdoptFragmentRef(const Fragment& fragment) {
ABSL_ASSERT(sizeof(T) <= fragment.size());
return FragmentRef<T>(RefCountedFragment::kAdoptExistingRef,
WrapRefCounted(this), fragment);
// Adds a new buffer to the underlying BufferPool to use as additional
// allocation capacity for blocks of size `block_size`. Note that the
// contents of the mapped region must already be initialized as a
// BlockAllocator.
bool AddBlockBuffer(BufferId id,
size_t block_size,
DriverMemoryMapping mapping);
// Allocates a Fragment of `size` bytes from the underlying BufferPool. May
// return a null Fragment if there was no readily available capacity.
Fragment AllocateFragment(size_t size);
// Attempts to allocate a Fragment of at least `size` bytes. If there are no
// readily available fragments large enough, this may return a fragment
// smaller than `size`.
Fragment AllocateFragmentBestEffort(size_t size);
// Frees a Fragment previously allocated through this NodeLinkMemory. Returns
// true on success. Returns false if `fragment` does not represent an
// allocated fragment within this NodeLinkMemory.
bool FreeFragment(const Fragment& fragment);
// Allocates a fragment to store a new RouterLinkState and initializes a new
// RouterLinkState instance there. If no capacity is currently available to
// allocate an appropriate fragment, this may return null.
FragmentRef<RouterLinkState> TryAllocateRouterLinkState();
// Allocates a fragment to store a new RouterLinkState and initializes a new
// RouterLinkState instance there. Calls `callback` with a reference to the
// new fragment once allocated. Unlike TryAllocateRouterLinkState(), this
// allocation always succeeds eventually unless driver memory allocation
// itself begins to fail unrecoverably. If the allocation can succeed
// synchronously, `callback` may be called before this method returns.
using RouterLinkStateCallback =
void AllocateRouterLinkState(RouterLinkStateCallback callback);
// Runs `callback` as soon as the identified buffer is added to the underlying
// BufferPool. If the buffer is already present here, `callback` is run
// immediately.
void WaitForBufferAsync(BufferId id,
BufferPool::WaitForBufferCallback callback);
struct PrimaryBuffer;
// Constructs a new NodeLinkMemory over `mapping`, which must correspond to
// a DriverMemory whose contents have already been initialized as a
// NodeLinkMemory primary buffer.
NodeLinkMemory(Ref<Node> node, DriverMemoryMapping mapping);
~NodeLinkMemory() override;
// Indicates whether the NodeLinkMemory should be allowed to expand its
// allocation capacity further for blocks of size `block_size`.
bool CanExpandBlockCapacity(size_t block_size);
// Attempts to expand the total block allocation capacity for blocks of
// `block_size` bytes. `callback` may be called synchronously or
// asynchronously with a result indicating whether the expansion succeeded.
using RequestBlockCapacityCallback = std::function<void(bool)>;
void RequestBlockCapacity(size_t block_size,
RequestBlockCapacityCallback callback);
void OnCapacityRequestComplete(size_t block_size, bool success);
// Initializes `fragment` as a new RouterLinkState and returns a ref to it.
FragmentRef<RouterLinkState> InitializeRouterLinkStateFragment(
const Fragment& fragment);
const Ref<Node> node_;
const bool allow_parcel_data_allocation_;
// The underlying BufferPool. Note that this object is itself thread-safe, so
// access to it is not synchronized by NodeLinkMemory.
BufferPool buffer_pool_;
// Mapping for this link's fixed primary buffer.
const absl::Span<uint8_t> primary_buffer_memory_;
PrimaryBuffer& primary_buffer_;
absl::Mutex mutex_;
// The NodeLink which is using this NodeLinkMemory. Used to communicate with
// the NodeLinkMemory on the other side of the link.
Ref<NodeLink> node_link_ ABSL_GUARDED_BY(mutex_);
// Callbacks to invoke when a pending capacity request is fulfilled for a
// specific block size. Also used to prevent stacking of capacity requests for
// the same block size.
using CapacityCallbackList = std::vector<RequestBlockCapacityCallback>;
absl::flat_hash_map<uint32_t, CapacityCallbackList> capacity_callbacks_
} // namespace ipcz