mirror of
https://github.com/vicliu624/trail-mate.git
synced 2026-09-25 13:13:37 +00:00
perf(geocaching): dispatch known queries without history scans
This commit is contained in:
@@ -152,6 +152,14 @@ class QueryClient
|
||||
}
|
||||
bool hasMore() const { return phase_ == QueryClientPhase::PageReady && cursor_size_ != 0; }
|
||||
|
||||
bool pendingRequest(Destination& destination, RequestId& id) const
|
||||
{
|
||||
if (!selected_ || (phase_ != QueryClientPhase::CheckingCapabilities && phase_ != QueryClientPhase::Querying)) return false;
|
||||
destination = selected_->delivery;
|
||||
id = request_;
|
||||
return true;
|
||||
}
|
||||
|
||||
// A live request is already identified in memory. Historical receipt
|
||||
// lookups are only needed for retries after it has left the waiting state.
|
||||
bool expectsResponse(const Destination& source, const RequestId& id) const
|
||||
|
||||
@@ -44,7 +44,12 @@ int main(int argc, char** argv)
|
||||
app.insert(app.end(), {0, 0xa1, 'x'});
|
||||
assert(client->observe(discovery, delivery, {key.data(), 64}, {app.data(), app.size()}, 0));
|
||||
assert(client->query({300000000, 1200000000, 310000000, 1210000000}));
|
||||
geocaching::Destination pending_destination;
|
||||
geocaching::RequestId pending_id;
|
||||
assert(!client->pendingRequest(pending_destination, pending_id));
|
||||
assert(client->tick(0) && port.requests == 1);
|
||||
assert(client->pendingRequest(pending_destination, pending_id) && pending_destination.bytes == delivery.bytes);
|
||||
assert(pending_id.bytes[0] == 1);
|
||||
geocaching::RequestId active;
|
||||
active.bytes.fill(1);
|
||||
assert(client->expectsResponse(delivery, active));
|
||||
@@ -64,4 +69,5 @@ int main(int argc, char** argv)
|
||||
assert(port.requests == 2 && port.pages == 1 && client->phase() == geocaching::QueryClientPhase::PageReady);
|
||||
active.bytes.fill(3);
|
||||
assert(!client->expectsResponse(delivery, active));
|
||||
assert(!client->pendingRequest(pending_destination, pending_id));
|
||||
}
|
||||
|
||||
@@ -274,15 +274,18 @@ int checkIncrementalDispatch()
|
||||
return SdRequestStore::expireOneAttempt(now, started, timeout, expired);
|
||||
}
|
||||
};
|
||||
for (unsigned scenario = 0; scenario < 8; ++scenario)
|
||||
for (unsigned scenario = 0; scenario < 16; ++scenario)
|
||||
{
|
||||
const auto mode = scenario % 4;
|
||||
const bool foreground = scenario >= 8;
|
||||
const std::array<uint8_t, 48> preferred{};
|
||||
fixture::files.clear();
|
||||
fixture::flush_ok = true;
|
||||
uint8_t first[2048]{}, second[2048]{}, request[128]{};
|
||||
::geocaching::storage::LogicalState state(first, second, sizeof(first));
|
||||
auto store = std::make_unique<DelayedStore>(fixture::volume, 0, state);
|
||||
store->selection_wait = store->send_wait = scenario >= 4 ? 3 : 0;
|
||||
store->send_wait = scenario % 8 >= 4 ? 3 : 0;
|
||||
store->selection_wait = foreground ? 0 : store->send_wait;
|
||||
::geocaching::RequestId id;
|
||||
size_t size = 0;
|
||||
if (!::geocaching::protocol::encodeCapabilitiesRequest(id, request, sizeof(request), size) ||
|
||||
@@ -304,7 +307,8 @@ int checkIncrementalDispatch()
|
||||
fixture::step_io_calls = fixture::step_data_calls = 0;
|
||||
fixture::step_data_bytes = 0;
|
||||
const auto old_sends = router.sends;
|
||||
const auto result = dispatcher->dispatchOne({});
|
||||
const auto result = dispatcher->dispatchOne({}, foreground ? ::geocaching::ByteView{preferred.data(), preferred.size()} : ::geocaching::ByteView{});
|
||||
if (foreground && store->expiration_calls) return 242;
|
||||
if (store->selection_wait && (router.sends || store->expiration_calls != 1)) return 240;
|
||||
if (store->send_wait && router.sends) return 241;
|
||||
if (!stepBudgetOk()) return 112;
|
||||
@@ -313,6 +317,13 @@ int checkIncrementalDispatch()
|
||||
{
|
||||
if (router.sends != 1 || store->committedSequence() != 3 ||
|
||||
router.sent_bytes.size() != size || std::memcmp(router.sent_bytes.data(), request, size)) return 114;
|
||||
// A repeated hint after submission must not start another
|
||||
// attempt against the already in-flight durable request.
|
||||
if (foreground)
|
||||
{
|
||||
const auto repeated = dispatcher->dispatchOne({}, {preferred.data(), preferred.size()});
|
||||
if (repeated.status == DispatchStatus::StorageBlocked || router.sends != 1 || store->committedSequence() != 3) return 243;
|
||||
}
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
|
||||
+6
-2
@@ -25,7 +25,9 @@ class RequestDispatcher
|
||||
public:
|
||||
RequestDispatcher(chat::MeshAdapterRouter& router, RequestDispatchStore& store,
|
||||
uint32_t retry_delay_ms, uint64_t attempt_timeout_ms);
|
||||
DispatchResult dispatchOne(const ::geocaching::storage::StoredTime& now);
|
||||
// Optional key of a newly committed foreground request. It bypasses only
|
||||
// history selection; beginAttempt/readForSend still validate durable state.
|
||||
DispatchResult dispatchOne(const ::geocaching::storage::StoredTime& now, ::geocaching::ByteView preferred = {});
|
||||
|
||||
private:
|
||||
enum class Phase : uint8_t
|
||||
@@ -51,6 +53,8 @@ class RequestDispatcher
|
||||
std::array<uint8_t, 16> boot_{};
|
||||
std::array<uint8_t, 48> cursor_{};
|
||||
bool has_cursor_ = false;
|
||||
std::array<uint8_t, 48> preferred_{};
|
||||
bool has_preferred_ = false;
|
||||
};
|
||||
static_assert(sizeof(RequestDispatcher) <= 192, "Dispatcher retains metadata only, not a request-sized buffer");
|
||||
static_assert(sizeof(RequestDispatcher) <= 256, "Dispatcher retains metadata only, not a request-sized buffer");
|
||||
} // namespace platform::esp::arduino_common::geocaching
|
||||
|
||||
+13
-6
@@ -53,7 +53,7 @@ class SdIndexLookup
|
||||
if (inspectSdVolume(current) != SdVolumeResult::Ready) return fail(IndexLookupStep::IoError);
|
||||
if (current != volume_) return fail(IndexLookupStep::VolumeChanged);
|
||||
if (phase_ == Phase::VerifyVolume) return result_ = completion_;
|
||||
phase_ = length_ ? Phase::Probe : Phase::VerifyVolume;
|
||||
phase_ = length_ ? Phase::Open : Phase::VerifyVolume;
|
||||
return result_;
|
||||
}
|
||||
if (phase_ == Phase::Probe || phase_ == Phase::Open)
|
||||
@@ -64,12 +64,18 @@ class SdIndexLookup
|
||||
{
|
||||
const auto probe = storage::sd_read_file(path, bytes_.data(), 1);
|
||||
if (probe.status == storage::SdFileReadStatus::Missing) return fail(IndexLookupStep::Invalid);
|
||||
if (probe.status != storage::SdFileReadStatus::Ready && probe.status != storage::SdFileReadStatus::Invalid) return fail(IndexLookupStep::IoError);
|
||||
if (probe.file_size < length_) return fail(IndexLookupStep::Invalid);
|
||||
phase_ = Phase::Open;
|
||||
return result_;
|
||||
// Only classify an unsuccessful open. A present file that
|
||||
// could not be opened is an I/O error, never an empty index.
|
||||
return fail(IndexLookupStep::IoError);
|
||||
}
|
||||
if (!file_.open(path, "r")) return fail(IndexLookupStep::IoError);
|
||||
// Avoid the old one-byte whole-file probe: normal shards always
|
||||
// exceeded it, logged Invalid, then had to be opened a second time.
|
||||
phase_ = file_.open(path, "r") ? Phase::CheckLength : Phase::Probe;
|
||||
return result_;
|
||||
}
|
||||
if (phase_ == Phase::CheckLength)
|
||||
{
|
||||
if (file_.size() < length_) return fail(IndexLookupStep::Invalid);
|
||||
phase_ = Phase::Seek;
|
||||
return result_;
|
||||
}
|
||||
@@ -129,6 +135,7 @@ class SdIndexLookup
|
||||
Volume,
|
||||
Probe,
|
||||
Open,
|
||||
CheckLength,
|
||||
Seek,
|
||||
Read,
|
||||
CheckSize,
|
||||
|
||||
@@ -2373,7 +2373,17 @@ void step()
|
||||
next_step.store(millis() + 1000);
|
||||
return;
|
||||
}
|
||||
const auto sent = s.dispatcher->dispatchOne(now(nullptr));
|
||||
gc::Destination destination;
|
||||
gc::RequestId request;
|
||||
std::array<uint8_t, 48> preferred{};
|
||||
const bool foreground = s.client->pendingRequest(destination, request);
|
||||
if (foreground)
|
||||
{
|
||||
std::memcpy(preferred.data(), s.local.bytes.data(), 16);
|
||||
std::memcpy(preferred.data() + 16, destination.bytes.data(), 16);
|
||||
std::memcpy(preferred.data() + 32, request.bytes.data(), 16);
|
||||
}
|
||||
const auto sent = s.dispatcher->dispatchOne(now(nullptr), foreground ? gc::ByteView{preferred.data(), preferred.size()} : gc::ByteView{});
|
||||
if (sent.status == DispatchStatus::StorageBlocked || sent.status == DispatchStatus::Corrupt)
|
||||
fail("Query storage is blocked");
|
||||
else if (!s.dispatch_store->busy()) next_step.store(millis() + 250);
|
||||
|
||||
@@ -7,7 +7,7 @@ RequestDispatcher::RequestDispatcher(chat::MeshAdapterRouter& router, RequestDis
|
||||
uint32_t retry_delay_ms, uint64_t attempt_timeout_ms)
|
||||
: router_(router), store_(store), retry_delay_ms_(retry_delay_ms), attempt_timeout_ms_(attempt_timeout_ms) {}
|
||||
|
||||
DispatchResult RequestDispatcher::dispatchOne(const ::geocaching::storage::StoredTime& now)
|
||||
DispatchResult RequestDispatcher::dispatchOne(const ::geocaching::storage::StoredTime& now, ::geocaching::ByteView preferred)
|
||||
{
|
||||
using namespace ::geocaching::storage;
|
||||
if (store_.needsRecovery()) return {DispatchStatus::StorageBlocked};
|
||||
@@ -15,6 +15,24 @@ DispatchResult RequestDispatcher::dispatchOne(const ::geocaching::storage::Store
|
||||
{
|
||||
not_before_ = now.monotonic_ms > UINT64_MAX - retry_delay_ms_ ? UINT64_MAX : now.monotonic_ms + retry_delay_ms_;
|
||||
};
|
||||
const auto begin = [&](const ::geocaching::Destination& local) -> DispatchResult
|
||||
{
|
||||
esp_fill_random(attempt_id_.data(), attempt_id_.size());
|
||||
const auto begun = store_.beginAttempt(local, {cursor_.data(), cursor_.size()}, attempt_id_, now);
|
||||
if (begun == JournalWriteResult::InProgress)
|
||||
{
|
||||
phase_ = Phase::BeginCommit;
|
||||
return {DispatchStatus::Deferred};
|
||||
}
|
||||
if (begun == JournalWriteResult::Verified)
|
||||
{
|
||||
phase_ = Phase::Send;
|
||||
return {DispatchStatus::Deferred};
|
||||
}
|
||||
return {begun == JournalWriteResult::StateRejected || begun == JournalWriteResult::Busy
|
||||
? DispatchStatus::Deferred
|
||||
: DispatchStatus::StorageBlocked};
|
||||
};
|
||||
if (phase_ != Phase::Select && phase_ != Phase::SelectRequest && phase_ != Phase::Send)
|
||||
{
|
||||
const auto result = store_.stepCommit();
|
||||
@@ -82,11 +100,36 @@ DispatchResult RequestDispatcher::dispatchOne(const ::geocaching::storage::Store
|
||||
boot_ = now.boot_id;
|
||||
not_before_ = 0;
|
||||
has_cursor_ = false;
|
||||
has_preferred_ = false;
|
||||
recovery_started_ms_ = now.monotonic_ms;
|
||||
clock_initialized_ = true;
|
||||
}
|
||||
if (phase_ == Phase::Select)
|
||||
{
|
||||
// A background scan may already have selected the same request before
|
||||
// the caller supplied its hint. Do not reserve it a second time.
|
||||
if (preferred.data && preferred.size == preferred_.size() && has_cursor_ &&
|
||||
!std::memcmp(preferred.data, cursor_.data(), cursor_.size()))
|
||||
{
|
||||
preferred_ = cursor_;
|
||||
has_preferred_ = true;
|
||||
}
|
||||
if (preferred.data && preferred.size == preferred_.size() &&
|
||||
(!has_preferred_ || std::memcmp(preferred.data, preferred_.data(), preferred_.size())))
|
||||
{
|
||||
::geocaching::Destination local;
|
||||
if (!router_.getGeocachingDispatchDestination(local.bytes.data()) || std::memcmp(preferred.data, local.bytes.data(), 16))
|
||||
return {DispatchStatus::Deferred, chat::MeshOperationFailure::NotReady};
|
||||
std::memcpy(cursor_.data(), preferred.data, cursor_.size());
|
||||
has_cursor_ = true;
|
||||
const auto result = begin(local);
|
||||
if (phase_ == Phase::BeginCommit || phase_ == Phase::Send)
|
||||
{
|
||||
preferred_ = cursor_;
|
||||
has_preferred_ = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
bool expired = false;
|
||||
const auto expiration = store_.expireOneAttempt(now, recovery_started_ms_, attempt_timeout_ms_, expired);
|
||||
if (expiration == JournalWriteResult::Busy) return {DispatchStatus::Deferred};
|
||||
@@ -121,20 +164,6 @@ DispatchResult RequestDispatcher::dispatchOne(const ::geocaching::storage::Store
|
||||
if (selected == DispatchReadResult::None) return {DispatchStatus::Idle};
|
||||
cursor_ = pending.key;
|
||||
has_cursor_ = true;
|
||||
esp_fill_random(attempt_id_.data(), attempt_id_.size());
|
||||
const auto begun = store_.beginAttempt(local, {cursor_.data(), cursor_.size()}, attempt_id_, now);
|
||||
if (begun == JournalWriteResult::InProgress)
|
||||
{
|
||||
phase_ = Phase::BeginCommit;
|
||||
return {DispatchStatus::Deferred};
|
||||
}
|
||||
if (begun == JournalWriteResult::Verified)
|
||||
{
|
||||
phase_ = Phase::Send;
|
||||
return {DispatchStatus::Deferred};
|
||||
}
|
||||
return {begun == JournalWriteResult::StateRejected || begun == JournalWriteResult::Busy
|
||||
? DispatchStatus::Deferred
|
||||
: DispatchStatus::StorageBlocked};
|
||||
return begin(local);
|
||||
}
|
||||
} // namespace platform::esp::arduino_common::geocaching
|
||||
|
||||
Reference in New Issue
Block a user