Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
9f4ef22
craft: S2 write path — journal append at client-assigned LSN
shosseinimotlagh Jul 22, 2026
1e4fa01
craft: harden write() against data race, DoS gap, and missing/empty i…
shosseinimotlagh Jul 22, 2026
bdee8ef
craft: round-2 hardening — overflow guard, idempotent write, gap-loop…
shosseinimotlagh Jul 22, 2026
34870d6
craft: fix block leaks on write_slot failure and stale-term discard; …
shosseinimotlagh Jul 22, 2026
a7dbf2a
craft: address Copilot review comments — error codes and missing allo…
shosseinimotlagh Jul 23, 2026
7c702e7
craft: address second Copilot review — docstring, precondition assert…
shosseinimotlagh Jul 23, 2026
130bbad
craft: extend DuplicateWriteIsIdempotent with cross-type same-dLSN cases
shosseinimotlagh Jul 23, 2026
1da78a6
craft: harden CraftJournalEntry — add magic, version, term, lsn fields
shosseinimotlagh Aug 4, 2026
c76f530
craft: replace LogstoreWriteAwaitable with value_awaitable + iomgr re…
shosseinimotlagh Aug 4, 2026
48431f8
craft: remove free_data from post-flight stale path; drop CraftJourna…
shosseinimotlagh Aug 4, 2026
1f9ff08
craft: apply sbinmalek nit — replace .count() with .contains() in mock
shosseinimotlagh Aug 4, 2026
cfe0187
craft: correct write_slot lost-completion comment; guard shutdown case
shosseinimotlagh Aug 24, 2026
b91073a
craft: cap cumulative missing_lsns_ growth independent of per-write gap
shosseinimotlagh Aug 24, 2026
e79d758
craft: fix Guard 3 to exempt zero-gap in-order writes, add corner-cas…
shosseinimotlagh Aug 24, 2026
4d110c3
craft: document last_append_lsn eager-advance against CRAFT-Design; t…
shosseinimotlagh Aug 24, 2026
57f4090
craft: rename JournalSlot lba/len to lba_off_bytes/len_bytes
shosseinimotlagh Aug 24, 2026
fe6dfe0
craft: fix write_async return-value guard off-by-one
shosseinimotlagh Aug 24, 2026
84354b9
craft: add HomeStoreCraftJournalBackend test against real HomeStore
shosseinimotlagh Aug 24, 2026
5ffc5a8
volume: fix null-pointer dereference in VolumeChunkSelector::select_c…
shosseinimotlagh Aug 24, 2026
394a1d8
craft: extend real-backend test with truncate_to and alloc_write_data…
shosseinimotlagh Aug 24, 2026
0c90782
craft: add zero-copy regression test for the write path
shosseinimotlagh Aug 24, 2026
ef3e230
craft: lock down CraftJournalEntry on-disk format
shosseinimotlagh Aug 24, 2026
4076a40
craft: reject all_zeros=true with a non-empty payload
shosseinimotlagh Aug 24, 2026
8c97370
craft: guard against multi_blk_id::serialize() size mismatch
shosseinimotlagh Aug 24, 2026
1fd2db5
craft: trim reviewer/ticket references from in-source comments
shosseinimotlagh Aug 24, 2026
6717c83
craft: plumb all_zeros through the public CRAFT async_write() free fu…
shosseinimotlagh Aug 28, 2026
d589114
craft: simplify alloc guard to if (!all_zeros)
shosseinimotlagh Aug 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions conanfile.py
Comment thread
shosseinimotlagh marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

class HomeBlocksConan(ConanFile):
name = "homeblocks"
version = "6.0.4"
version = "6.0.5"

homepage = "https://github.com/eBay/HomeBlocks"
description = "Block Store built on HomeStore"
Expand Down Expand Up @@ -48,13 +48,13 @@ def configure(self):

def build_requirements(self):
self.test_requires("gtest/[^1.17]")
self.test_requires("ublkpp/[^0.35]@oss/main")
self.test_requires("ublkpp/[^0.36]@oss/main")

def requirements(self):
self.requires("homestore/[^8.0]@oss/dev", transitive_headers=True)
self.requires("iomgr/[^13.0]@oss/dev", transitive_headers=True)
self.requires("sisl/[^14.8]@oss/dev", transitive_headers=True)
self.requires("craft_client/0.3.0@oss/dev", transitive_headers=True) # the extracted CRAFT wire + client + reference
self.requires("craft_client/0.4.0@oss/dev", transitive_headers=True) # the extracted CRAFT wire + client + reference

def validate(self):
if self.info.settings.compiler.cppstd:
Expand Down
14 changes: 9 additions & 5 deletions src/include/homeblks/home_blocks.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ using volume_handle = std::shared_ptr< volume >;
// ride result<T> while staying branchable: if (r.error() == volume_error::CRC_MISMATCH) { ... }. Anything with a
// standard equivalent (invalid arg, no space, io error, unsupported op, ...) is returned as
// std::make_error_condition(std::errc::*) directly rather than duplicated here.
ENUM(volume_error, uint16_t, UNKNOWN_VOLUME = 1, CRC_MISMATCH, INDEX_ERROR, INTERNAL_ERROR, OFFLINE, STALE_TERM);
ENUM(volume_error, uint16_t, UNKNOWN_VOLUME = 1, CRC_MISMATCH, INDEX_ERROR, INTERNAL_ERROR, OFFLINE, STALE_TERM,
EMPTY_SLOT);

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.

EMPTY_SLOT has no representation on the CRAFT wire. craft_error in craft_client is STALE_TERM, NOT_LEADER, NO_QUORUM, WRONG_TOKEN, NOT_ELIGIBLE, REPLICA_DOWN (+ INTERNAL in szmyd/craft_client#2), and the wire status byte mirrors 1-6, so this condition can't survive to_wire_status — a client writing into an Empty-verdicted slot gets a generic failure instead of the specific one.

Worth adding the matching value to craft_error and the wire status table in the same cycle as craft_client#2, otherwise the specificity added here is lost at the transport boundary.

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.

Verified directly against the craft_client headers — confirmed craft_error/to_wire_status only cover the six values, no Empty-slot equivalent.

Posted a review comment on craft_client#2 directly (it's still open, and already touching this exact enum for INTERNAL/NOT_IMPLEMENTED), asking for an Empty-slot value to be added there while that window is open, rather than deferring to S9. Kept SDSTOR-25280 as a backstop sub-task in case craft_client#2 merges without it.


ENUM(volume_state, uint32_t,
INIT, // created, not yet online
Expand Down Expand Up @@ -173,14 +174,17 @@ async_status async_unmap(volume_handle const& vol, uint64_t addr, uint64_t len);

// Append one client-assigned write at slot `dlsn`. `addr`/`len` are BYTE offset/length and must be
// aligned to the volume's lba_size (from craft::LoginResult), else std::errc::invalid_argument. `data` is a
// caller-owned (iomgr) buffer: EMPTY data (size 0) is a zero write (WRITE_ZEROES / unmap; reads back as
// a hole); non-empty data is a data write of exactly `len` bytes -- so the empty buffer, not a flag,
// signals a zero write. Not applied to the index directly; `hdr.commit_lsn` rides along and advances the
// caller-owned (iomgr) buffer: set `all_zeros=true` for a WRITE_ZEROES/unmap over [addr, addr+len) --
// `data` must be empty in that case; otherwise this is a data write of exactly `len` bytes and `data`
// must be non-empty. The flag, not data emptiness, is what selects the write kind -- an empty buffer
// with all_zeros=false (or vice versa) is rejected as std::errc::invalid_argument, not silently
// reinterpreted. Not applied to the index directly; `hdr.commit_lsn` rides along and advances the
// frontier best-effort in dLSN order (CRAFT's piggybacked commit). STALE_TERM if hdr.term != session term.
// The ack returns the replica's achieved {commit_lsn, last_append_lsn}: every CRAFT IO response piggybacks
// the watermarks, so any round-trip refreshes the client's per-member model without a keep_alive.
[[nodiscard]] async_result< craft::lsn_pair > async_write(volume_handle const& vol, craft::client_hdr hdr, int64_t dlsn,
uint64_t addr, uint64_t len, sisl::sg_list data);
uint64_t addr, uint64_t len, sisl::sg_list data,
bool all_zeros = false);

// Read the latest version <= `read_lsn` (horizon H) for [addr, addr+len) (BYTE offset/length, aligned to
// lba_size). Fills the caller-owned `dest` buffer in place -- data sub-ranges get their bytes, holes get
Expand Down
4 changes: 2 additions & 2 deletions src/lib/craft/craft_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ async_status logout(volume_handle const& vol, craft::client_hdr hdr) {
}

async_result< craft::lsn_pair > async_write(volume_handle const& vol, craft::client_hdr hdr, int64_t dlsn,
uint64_t addr, uint64_t len, sisl::sg_list data) {
uint64_t addr, uint64_t len, sisl::sg_list data, bool all_zeros) {
auto* d = craft_dev_of(vol);
if (!d) co_return no_craft_backend();
co_return co_await d->write(hdr, dlsn, addr, len, std::move(data));
co_return co_await d->write(hdr, dlsn, addr, len, std::move(data), all_zeros);
}

async_result< craft::read_result > async_read(volume_handle const& vol, craft::client_hdr hdr, int64_t read_lsn,
Expand Down
Loading
Loading