blob: c1dc341e84a7ad183cde4e47d47d973f697866e6 [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef MOJO_PUBLIC_CPP_BASE_SHARED_MEMORY_VERSION_H_
#define MOJO_PUBLIC_CPP_BASE_SHARED_MEMORY_VERSION_H_
#include <stdint.h>
#include <atomic>
#include <optional>
#include "base/component_export.h"
#include "base/memory/read_only_shared_memory_region.h"
#include "base/memory/structured_shared_memory.h"
namespace mojo {
class SharedMemoryVersionClient;
using VersionType = uint64_t;
using CountType = uint64_t;
struct VersionState {
std::atomic<VersionType> version;
std::atomic<CountType> committed_writes_count;
};
// This file contains classes to share a version between processes through
// shared memory. A version is a nonzero monotonically increasing integer. A
// controller has read and write access to the version and one or many clients
// have read access only. Controllers should only be created in privileged
// processes.
//
// Clients can avoid issuing IPCs depending on the version stored in shared
// memory. However this should only be used as a hint to avoid redundant IPC's,
// not to version other objects stored in shared memory: if the version
// increases, the client's copy of an object is out of date and it must fetch a
// fresh copy. But it couldn't use a copy of the object in shared memory and
// assume that it matches the updated version, because writes to the object and
// the version number can be reordered.
//
// Shared memory allocation can fail like any allocation. In that case it's not
// always possible to know why. When that happens classes in this file default
// to consistently falling back on IPCs.
//
// Example:
//
// class Controller : mojom::StateProvider {
// ...
//
// void SetState(State state) {
// state_ = state;
// version_.Increment();
// }
//
// void GetState(
// base::OnceCallback<void(State, VersionType)> callback) override {
// callback_.Run(state_, version_.GetSharedVersion());
// }
//
// SharedMemoryVersionController version_;
// };
//
// class Client {
// ...
//
// State GetState() {
// // IPC can be avoided.
// if (cached_version_ &&
// !version_.SharedVersionIsGreaterThan(cached_version_)) {
// return cached_state_.value();
// }
//
// State state;
// VersionType version;
//
// // Sync IPC to keep the example simple. Prefer async IPCs.
// if (!provider_->GetState(&state, &version)) {
// // error handling
// }
//
// cached_state_ = state;
// cached_version_ = version;
// return cached_state_.value();
// }
//
// mojo::Receiver<mojom::StateProvider> provider_;
// std::optional<State> cached_state_;
// std::optional<VersionType> cached_version_;
// SharedMemoryVersionClient version_;
//
// };
namespace shared_memory_version {
static constexpr VersionType kInvalidVersion = 0ULL;
static constexpr VersionType kInitialVersion = 1ULL;
} // namespace shared_memory_version
// Used to modify the version number and issue read only handles to it.
class COMPONENT_EXPORT(MOJO_BASE) SharedMemoryVersionController {
public:
SharedMemoryVersionController();
~SharedMemoryVersionController();
// Not copyable or movable
SharedMemoryVersionController(const SharedMemoryVersionController&) = delete;
SharedMemoryVersionController& operator=(
const SharedMemoryVersionController&) = delete;
// Get a shared memory region to be sent to a different process. It will be
// used to instantiate a SharedMemoryVersionClient.
base::ReadOnlySharedMemoryRegion GetSharedMemoryRegion() const;
VersionType GetSharedVersion() const;
// Increment shared version. This is not expected to cause a wrap of the value
// during normal operation. This invariant is guaranteed with a CHECK.
void Increment();
// Increment the committed writes counter. This is not expected to cause a
// wrap of the value during normal operation. This invariant is guaranteed
// with a CHECK.
void CommitWrite();
// Directly set shared version. `version` must be strictly larger than
// previous version. `version` cannot be maximum representable value for
// VersionType.
void SetVersion(VersionType version);
private:
std::optional<base::StructuredSharedMemory<VersionState>> mapped_region_;
};
// Used to keep track of a remote version number and compare it to a
// locally tracked version.
class COMPONENT_EXPORT(MOJO_BASE) SharedMemoryVersionClient {
public:
explicit SharedMemoryVersionClient(
base::ReadOnlySharedMemoryRegion shared_region);
~SharedMemoryVersionClient();
// Not copyable or movable
SharedMemoryVersionClient(const SharedMemoryVersionClient&) = delete;
SharedMemoryVersionClient& operator=(const SharedMemoryVersionClient&) =
delete;
// These functions can be used to form statements such as:
// "Skip the IPC if `SharedVersionIsLessThan()` returns true."
// The functions err on the side of caution and return true if the comparison
// is impossible since issuing an IPC should always be an option.
bool SharedVersionIsLessThan(VersionType version) const;
bool SharedVersionIsGreaterThan(VersionType version) const;
// This function can be used to form statements such as:
// "Perform IPC if `CommittedWritesIsLessThan()` returns true.""
// The function errs on the side of caution and returns true if the comparison
// is impossible, since issuing an IPC should always be an option.
bool CommittedWritesIsLessThan(CountType count) const;
private:
// Returns the current value in shared memory.
VersionType GetSharedVersion() const;
const std::optional<
base::StructuredSharedMemory<VersionState>::ReadOnlyMapping>
read_only_mapping_;
};
} // namespace mojo
#endif // MOJO_PUBLIC_CPP_BASE_SHARED_MEMORY_VERSION_H_