Skip to content

Commit

Permalink
pw_multibuf: Add SingleChunkRegionTracker
Browse files Browse the repository at this point in the history
The SingleChunkRegionTracker tracks a single chunk on a given span of
contigous memory.

Test: compiles in CMake, Soong, and GN.

Change-Id: I800af3e85c137d23c5886435efc32c9480603a86
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/189074
Reviewed-by: Taylor Cramer <cramertj@google.com>
Commit-Queue: Carlos Chinchilla <cachinchilla@google.com>
  • Loading branch information
ChinchillaWithGoggles authored and CQ Bot Account committed Jan 26, 2024
1 parent e0cdf4e commit 14f8112
Show file tree
Hide file tree
Showing 8 changed files with 259 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ _doxygen_input_files = [ # keep-sorted: start
"$dir_pw_multibuf/public/pw_multibuf/chunk.h",
"$dir_pw_multibuf/public/pw_multibuf/chunk_region_tracker.h",
"$dir_pw_multibuf/public/pw_multibuf/multibuf.h",
"$dir_pw_multibuf/public/pw_multibuf/single_chunk_region_tracker.h",
"$dir_pw_perf_test/public/pw_perf_test/event_handler.h",
"$dir_pw_perf_test/public/pw_perf_test/perf_test.h",
"$dir_pw_protobuf/public/pw_protobuf/find.h",
Expand Down
26 changes: 26 additions & 0 deletions pw_multibuf/Android.bp
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,29 @@ cc_library_static {
"pw_bytes",
],
}

cc_library_static {
name: "pw_multibuf_single_chunk_region_tracker",
cpp_std: "c++20",
host_supported: true,
vendor_available: true,
export_include_dirs: ["public"],
header_libs: [
"pw_assert_headers",
"pw_assert_log_headers",
],
export_header_lib_headers: [
"pw_assert_headers",
"pw_assert_log_headers",
],
defaults: [
"pw_assert_log_defaults",
"pw_chrono_stl_defaults",
],
static_libs: [
"pw_bytes",
],
export_static_lib_headers: [
"pw_bytes",
],
}
20 changes: 20 additions & 0 deletions pw_multibuf/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,17 @@ cc_library(
],
)

cc_library(
name = "single_chunk_region_tracker",
hdrs = ["public/pw_multibuf/single_chunk_region_tracker.h"],
includes = ["public"],
deps = [
":chunk",
"//pw_assert",
"//pw_bytes",
],
)

pw_cc_test(
name = "chunk_test",
srcs = ["chunk_test.cc"],
Expand All @@ -68,6 +79,15 @@ pw_cc_test(
],
)

pw_cc_test(
name = "single_chunk_region_tracker_test",
srcs = ["single_chunk_region_tracker_test.cc"],
deps = [
":chunk",
":single_chunk_region_tracker",
],
)

cc_library(
name = "pw_multibuf",
srcs = ["multibuf.cc"],
Expand Down
22 changes: 22 additions & 0 deletions pw_multibuf/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,27 @@ pw_test("chunk_region_tracker_test") {
sources = [ "chunk_region_tracker_test.cc" ]
}

pw_source_set("single_chunk_region_tracker") {
public_configs = [ ":public_include_path" ]
public = [ "public/pw_multibuf/single_chunk_region_tracker.h" ]
public_deps = [
":chunk",
dir_pw_assert,
dir_pw_bytes,
]
}

pw_test("single_chunk_region_tracker_test") {
deps = [
":chunk",
":single_chunk_region_tracker",
]
sources = [ "single_chunk_region_tracker_test.cc" ]

# TODO: b/260624583 - Fix this for //targets/rp2040
enable_if = pw_build_EXECUTABLE_TARGET_TYPE != "pico_executable"
}

pw_test("chunk_test") {
deps = [
":chunk",
Expand Down Expand Up @@ -88,6 +109,7 @@ pw_test_group("tests") {
":chunk_test",
":chunk_region_tracker_test",
":multibuf_test",
":single_chunk_region_tracker_test",
]
}

Expand Down
22 changes: 22 additions & 0 deletions pw_multibuf/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,17 @@ pw_add_library(pw_multibuf.chunk_region_tracker STATIC
pw_multibuf.chunk
)

pw_add_library(pw_multibuf.single_chunk_region_tracker STATIC
SOURCES
public/pw_multibuf/single_chunk_region_tracker.h
PUBLIC_INCLUDES
public
PUBLIC_DEPS
pw_assert
pw_bytes
pw_multibuf.chunk
)

pw_add_test(pw_multibuf.chunk_test
SOURCES
chunk_test.cc
Expand All @@ -67,6 +78,17 @@ pw_add_test(pw_multibuf.chunk_region_tracker_test
pw_multibuf
)

pw_add_test(pw_multibuf.single_chunk_region_tracker_test
SOURCES
single_chunk_region_tracker_test.cc
PRIVATE_DEPS
pw_multibuf.chunk
pw_multibuf.single_chunk_region_tracker
GROUPS
modules
pw_multibuf
)

pw_add_library(pw_multibuf STATIC
HEADERS
public/pw_multibuf/multibuf.h
Expand Down
9 changes: 9 additions & 0 deletions pw_multibuf/docs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,12 @@ synchronous, making this class suitable for testing. The allocated region or

.. doxygenclass:: pw::multibuf::HeaderChunkRegionTracker
:members:

Another ``ChunkRegionTracker`` specialization is the lightweight
``SingleChunkRegionTracker``, which does not rely on ``Allocator`` and uses the
provided memory view to create a single chunk. This is useful when a single
``Chunk`` is sufficient at no extra overhead. However, the user needs to own
the provided memory and know when a new ``Chunk`` can be requested.

.. doxygenclass:: pw::multibuf::SingleChunkRegionTracker
:members:
82 changes: 82 additions & 0 deletions pw_multibuf/public/pw_multibuf/single_chunk_region_tracker.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright 2024 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#pragma once

#include <atomic>
#include <cstring>
#include <optional>

#include "pw_assert/assert.h"
#include "pw_bytes/span.h"
#include "pw_multibuf/chunk.h"

namespace pw::multibuf {

/// A `ChunkRegionTracker` that uses inline memory to create a single `Chunk`
/// with the only caveat that the provided `Chunk` cannot be split. All attempts
/// will result in `std::nullopt`.
class SingleChunkRegionTracker : public ChunkRegionTracker {
public:
/// Constructs a region tracker with a single `Chunk` that maps to `region`,
/// which must outlive this tracker and any `OwnedChunk` it creates.
explicit SingleChunkRegionTracker(ByteSpan region) : region_(region) {}
~SingleChunkRegionTracker() override { Destroy(); }

/// Gets a `Chunk` of a given size, which must be less than or equal to the
/// provided region.
///
/// Returns:
/// An `OwnedChunk` if the `Chunk` is free, otherwise `std::nullopt`, in
/// which case `GetChunk()` can be called again.
std::optional<OwnedChunk> GetChunk(size_t size) {
PW_DASSERT(size <= region_.size());
// Since this is a single `Chunk` region, re-create the first `Chunk` is
// allowed if freed.
std::optional<OwnedChunk> chunk = Chunk::CreateFirstForRegion(*this);
if (chunk.has_value() && size < region_.size()) {
(*chunk)->Truncate(size);
}
return chunk;
}

void Destroy() final {
// Nothing to release here.
PW_ASSERT(!chunk_in_use_);
}

ByteSpan Region() const final { return region_; }

void* AllocateChunkClass() final {
bool in_use = false;
if (!chunk_in_use_.compare_exchange_strong(in_use, true)) {
return nullptr;
}
return &chunk_storage_;
}

void DeallocateChunkClass(void* chunk) final {
PW_ASSERT(chunk == chunk_storage_.data());
// Mark the `Chunk` as not in use and zero-out the region and chunk storage.
std::memset(chunk_storage_.data(), 0, chunk_storage_.size());
std::memset(region_.data(), 0, region_.size());
chunk_in_use_ = false;
}

private:
ByteSpan region_;
std::atomic<bool> chunk_in_use_ = false;
alignas(Chunk) std::array<std::byte, sizeof(Chunk)> chunk_storage_;
};

} // namespace pw::multibuf
77 changes: 77 additions & 0 deletions pw_multibuf/single_chunk_region_tracker_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright 2023 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

#include "pw_multibuf/single_chunk_region_tracker.h"

#include <cstddef>
#include <optional>

#include "pw_multibuf/chunk.h"
#include "pw_unit_test/framework.h"

namespace pw::multibuf {
namespace {

const size_t kArbitraryRegionSize = 1024;
const size_t kArbitraryChunkSize = 32;

TEST(SingleChunkRegionTrackerTest, GetChunkSmallerThanRegionSuccess) {
std::array<std::byte, kArbitraryRegionSize> chunk_storage{};
SingleChunkRegionTracker chunk_tracker(chunk_storage);
std::optional<OwnedChunk> chunk = chunk_tracker.GetChunk(kArbitraryChunkSize);
EXPECT_TRUE(chunk.has_value());
EXPECT_EQ(chunk->size(), kArbitraryChunkSize);
}

TEST(SingleChunkRegionTrackerTest, GetChunkSameSizeAsRegionSuccess) {
std::array<std::byte, kArbitraryRegionSize> chunk_storage{};
SingleChunkRegionTracker chunk_tracker(chunk_storage);
std::optional<OwnedChunk> chunk =
chunk_tracker.GetChunk(kArbitraryRegionSize);
EXPECT_TRUE(chunk.has_value());
EXPECT_EQ(chunk->size(), kArbitraryRegionSize);
}

TEST(SingleChunkRegionTrackerTest, GetChunkFailChunkInUse) {
std::array<std::byte, kArbitraryRegionSize> chunk_storage{};
SingleChunkRegionTracker chunk_tracker(chunk_storage);
std::optional<OwnedChunk> chunk1 =
chunk_tracker.GetChunk(kArbitraryChunkSize);
ASSERT_TRUE(chunk1.has_value());

std::optional<OwnedChunk> chunk2 =
chunk_tracker.GetChunk(kArbitraryChunkSize);
EXPECT_FALSE(chunk2.has_value());
}

TEST(SingleChunkRegionTrackerTest, GetChunkAfterReleasedChunkSuccess) {
std::array<std::byte, kArbitraryRegionSize> chunk_storage{};
SingleChunkRegionTracker chunk_tracker(chunk_storage);
std::optional<OwnedChunk> chunk1 =
chunk_tracker.GetChunk(kArbitraryChunkSize);
ASSERT_TRUE(chunk1.has_value());

std::optional<OwnedChunk> chunk2 =
chunk_tracker.GetChunk(kArbitraryChunkSize);
ASSERT_FALSE(chunk2.has_value());

chunk1->Release();

std::optional<OwnedChunk> chunk3 =
chunk_tracker.GetChunk(kArbitraryChunkSize);
EXPECT_TRUE(chunk3.has_value());
}

} // namespace
} // namespace pw::multibuf

0 comments on commit 14f8112

Please sign in to comment.