blob: be6770b281f0ce0e719dd423a8ae674eabd10ec8 [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.
#ifndef IPCZ_SRC_IPCZ_NODE_LINK_MEMORY_H_
#define IPCZ_SRC_IPCZ_NODE_LINK_MEMORY_H_
#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 {
public:
// 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;
NodeLinkMemory(NodeLinkMemory&&);
// Returned by Allocate().
struct Allocation {
// The NodeLinkMemory created by a succesful call to Allocate(), or null if
// memory could not be allocated. This memory is initialized with a
// primary buffer (BufferId 0) whose contents have also been appropriately
// initialized. This object is ready for immediate use by a new NodeLink on
// the `node` passed to Allocate().
Ref<NodeLinkMemory> node_link_memory;
// A handle to the region underlying the new NodeLinkMemory's primary
// buffer. This should be shared with the corresponding NodeLink's remote
// node, where it can be passed to Adopt() to establish a new NodeLinkMemory
// there.
DriverMemory primary_buffer_memory;
};
// 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);
// Constructs a new NodeLinkMemory over a newly allocated DriverMemory object.
// The new DriverMemory is returned in `primary_buffer_memory`, while the
// returned NodeLinkMemory internally retains a mapping of that memory.
static Allocation Allocate(Ref<Node> node);
// Constructs a new NodeLinkMemory with BufferId 0 (the primary buffer) mapped
// from `primary_buffer_memory`. The buffer must have been created and
// initialized by a prior call to Allocate() above.
static Ref<NodeLinkMemory> Adopt(Ref<Node> node,
DriverMemory primary_buffer_memory);
// 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);
// 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 =
std::function<void(FragmentRef<RouterLinkState>)>;
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);
private:
struct PrimaryBuffer;
NodeLinkMemory(Ref<Node> node, DriverMemoryMapping primary_buffer);
~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_;
// 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_
ABSL_GUARDED_BY(mutex_);
};
} // namespace ipcz
#endif // IPCZ_SRC_IPCZ_NODE_LINK_MEMORY_H_