Skip to content

CraftReplDev write path#165

Merged
shosseinimotlagh merged 3 commits into
eBay:dev/v6.xfrom
sbinmalek:craft-write-path
Jul 21, 2026
Merged

CraftReplDev write path#165
shosseinimotlagh merged 3 commits into
eBay:dev/v6.xfrom
sbinmalek:craft-write-path

Conversation

@sbinmalek

@sbinmalek sbinmalek commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Implements the client-facing write path for CraftReplDev (S2 of the CRAFT epic). Builds on the S1 foundation; the next subtask (S3) will add read, commit, and keep-alive.

  • STALE_TERM error — new volume_error enum value returned when a write arrives with a term older than state_.term; callers can branch on it to trigger re-login
  • HomeStoreCraftJournalBackend stub — production backend skeleton in craft_repl_dev.cpp; all three methods (write_slot, read_slot, truncate_to) log a warning and return not_supported pending HomeStore CRAFT journal APIs
  • CraftReplDev::write() — real implementation: term check → missing-set + last_append_lsn update (under missing_mu_) → co_await journal_->write_slot(); out-of-order arrivals are tracked in missing_lsns_ and filled in on subsequent writes
  • Unit tests — MockCraftJournalBackend (in-memory std::map-backed) + CraftWriteTest fixture with three cases: in-order writes, out-of-order gap tracking, and STALE_TERM rejection

Test plan

  • CraftWriteTest/InOrderWrites — lsns 0,1,2 sequential; missing set stays empty; last_append_lsn = 2
  • CraftWriteTest/OutOfOrderWrites — jump to lsn=4 creates {1,2,3} in missing set; filling in clears it
  • CraftWriteTest/TermRejection — term=1 against state_.term=0 → STALE_TERM; journal untouched
  • All existing volume tests still pass

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements the client-facing write path for CraftReplDev (CRAFT epic S2), introducing a new volume_error::STALE_TERM for session/term rejection and adding initial unit tests plus a stub production journal backend factory.

Changes:

  • Add CraftReplDev::write() implementation with term checking and missing-LSN tracking.
  • Introduce HomeStoreCraftJournalBackend stub + make_homestore_journal_backend() factory for production wiring.
  • Add CraftWriteTest with an in-memory journal backend and CMake integration for craft tests.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/lib/craft/tests/test_craft_write.cpp Adds unit tests and an in-memory MockCraftJournalBackend to validate write ordering and term rejection behavior.
src/lib/craft/tests/CMakeLists.txt Adds test_craft_write target and registers it with CTest.
src/lib/craft/craft_repl_dev.hpp Adds missing-set observability helpers, makes the mutex mutable, and declares the HomeStore journal backend factory.
src/lib/craft/craft_repl_dev.cpp Adds the production backend stub + factory and replaces the write() stub with a real implementation.
src/lib/craft/CMakeLists.txt Adds tests/ subdirectory to the craft build.
src/include/homeblks/home_blocks.hpp Extends volume_error with STALE_TERM.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/lib/craft/craft_repl_dev.cpp Outdated
Comment on lines +85 to +107
// 2. Update missing set and last_append_lsn under lock.
// Done before the journal write so the missing set is always conservative:
// a failed write leaves the LSN in the set (correctly marked absent).
{
std::lock_guard lock{missing_mu_};
// Any LSNs between the old last_append and this one are gaps.
for (int64_t gap = state_.last_append_lsn + 1; gap < lsn; ++gap) {
missing_lsns_.insert(gap);
}
// This slot is being filled.
missing_lsns_.erase(lsn);
state_.last_append_lsn = std::max(state_.last_append_lsn, lsn);
}

// 3. Journal append — zero-copy; data buffer is not copied on the hot path.
auto res = co_await journal_->write_slot(lsn, lba, len, std::move(data));
if (!res) {
LOGE("write_slot failed lsn={} lba={} len={}: {}", lsn, lba, len, res.error().message());
co_return std::unexpected(res.error());
}

LOGT("write ok lsn={} lba={} len={}", lsn, lba, len);
co_return homestore::ok();
Comment thread src/lib/craft/craft_repl_dev.cpp Outdated
Comment on lines +79 to +83
// 1. Term check — old-session client; must re-login.
if (term != state_.term) {
LOGW("write rejected: stale term want={} got={} lsn={}", state_.term, term, lsn);
co_return std::unexpected(make_error_condition(volume_error::STALE_TERM));
}
Comment thread src/include/homeblks/home_blocks.hpp Outdated
Comment on lines +69 to +71
ENUM(volume_error, uint16_t, UNKNOWN_VOLUME = 1, CRC_MISMATCH, INDEX_ERROR, INTERNAL_ERROR, OFFLINE,
STALE_TERM
);
// Term rejection: write with term != state_.term returns STALE_TERM.
// Journal must not be touched.
TEST_F(CraftWriteTest, TermRejection) {
// state_.term starts at 0; term=1 is stale.
@sbinmalek
sbinmalek marked this pull request as ready for review June 24, 2026 22:13
@sbinmalek
sbinmalek requested a review from Copilot July 2, 2026 17:50

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.

Comment thread src/lib/craft/craft_repl_dev.cpp Outdated
Comment thread src/lib/craft/craft_repl_dev.cpp Outdated
Comment thread src/lib/craft/craft_repl_dev.cpp Outdated
Comment on lines +20 to +22
// ─── HomeStoreCraftJournalBackend ─────────────────────────────────────────────
//
// Production journal backend. Wraps the HomeStore data-journal at client-assigned

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed

Comment thread src/lib/craft/craft_repl_dev.hpp Outdated
Comment on lines +133 to +137
// Term rejection: write with term != state_.term returns STALE_TERM.
// Journal must not be touched.
TEST_F(CraftWriteTest, TermRejection) {
// state_.term starts at 0; term=1 is stale.
auto res = do_write(1 /* wrong term */, 0);
Comment thread src/lib/craft/tests/test_craft_write.cpp Outdated

@shosseinimotlagh shosseinimotlagh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: add ticket number on the pr title (for instance : SDSTOR-22385)

@szmyd szmyd left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed against the S2 acceptance criteria in docs/craft/subtasks.md and the CRAFT-on-HomeBlocks wiki. Leaving signature/return-type mismatches for the conflict resolution pass - a few design-level issues worth addressing first.


all_zeros thin write is missing (required by S2 AC)

The S2 acceptance criteria explicitly require:

write(term, lsn, lba, len, all_zeros, data) with all_zeros=true skips allocation and the data-service write; journal a metadata-only slot {term, lsn, lba, len, all_zeros}. Both forms take a dLSN and merge by highest-per-LBA.

JournalSlot already has bool all_zeros{false}. There is no all_zeros parameter in write(), no zero-write branch, and no corresponding test. The AC explicitly calls out a test case: "a zero (all_zeros) write allocates no blocks and merges over/under a data write by dLSN." This is required in S2, not deferred to S3.


write_slot API contradicts the HS_DATA_LINKED pattern

The wiki is explicit (Journal backing):

A CRAFT write does not put the payload in the journal: blocks are allocated up front, the payload is written once via the data service, and the journal slot records only the reference {term, lsn, lba, len, blkid}.

The current write_slot(lsn, lba, len, sisl::sg_list data) passes the raw payload into the backend. The production HomeStoreCraftJournalBackend cannot follow the HS_DATA_LINKED pattern with this API — the data-service call lives in CraftReplDev::write(), and only the resulting blkid should cross into the backend:

virtual async_status write_slot(int64_t lsn, lba_t lba, lba_count_t len,
                                homestore::MultiBlkId blkid) = 0;

sisl::sg_list belongs on read_slot / fetch_data return paths (after reading blocks back from the data service), not on write_slot. The mock can keep empty blkids for now; the point is the abstraction boundary is wrong and will force a re-API when the real backend lands.


glsn parameter has no basis in the design

The S2 AC signature is write(term, lsn, lba, len, data). There is no global LSN in the protocol — CRAFT is fully dLSN-based. If this is bridging something from the wire format, it should be named and documented here. If it's speculative, drop it.


Missing test: journal write failure

The central invariant of the pre-insert scheme is that a failed write_slot leaves lsn in missing_lsns_ without corrupting future gap tracking. That is the most important property of the approach and it is untested. A MockCraftJournalBackend that can be told to fail on a specific LSN would cover this.

@shosseinimotlagh

Copy link
Copy Markdown
Contributor

@copilot resolve the merge conflicts in this pull request

Resolves the conflict between sbinmalek's PR eBay#165 (write path) and the
current dev/v6.x API. Changes applied to a clean branch from origin/dev/v6.x:

- home_blocks.hpp: add STALE_TERM to volume_error enum (server-internal
  counterpart to craft::craft_error::STALE_TERM on the client wire)

- craft_repl_dev.hpp: add missing_count()/is_missing() test-observability
  helpers (const, lock mutable missing_mu_), add
  make_homestore_journal_backend() factory declaration

- craft_repl_dev.cpp: add HomeStoreCraftJournalBackend stub (stubs for
  write_slot/read_slot/truncate_to with LOGW; S2 through S4 fill them),
  implement CraftReplDev::write() with term check, gap-tracking in
  missing_lsns_, lock-before-I/O pattern, lsn_pair snapshot return

- craft/CMakeLists.txt: drop stale reference-model comment, wire up
  add_subdirectory(tests)

- craft/tests/CMakeLists.txt: direct-compile test target for
  test_craft_write (no ${PROJECT_NAME} dep — avoids HomeStore plumbing)

- craft/tests/test_craft_write.cpp: three tests — InOrderWrites,
  OutOfOrderWrites, TermRejection — using MockCraftJournalBackend
- write() now takes craft::client_hdr (carries term/commit_lsn/all_committed_lsn)
  and byte-addressed uint64_t addr/len instead of raw term + lba_t/lba_count_t.
  Term check uses hdr.term; addr/len are cast to lba_t/lba_count_t for the
  journal backend (byte-to-block division is deferred until lba_size is wired).
- read() and request_resolution() stubs updated to matching signatures.
- craft_api.cpp: dispatch through CraftReplDev* (via volume::craft_dev()) instead
  of the craft::craft_replica abstract interface, which is not installed by any
  version of craft_client. Matches craft_truncate exactly.
- volume.hpp: remove craft::craft_replica forward-declaration; include
  craft_repl_dev.hpp directly and expose craft_dev() returning CraftReplDev*.
- test: do_write() passes craft::client_hdr{term,...} to match new signature.
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 0% with 2 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (dev/v6.x@4847a0d). Learn more about missing BASE report.

Files with missing lines Patch % Lines
src/include/homeblks/home_blocks.hpp 0.00% 1 Missing ⚠️
src/lib/craft/craft_repl_dev.cpp 0.00% 0 Missing and 1 partial ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files
@@             Coverage Diff             @@
##             dev/v6.x     #165   +/-   ##
===========================================
  Coverage            ?   45.09%           
===========================================
  Files               ?       18           
  Lines               ?     1020           
  Branches            ?      442           
===========================================
  Hits                ?      460           
  Misses              ?      273           
  Partials            ?      287           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@shosseinimotlagh
shosseinimotlagh self-requested a review July 21, 2026 06:10
@shosseinimotlagh
shosseinimotlagh merged commit 7c7fa4f into eBay:dev/v6.x Jul 21, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants