Skip to content

Commit b1acd08

Browse files
authored
Add null check to PackedPointer. (#176)
Add ScenarioRunner (testing); more static checks for cache slots/refs. Fix header double-inclusion guard name. CR feedback.
1 parent 3c3f408 commit b1acd08

11 files changed

+314
-45
lines changed

script

src/llfs/api_types.hpp

+4
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ BATT_STRONG_TYPEDEF(bool, HasOutgoingRefs);
8989
*/
9090
BATT_STRONG_TYPEDEF(usize, ItemOffset);
9191

92+
/** \brief A pseudo-random number generator seed.
93+
*/
94+
BATT_STRONG_TYPEDEF(u32, RandomSeed);
95+
9296
} // namespace llfs
9397

9498
#endif // LLFS_API_TYPES_HPP

src/llfs/ioring_log_device2.test.cpp

+6-25
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
#include <llfs/ioring_log_device.test.hpp>
2020
#include <llfs/storage_simulation.hpp>
2121

22+
#include <llfs/testing/scenario_runner.hpp>
23+
2224
namespace {
2325

2426
using namespace llfs::constants;
@@ -511,9 +513,6 @@ class IoRingLogDevice2SimTest : public ::testing::Test
511513

512514
TEST_F(IoRingLogDevice2SimTest, Simulation)
513515
{
514-
const usize kNumThreads = std::thread::hardware_concurrency();
515-
const usize kUpdateInterval = kNumSeeds / 20;
516-
517516
llfs::testing::TestConfig test_config;
518517

519518
LLFS_LOG_INFO() << BATT_INSPECT(kNumSeeds);
@@ -527,28 +526,10 @@ TEST_F(IoRingLogDevice2SimTest, Simulation)
527526
EXPECT_FALSE(goals.tail_collision);
528527
EXPECT_FALSE(goals.tail_rewrite);
529528

530-
std::vector<std::thread> threads;
531-
usize start_seed = test_config.get_random_seed();
532-
usize seeds_remaining = kNumSeeds;
533-
for (usize i = 0; i < kNumThreads; ++i) {
534-
usize n_seeds = seeds_remaining / (kNumThreads - i);
535-
usize end_seed = start_seed + n_seeds;
536-
threads.emplace_back([&, start_seed, end_seed] {
537-
for (usize seed = start_seed; seed < end_seed; seed += 1) {
538-
LLFS_LOG_INFO_EVERY_N(kUpdateInterval)
539-
<< "progress=" << (seed - start_seed) * 100 / (end_seed - start_seed) << "%";
540-
541-
Scenario scenario{seed, goals};
542-
ASSERT_NO_FATAL_FAILURE(scenario.run());
543-
}
544-
});
545-
start_seed += n_seeds;
546-
seeds_remaining -= n_seeds;
547-
}
548-
549-
for (std::thread& t : threads) {
550-
t.join();
551-
}
529+
llfs::testing::ScenarioRunner{} //
530+
.n_seeds(kNumSeeds)
531+
.n_updates(20)
532+
.run(batt::StaticType<Scenario>{}, goals);
552533

553534
EXPECT_TRUE(goals.concurrent_writes);
554535
EXPECT_TRUE(goals.burst_mode_checked);

src/llfs/packed_pointer.hpp

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ struct PackedPointer {
4343
const T* get() const
4444
{
4545
BATT_STATIC_ASSERT_EQ(sizeof(PackedPointer), sizeof(Offset));
46+
BATT_CHECK_NE(this->offset, 0);
4647

4748
return reinterpret_cast<const T*>(reinterpret_cast<const u8*>(this) + this->offset);
4849
}

src/llfs/page_cache_slot.cpp

+11-11
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ bool PageCacheSlot::is_pinned() const noexcept
7676

7777
//==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - -
7878
//
79-
u32 PageCacheSlot::pin_count() const noexcept
79+
u64 PageCacheSlot::pin_count() const noexcept
8080
{
8181
return Self::get_pin_count(this->state_.load(std::memory_order_acquire));
8282
}
@@ -116,11 +116,11 @@ auto PageCacheSlot::acquire_pin(PageId key, bool ignore_key) noexcept -> PinnedR
116116
{
117117
const auto old_state = this->state_.fetch_add(kPinCountDelta, std::memory_order_acquire);
118118
const auto new_state = old_state + kPinCountDelta;
119-
const bool newly_pinned = !this->is_pinned(old_state);
119+
const bool newly_pinned = !Self::is_pinned(old_state);
120120

121121
BATT_CHECK_EQ(new_state & Self::kOverflowMask, 0);
122122

123-
BATT_CHECK(this->is_pinned(new_state));
123+
BATT_CHECK(Self::is_pinned(new_state));
124124

125125
BATT_SUPPRESS_IF_GCC("-Wmaybe-uninitialized")
126126

@@ -134,7 +134,7 @@ auto PageCacheSlot::acquire_pin(PageId key, bool ignore_key) noexcept -> PinnedR
134134
// If the pin_count > 1 (because of the fetch_add above) and the slot is valid, it is safe to read
135135
// the key. If the key doesn't match, release the ref and return failure.
136136
//
137-
if (!this->is_valid(old_state) ||
137+
if (!Self::is_valid(old_state) ||
138138
(!ignore_key && (!this->key_.is_valid() || this->key_ != key))) {
139139
this->release_pin();
140140
return PinnedRef{};
@@ -149,7 +149,7 @@ auto PageCacheSlot::acquire_pin(PageId key, bool ignore_key) noexcept -> PinnedR
149149
BATT_CHECK(this->value_);
150150
}
151151

152-
return PinnedRef{this};
152+
return PinnedRef{this, CallerPromisesTheyAcquiredPinCount{}};
153153
}
154154

155155
//==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - -
@@ -199,7 +199,7 @@ void PageCacheSlot::release_pin() noexcept
199199
<< BATT_INSPECT(old_state);
200200

201201
const auto new_state = old_state - kPinCountDelta;
202-
const bool newly_unpinned = !this->is_pinned(new_state);
202+
const bool newly_unpinned = !Self::is_pinned(new_state);
203203

204204
BATT_CHECK_EQ(new_state & Self::kOverflowMask, 0);
205205

@@ -221,7 +221,7 @@ bool PageCacheSlot::evict() noexcept
221221
//
222222
auto observed_state = this->state_.load(std::memory_order_acquire);
223223
for (;;) {
224-
if (Self::is_pinned(observed_state) || !this->is_valid(observed_state)) {
224+
if (Self::is_pinned(observed_state) || !Self::is_valid(observed_state)) {
225225
return false;
226226
}
227227

@@ -244,7 +244,7 @@ bool PageCacheSlot::evict_if_key_equals(PageId key) noexcept
244244
const auto old_state = this->state_.fetch_add(kPinCountDelta, std::memory_order_acquire);
245245
auto observed_state = old_state + kPinCountDelta;
246246

247-
const bool newly_pinned = !this->is_pinned(old_state);
247+
const bool newly_pinned = !Self::is_pinned(old_state);
248248
if (newly_pinned) {
249249
this->add_ref();
250250
}
@@ -257,7 +257,7 @@ bool PageCacheSlot::evict_if_key_equals(PageId key) noexcept
257257
for (;;) {
258258
// To succeed, we must be holding the only pin, the slot must be valid, and the key must match.
259259
//
260-
if (!(Self::get_pin_count(observed_state) == 1 && this->is_valid(observed_state) &&
260+
if (!(Self::get_pin_count(observed_state) == 1 && Self::is_valid(observed_state) &&
261261
this->key_ == key)) {
262262
this->release_pin();
263263
return false;
@@ -300,7 +300,7 @@ auto PageCacheSlot::fill(PageId key) noexcept -> PinnedRef
300300
this->add_ref();
301301
this->set_valid();
302302

303-
return PinnedRef{this};
303+
return PinnedRef{this, CallerPromisesTheyAcquiredPinCount{}};
304304
}
305305

306306
//==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - -
@@ -333,7 +333,7 @@ void PageCacheSlot::notify_last_ref_released()
333333
void PageCacheSlot::set_valid()
334334
{
335335
const auto observed_state = this->state_.fetch_or(kValidMask, std::memory_order_release);
336-
BATT_CHECK(!this->is_valid(observed_state)) << "Must go from an invalid state to valid!";
336+
BATT_CHECK(!Self::is_valid(observed_state)) << "Must go from an invalid state to valid!";
337337
}
338338

339339
} //namespace llfs

src/llfs/page_cache_slot.hpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ class PageCacheSlot
107107

108108
/** \brief Returns the pin count bit field within the passed state integer.
109109
*/
110-
static constexpr u32 get_pin_count(u64 state)
110+
static constexpr u64 get_pin_count(u64 state)
111111
{
112112
return state >> kPinCountShift;
113113
}
@@ -202,7 +202,7 @@ class PageCacheSlot
202202

203203
/** \brief Returns the current pin count of the slot; if this is 0, the slot is not pinned.
204204
*/
205-
u32 pin_count() const noexcept;
205+
u64 pin_count() const noexcept;
206206

207207
/** \brief Conditionally pins the slot so it can't be evicted.
208208
*

src/llfs/page_cache_slot.test.cpp

+8-5
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ class PageCacheSlotTest : public ::testing::Test
6868
// 1. Create slots with different index values in a Pool; verify index()
6969
// - also verify initial is_valid state
7070
//
71-
TEST_F(PageCacheSlotTest, CreateSlotsDeath)
71+
TEST_F(PageCacheSlotTest, CreateSlots)
7272
{
7373
for (usize i = 0; i < kNumTestSlots; ++i) {
7474
llfs::PageCacheSlot* slot = this->pool_->allocate();
@@ -152,7 +152,8 @@ TEST_F(PageCacheSlotTest, StateTransitions)
152152
// i. Valid + Cleared --(acquire_pin)--> Valid + Cleared + Pinned
153153
//
154154
{
155-
llfs::PageCacheSlot::PinnedRef valid_cleared_ref = slot->acquire_pin(llfs::PageId{}, /*ignore_key=*/true);
155+
llfs::PageCacheSlot::PinnedRef valid_cleared_ref =
156+
slot->acquire_pin(llfs::PageId{}, /*ignore_key=*/true);
156157
EXPECT_EQ(valid_cleared_ref.value(), nullptr);
157158
EXPECT_EQ(slot->pin_count(), 1u);
158159
EXPECT_EQ(slot->ref_count(), 1u);
@@ -421,7 +422,8 @@ TEST_F(PageCacheSlotTest, FillFailureClearedDeath)
421422
slot->clear();
422423

423424
{
424-
llfs::PageCacheSlot::PinnedRef valid_cleared_ref = slot->acquire_pin(llfs::PageId{}, /*ignore_key=*/true);
425+
llfs::PageCacheSlot::PinnedRef valid_cleared_ref =
426+
slot->acquire_pin(llfs::PageId{}, /*ignore_key=*/true);
425427
EXPECT_TRUE(slot->is_valid());
426428
EXPECT_DEATH(slot->fill(llfs::PageId{2}), "Assert.*fail.*is.*valid");
427429
}
@@ -473,12 +475,13 @@ TEST_F(PageCacheSlotTest, RefCounting)
473475
continue;
474476
}
475477

476-
// Split the workload: let half the threads perform acquire_pin calls and
478+
// Split the workload: let half the threads perform acquire_pin calls and
477479
// let the other half perform evictions.
478480
//
479481
if (i % 2) {
480482
{
481-
llfs::PageCacheSlot::PinnedRef pinned = slot->acquire_pin(llfs::PageId{}, /*ignore_key=*/true);
483+
llfs::PageCacheSlot::PinnedRef pinned =
484+
slot->acquire_pin(llfs::PageId{}, /*ignore_key=*/true);
482485
}
483486
} else {
484487
slot->evict_if_key_equals(llfs::PageId{1});

src/llfs/page_cache_slot_pinned_ref.hpp

+21-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,26 @@
1212

1313
namespace llfs {
1414

15+
//==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - -
16+
//
17+
class CallerPromisesTheyAcquiredPinCount
18+
{
19+
private:
20+
CallerPromisesTheyAcquiredPinCount() = default;
21+
22+
//+++++++++++-+-+--+----- --- -- - - - -
23+
// The following declared "friend" functions are the only places that should be acquiring a pin on
24+
// a cache slot and using it to create a PinnedRef!
25+
//+++++++++++-+-+--+----- --- -- - - - -
26+
27+
friend auto PageCacheSlot::acquire_pin(PageId key,
28+
bool ignore_key) noexcept -> PageCacheSlot::PinnedRef;
29+
30+
friend auto PageCacheSlot::fill(PageId key) noexcept -> PageCacheSlot::PinnedRef;
31+
};
32+
33+
//==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - -
34+
//
1535
class PageCacheSlot::PinnedRef : public boost::equality_comparable<PageCacheSlot::PinnedRef>
1636
{
1737
public:
@@ -22,7 +42,7 @@ class PageCacheSlot::PinnedRef : public boost::equality_comparable<PageCacheSlot
2242
PinnedRef() = default;
2343

2444
private:
25-
explicit PinnedRef(PageCacheSlot* slot) noexcept
45+
explicit PinnedRef(PageCacheSlot* slot, CallerPromisesTheyAcquiredPinCount) noexcept
2646
: slot_{slot}
2747
, value_{slot ? slot->value() : nullptr}
2848
{

src/llfs/storage_simulation.cpp

+22
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,18 @@ class StorageSimulation::TaskSchedulerImpl : public batt::TaskScheduler
4949

5050
//=#=#==#==#===============+=+=+=+=++=++++++++++++++-++-+--+-+----+---------------
5151

52+
//==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - -
53+
//
54+
/*explicit*/ StorageSimulation::StorageSimulation(RandomSeed seed) noexcept
55+
: StorageSimulation{batt::StateMachineEntropySource{
56+
/*entropy_fn=*/[rng = std::make_shared<std::default_random_engine>(seed)](
57+
usize min_value, usize max_value) -> usize {
58+
std::uniform_int_distribution<usize> pick_value{min_value, max_value};
59+
return pick_value(*rng);
60+
}}}
61+
{
62+
}
63+
5264
//==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - -
5365
//
5466
/*explicit*/ StorageSimulation::StorageSimulation(
@@ -423,6 +435,16 @@ const batt::SharedPtr<PageCache>& StorageSimulation::init_cache() noexcept
423435
return this->cache_;
424436
}
425437

438+
//==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - -
439+
//
440+
std::unique_ptr<LogDeviceFactory> StorageSimulation::get_log_device_factory(const std::string& name,
441+
Optional<u64> capacity)
442+
{
443+
return std::make_unique<BasicLogDeviceFactory>([this, name, capacity] {
444+
return this->get_log_device(name, capacity);
445+
});
446+
}
447+
426448
//==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - -
427449
//
428450
StatusOr<std::unique_ptr<Volume>> StorageSimulation::get_volume(

src/llfs/storage_simulation.hpp

+56
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ class StorageSimulation
5454

5555
//+++++++++++-+-+--+----- --- -- - - - -
5656

57+
/** \brief Creates a new StorageSimulation using a default random engine seeded with the passed
58+
* value.
59+
*/
60+
explicit StorageSimulation(RandomSeed seed) noexcept;
61+
5762
/** \brief Creates a new StorageSimulation context that draws from the passed entropy source.
5863
*/
5964
explicit StorageSimulation(batt::StateMachineEntropySource&& entropy_source) noexcept;
@@ -121,13 +126,42 @@ class StorageSimulation
121126
*/
122127
batt::TaskScheduler& task_scheduler() noexcept;
123128

129+
/** \brief Convenience: invokes this->task_scheduler().schedule_task() and returns the resulting
130+
* executor.
131+
*/
132+
boost::asio::any_io_executor schedule_task() noexcept
133+
{
134+
return this->task_scheduler().schedule_task();
135+
}
136+
137+
/** \brief Convenience: invokes this->entropy_source().pick_int(min_value, max_value).
138+
*/
139+
usize pick_int(usize min_value, usize max_value) const
140+
{
141+
return this->entropy_source().pick_int(min_value, max_value);
142+
}
143+
144+
/** \brief Convenience: invokes this->entropy_source().pick_branch().
145+
*/
146+
bool pick_branch() const
147+
{
148+
return this->entropy_source().pick_branch();
149+
}
150+
124151
/** \brief Returns a reference to the entropy source passed in at construction time.
125152
*/
126153
batt::StateMachineEntropySource& entropy_source() noexcept
127154
{
128155
return this->entropy_source_;
129156
}
130157

158+
/** \brief Returns a reference to the entropy source passed in at construction time.
159+
*/
160+
const batt::StateMachineEntropySource& entropy_source() const noexcept
161+
{
162+
return this->entropy_source_;
163+
}
164+
131165
/** \brief Returns whether the simulation is currently injecting failures randomly into system
132166
* components.
133167
*
@@ -203,6 +237,15 @@ class StorageSimulation
203237
*/
204238
std::unique_ptr<LogDevice> get_log_device(const std::string& name, Optional<u64> capacity = None);
205239

240+
/** \brief Creates/accesses a simulated LogDevice via the LogDeviceFactory interface.
241+
*
242+
* \param name A unique name used to identify the LogDevice in the context of this simulation
243+
* \param capacity The maximum size in bytes of the log; must be specified the first time this
244+
* function is called for a given name (i.e., when the log is initially created)
245+
*/
246+
std::unique_ptr<LogDeviceFactory> get_log_device_factory(const std::string& name,
247+
Optional<u64> capacity = None);
248+
206249
/** \brief Creates/accesses a simulated PageDevice.
207250
*
208251
* The args `page_count` and `page_size` must be specified the first time this
@@ -253,6 +296,19 @@ class StorageSimulation
253296
return this->step_.get_value();
254297
}
255298

299+
/** \brief Blocks the calling task until the simulation step has reached or exceeded the specified
300+
* value.
301+
*
302+
* \return the new step value, or error code if the simulation was interrupted/ended before
303+
* the target step was reached.
304+
*/
305+
StatusOr<u64> await_step(u64 minimum_step) noexcept
306+
{
307+
return this->step_.await_true([minimum_step](u64 observed_step) {
308+
return observed_step >= minimum_step;
309+
});
310+
}
311+
256312
/** \brief Returns true if all blocks for a given page are present in a simulated PageDevice,
257313
* false if they are all absent, error status code otherwise.
258314
*/

0 commit comments

Comments
 (0)