Skip to content

Conversation

@zhangchiqing
Copy link
Member

@zhangchiqing zhangchiqing commented Sep 22, 2025

Work towards #7912

  • Adds a dedicated LockInsertChunkDataPack so all chunk data pack writes happen under a storage lock.
  • Wraps Store(chunks) in storage.WithLock(...) instead of direct calls. The storage.WithLock can be reused in the future.

// LockBootstrapping protects data that is *exclusively* written during bootstrapping.
LockBootstrapping = "lock_bootstrapping"
// LockInsertChunkDataPack protects the insertion of chunk data packs (not yet used anywhere
LockInsertChunkDataPack = "lock_insert_chunk_data_pack"
Copy link
Member Author

Choose a reason for hiding this comment

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

chunk data pack is stored in its own pebble database, so can't reuse the InsertOwnReceipt lock.

Copy link
Member

@AlexHentschel AlexHentschel Sep 26, 2025

Choose a reason for hiding this comment

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

Sorry, can you help by explaining this some more? As I understand it, a storage lock protects a set of keys ... more precisely the lock protects a set of storage codes, such as codeChunkDataPack. Even if those storage codes live in different databases, the locking would still ensure correctness (specifically prevent stale reads)?

I am asking because you write that we "cannot use InsertOwnReceipt", to me that reads like "it is impossible to use InsertOwnReceipt". I just want to clarify my understanding that reusing InsertOwnReceipt would actually be possible, but we decide not to for performance reasons. Would you agree?

Copy link
Member Author

Choose a reason for hiding this comment

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

This lock will be removed in #7983

@codecov-commenter
Copy link

codecov-commenter commented Sep 22, 2025

}

err = s.chunkDataPacks.Store(chunks)
err = storage.WithLock(s.lockManager, storage.LockInsertChunkDataPack, func(lctx lockctx.Context) error {
Copy link
Member Author

Choose a reason for hiding this comment

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

This process acquires two locks, it's better to use WithLock function instead of AcquireLock, because a misuse of defer lctx.Release will hold the lock for too long.

@zhangchiqing zhangchiqing force-pushed the leo/refactor-insert-chunk-data-pack branch from 494707f to ceb03e1 Compare September 22, 2025 21:50
// BatchStore stores ChunkDataPack c keyed by its ChunkID in provided batch.
// No errors are expected during normal operation, but it may return generic error
// if entity is not serializable or Badger unexpectedly fails to process request
func (ch *ChunkDataPacks) BatchStore(c *flow.ChunkDataPack, rw storage.ReaderBatchWriter) error {
Copy link
Member Author

Choose a reason for hiding this comment

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

BatchStore is not being used anywhere, removed.

@zhangchiqing zhangchiqing marked this pull request as ready for review September 23, 2025 01:35
@zhangchiqing zhangchiqing requested a review from a team as a code owner September 23, 2025 01:35
Comment on lines 421 to 423
// the number of database interactions. This is a large batch of data, which might not be
// committed within a single operation (e.g. if using Badger DB as storage backend, which has
// a size limit for its transactions).
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// the number of database interactions. This is a large batch of data, which might not be
// committed within a single operation (e.g. if using Badger DB as storage backend, which has
// a size limit for its transactions).
// the number of database interactions.

Assuming this is only true for Badger, I think we could remove this comment now.

Comment on lines 426 to 437
// Rollback if an error occurs during batch operations
if err != nil {
chunkIDs := make([]flow.Identifier, 0, len(chunks))
for _, chunk := range chunks {
chunkIDs = append(chunkIDs, chunk.ChunkID)
}
_ = s.chunkDataPacks.Remove(chunkIDs)
Copy link
Member

Choose a reason for hiding this comment

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

This more complex rollback logic is arising from storing chunk data packs in a separate database, and needing to coordinate commits between the 2 databases.

Rollback error handling can silently swallow errors indicating data corruption

The error handling flow is introducing the possibility of silently corrupting the database: if s.chunkDataPacks.Remove fails, the error is swallowed and the database will be in an unexpected state. The node will continue operating with incorrect data, which we never want to allow.

(Even if there is no error, the "my receipt" and chunk data packs will not be committed atomically from the perspective of other reader threads. I'm not sure if this could be a problem in practice.)

Concurrency safety of rollback mechanism

I'm also not convinced this function won't produce inconsistent database states when invoked concurrently. For example, suppose threads A and B are executing concurrently and storing chunk data pack P and auxiliary information (events, transaction results, etc.) X:
A: write P
B: write P
A: errors while writing X, invoking rollback logic and deleting P
B: successfully writes X

At the end we will have an inconsistent state across the two databases:

  • P does not exist in the database
  • X does exist in the database

Both threads have an incorrect view of what happened:

  • A expects X to not exist, but it does
  • B expects P to exist, but it doesn't

This function or its callers doesn't document (from what I can tell) whether it is concurrency safe, although it is ultimately called from an engine using multiple worker threads, so I think it at least could be called concurrently. There is also this TODO gesturing at the idea that the component should be concurrency safe.


I'm wondering if we still need to have separate databases now that the main database uses Pebble. My understanding is that we split out chunk data packs primarily so that we could use Pebble's "real" delete feature to prune and save disk space. Now that the primary database uses Pebble, can we return to storing chunk datapacks in the primary database?

While we have the two databases, and these resulting problems, I think we need to:

  • make sure the current error handling behaviour is acceptable given how this component is being used
  • document the expectations this component makes of its callers, what guarantees it does/doesn't make
  • or update the behaviour/usage to better work around the atomicity/consistency issues

Copy link
Member Author

Choose a reason for hiding this comment

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

Good catch, I moved locks up higher to cover the entire save execution result, which makes it concurrent safe. Also added some TODO to work on the chunk data pack removal issue in the future.

Copy link
Member Author

Choose a reason for hiding this comment

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

Addressed #7983

if !bytes.Equal(c.Proof, other.Proof) {
return ErrDataMismatch
}
if !c.SystemChunk && c.CollectionID != other.CollectionID {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if !c.SystemChunk && c.CollectionID != other.CollectionID {
if c.CollectionID != other.CollectionID {

Shouldn't they still be equal (zero) when it is a system chunk?

@zhangchiqing zhangchiqing force-pushed the leo/refactor-insert-chunk-data-pack branch from 3fef499 to e671f10 Compare September 25, 2025 16:08
// Store stores multiple ChunkDataPacks cs keyed by their ChunkIDs in a batch.
// No errors are expected during normal operation, but it may return generic error
Store(cs []*flow.ChunkDataPack) error
StoreByChunkID(lctx lockctx.Proof, cs []*flow.ChunkDataPack) error
Copy link
Member

@AlexHentschel AlexHentschel Sep 26, 2025

Choose a reason for hiding this comment

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

I feel this API here breaks with existing API patterns:

  • We already have ReaderBatchWriter to batch database interactions.

  • This function accepts a lock proof but no ReaderBatchWriter - unlike the vast majority of other storage APIs in this package

  • My understanding of the documentation in the caller seems to be ad odds with the API here:

    err := s.chunkDataPacks.StoreByChunkID(lctx, chunks)
    if err != nil {
    return fmt.Errorf("can not store multiple chunk data pack: %w", err)
    }
    // Save entire execution result (including all chunk data packs) within one batch to minimize
    // the number of database interactions.
    return s.db.WithReaderBatchWriter(func(batch storage.ReaderBatchWriter) error {

    The caller talks about saving the "entire execution result (including all chunk data packs) within one batch to minimize [..] database interactions." But don't we save them independently??

May I guess why you chose a different API? I am hypothesizing that this is because chunk data packs and results are stored in different databases? So we are worried about the same ReaderBatchWriter being accidentally used across the databases? If my speculation is correct:

  • for now, could you please diligently document this in the interface and implementation

  • [beyond the scope of this PR], I suspect we need a clearer encapsulation of database and storage abstractions served by that database. Maybe we can introduce different packages. Like a protocol_store package and an execution_store package. Each could have its own type ReaderBatchWriter ... just a random thought to illustrate my thinking.

    Nothing to implement now, but probably good to keep in mind and thinking about solutions, because I suspect this is not the last case where we have to make the API inconsistent to avoid accidentally storing an object in the wrong database.

Copy link
Member Author

Choose a reason for hiding this comment

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

The Store function is refactored in #7983

Comment on lines +415 to +421
err := s.chunkDataPacks.StoreByChunkID(lctx, chunks)
if err != nil {
return fmt.Errorf("can not store multiple chunk data pack: %w", err)
}

lctx := s.lockManager.NewContext()
defer lctx.Release()
err = lctx.AcquireLock(storage.LockInsertOwnReceipt)
if err != nil {
return err
}
// Save entire execution result (including all chunk data packs) within one batch to minimize
// the number of database interactions.
Copy link
Member

@AlexHentschel AlexHentschel Sep 26, 2025

Choose a reason for hiding this comment

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

I think this comment is not reflecting the current implementation:

Save entire execution result (including all chunk data packs) within one batch to minimize the number of database interactions.

because we are not storing execution result and chunk data packs in the same operation ... maybe I am misunderstanding some detail.

Copy link
Member Author

Choose a reason for hiding this comment

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

Right. Updated cbc5873 in #7983

Copy link
Member

Choose a reason for hiding this comment

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

[entirely optional]
If it's easy, can we change StoredChunkDataPack into an internal datatype of the storage/store package. Conceptually, this would seem cleaner to me. But I suspect is is not easy due to this 👇

// Its prime difference is instead of an actual collection, it keeps a collection ID hence relying on maintaining

If you agree, but it's too big of a change for this PR, maybe you could create an issue and add it to the epic [Protocol] Misc improvements and tech debt. Also, you don't agree, just feel free to ignore (no detailed response needed). Thanks 🙂

Store(cs []*flow.ChunkDataPack) error
StoreByChunkID(lctx lockctx.Proof, cs []*flow.ChunkDataPack) error

// Remove removes multiple ChunkDataPacks cs keyed by their ChunkIDs in a batch.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// Remove removes multiple ChunkDataPacks cs keyed by their ChunkIDs in a batch.
// Remove multiple ChunkDataPacks with the given chunk IDs (in a batch).

Copy link
Member Author

Choose a reason for hiding this comment

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

Comment on lines 14 to 15
// Store stores multiple ChunkDataPacks cs keyed by their ChunkIDs in a batch.
// No errors are expected during normal operation, but it may return generic error
Copy link
Member

@AlexHentschel AlexHentschel Sep 26, 2025

Choose a reason for hiding this comment

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

For every function taking a storage lock, please make sure you document the lock diligently (ideally with some context).

Suggested change
// Store stores multiple ChunkDataPacks cs keyed by their ChunkIDs in a batch.
// No errors are expected during normal operation, but it may return generic error
// StoreByChunkID persists multiple [flow.ChunkDataPack]s keyed by their ChunkIDs in a batch.
//
// CAUTION:
// - The chunk ID is _not_ a collision resistant hash of the chunk data pack. For any given chunk, an Execution Node should
// always provide the _same_ chunk data pack (slashable protocol violation otherwise). To prevent bugs in higher-lever logic
// from accidentally overwriting an existing chunk data pack with a different one, we must atomically read and write data here.
// Hence, the caller must acquire the [storage.LockInsertChunkDataPack] and hold it until the database write has been committed.
//
// No error returns are expected during normal operation.

Copy link
Member Author

Choose a reason for hiding this comment

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


// Equals returns true if and only if receiver BlockExecutionDataRoot is equal to the `other`.
func (b BlockExecutionDataRoot) Equals(other BlockExecutionDataRoot) bool {
// Compare BlockID fields
Copy link
Member

Choose a reason for hiding this comment

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

From my perspective, this is trivially apparent and doesn't need documentation (... didn’t think I’d ever say this 😅 )

Suggested change
// Compare BlockID fields

Copy link
Member Author

Choose a reason for hiding this comment

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

Applied in c37af9c

return false
}

// Compare ChunkExecutionDataIDs slices
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// Compare ChunkExecutionDataIDs slices

Copy link
Member Author

Choose a reason for hiding this comment

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

Applied in c37af9c

LockInsertCollection = "lock_insert_collection"
// LockBootstrapping protects data that is *exclusively* written during bootstrapping.
LockBootstrapping = "lock_bootstrapping"
// LockInsertChunkDataPack protects the insertion of chunk data packs (not yet used anywhere
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// LockInsertChunkDataPack protects the insertion of chunk data packs (not yet used anywhere
// LockInsertChunkDataPack protects the insertion of chunk data packs

Copy link
Member Author

Choose a reason for hiding this comment

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

This lock will be removed


// saveExecutionResults saves all data related to the execution of a block.
// It is concurrent-safe
func (s *state) saveExecutionResults(
Copy link
Member

Choose a reason for hiding this comment

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

It would be great if you to document error returns for this function as part of this PR, please.

Copy link
Member Author

Choose a reason for hiding this comment

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

Added in #7983

return storage.WithLocks(s.lockManager, []string{storage.LockInsertOwnReceipt, storage.LockInsertChunkDataPack}, func(lctx lockctx.Context) error {
err := s.chunkDataPacks.StoreByChunkID(lctx, chunks)
if err != nil {
return fmt.Errorf("can not store multiple chunk data pack: %w", err)
Copy link
Member

Choose a reason for hiding this comment

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

I think this error message is somewhat vague. When I first read it, I mistakingly understood that the attempt to store multiple chunk data packs was the reason for the failure.

Suggested change
return fmt.Errorf("can not store multiple chunk data pack: %w", err)
return fmt.Errorf("failed to persist batch of chunk data packs for block %v: %w", blockID, err)

Copy link
Member Author

Choose a reason for hiding this comment

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

Updated in #7983

Comment on lines +424 to +427
// Rollback if an error occurs during batch operations
// Chunk data packs are saved in a separate database, there is a chance
// that execution result was failed to save, but chunk data packs was saved and
// didnt get removed.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// Rollback if an error occurs during batch operations
// Chunk data packs are saved in a separate database, there is a chance
// that execution result was failed to save, but chunk data packs was saved and
// didnt get removed.
// Rollback if an error occurs during batch operations. This is critical for data consistency:
// as chunk data packs and results are saved in separate databases. If we reach this code, storing chunk
// data packs has already succeeded, but persisting the execution result is failing. In this case, the
// chunk data packs that were previously saved need to be removed again to maintain data consistency.

Copy link
Member Author

Choose a reason for hiding this comment

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

Updated in #7983

Comment on lines +428 to +429
// TODO(leo): when retrieving chunk data packs, we need to add a check to ensure the block
// has been executed before returning chunk data packs
Copy link
Member

Choose a reason for hiding this comment

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

This is a subtle dependency that the logic for retrieving chunk data packs has on the logic storing the chunk data packs. In my experience, those dependencies are easily broken over time. I can totally see an engineer thinking: "why do I need to check? If the chunk data pack exists, the chunk clearly has been executed"

I think we can address this. From my perspective, the issue is that:

  • ENs persist the information whether it has executed a block in the protocol data base (assumption on my end)
  • in contrast, the mapping from chunk ID to chunk-data-pack is stored in the execution database.

Now imagine the following approach:

  • In the protocol data base, the EN persists:
    • information whether it has executed a block (as before)
    • index from chunk ID to chunk-data-pack hash
  • the execution database only contains the chunk-data-packs stored by their respective hashes.

With this approach, all queries for a chunk data pack by its chunk ID always go through the protocol database. (alternatively, we could move all of the "my execution-result-related-data", including "my receipts" to the execution database, so that only the execution database needs to be queried and it either has all or none of the information pertaining to some result). Thereby we have complete atomicity in all practically relevant scenarios, including storage failures.

In this context, I want to emphasize again the paradigm of high-assurance engineering, which I previously brought up on slack:

  • For high-assurance systems, if the caller is ignorant, the API should ideally error making the caller crash
  • What we want to avoid is an API design, where an ignorant caller (forgetting to check whether the result exists, and just assuming it exists if the API call returns) will not notice and likely behave incorrectly.

Note that we are explicitly thinking about an ignorant caller in the spirt of defensive programming.

Hope that makes sense. If that explanation is too brief, don't hesitate to let me know and we can chat in person. If you agree, but it's too big of a change for this PR, maybe you could create an issue and add it to the epic [Protocol] Misc improvements and tech debt. Thanks 🙂

Copy link
Member

Choose a reason for hiding this comment

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

randomly noticed this:
In line 43 you could replace pebbleimpl.ToDB(pdb) with the variable you initialized above in line 33

Copy link
Member Author

Choose a reason for hiding this comment

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

updated 7fa1ef6

if err != nil {
return fmt.Errorf("can not store multiple chunk data pack: %w", err)
}
// Acquire both locks to ensure it's concurrent safe when inserting the execution results and chunk data packs.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// Acquire both locks to ensure it's concurrent safe when inserting the execution results and chunk data packs.
// Acquire both locks because we want to persist the execution results *and* chunk data packs atomically in one write batch.

Copy link
Member

Choose a reason for hiding this comment

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

They're in different databases, so we can't commit them atomically in one write batch.

Copy link
Member Author

Choose a reason for hiding this comment

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

Will be refactored in #7983

Copy link
Member

@AlexHentschel AlexHentschel left a comment

Choose a reason for hiding this comment

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

Looks good.

// Save entire execution result (including all chunk data packs) within one batch to minimize
// the number of database interactions.
return s.db.WithReaderBatchWriter(func(batch storage.ReaderBatchWriter) error {
batch.AddCallback(func(err error) {
Copy link
Member

Choose a reason for hiding this comment

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

For better logical flow, I would move this to the end, right before line 477. Then, the logical flow reads:

  • we index the events
  • then we index the service events,
  • ...
  • at the end, we index the end state
  • and unless all those operations ☝️ succeed, we remove the chunk data packs

return s.db.WithReaderBatchWriter(func(batch storage.ReaderBatchWriter) error {
batch.AddCallback(func(err error) {
// Rollback if an error occurs during batch operations
err = s.events.BatchStore(blockID, []flow.EventsList{result.AllEvents()}, batch)
Copy link
Member

Choose a reason for hiding this comment

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

All of the storage operations in lines 439 - 475 are indexing operations, where we have the risks for overwrites. As discussed in our meeting Continue storage API discussion (Friday, Sep 26), all those should contain deduplication checks and hence locks.

Copy link
Member Author

Choose a reason for hiding this comment

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

yes. This will be done in separate PRs.

Comment on lines +64 to 65
// StoreByChunkID stores multiple ChunkDataPacks cs keyed by their ChunkIDs in a batch.
// No errors are expected during normal operation, but it may return generic error
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// StoreByChunkID stores multiple ChunkDataPacks cs keyed by their ChunkIDs in a batch.
// No errors are expected during normal operation, but it may return generic error
// StoreByChunkID persists multiple [flow.ChunkDataPack]s keyed by their ChunkIDs in a batch.
//
// CAUTION:
// - The chunk ID is _not_ a collision resistant hash of the chunk data pack. For any given chunk, an Execution Node should
// always provide the _same_ chunk data pack (slashable protocol violation otherwise). To prevent bugs in higher-lever logic
// from accidentally overwriting an existing chunk data pack with a different one, we must atomically read and write data here.
// Hence, the caller must acquire the [storage.LockInsertChunkDataPack] and hold it until the database write has been committed.
//
// No error returns are expected during normal operation.

return sc
}

func (c StoredChunkDataPack) Equals(other StoredChunkDataPack) error {
Copy link
Member

Choose a reason for hiding this comment

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

⚠️ breaking with coding conventions

  • We are returning a sentinel error here, which is used here. No error documentation is provided. This is violating our coding conventions.
  • I find that this signature is breaking with widely-used patterns: Equals returns typically a boolean.

Options:

  • change return type to boolean
  • if you want a convenience function that returns an error, I would suggest to name it EnsureEquals
Suggested change
func (c StoredChunkDataPack) Equals(other StoredChunkDataPack) error {
// EnsureEquals passes if and only if `other` is equal to the receiver.
// Otherwise, a `storage.ErrDataMismatch` is returned.
func (c StoredChunkDataPack) EnsureEquals(other StoredChunkDataPack) error {
if !c.Equals(other) {
return ErrDataMismatch
}
return nil
}
// Equals returns true if and only if `other` is equal to the receiver.
func (c StoredChunkDataPack) Equals(other StoredChunkDataPack) bool {

Copy link
Member

Choose a reason for hiding this comment

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

I see the reason for using error here: conceptually we want to return a boolean and some context to include in an error message if necessary. Using a boolean only strips all context from the resulting error.

The problem with error is that it usually means something else. What about returning bool, string? If you return false, you can optionally include some extra context in the string. Otherwise it can be ignored.

Comment on lines +34 to +35
err := c.Equals(existing)
if err != nil {
Copy link
Member

Choose a reason for hiding this comment

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

⚠️

We are interpreting all errors without further differentiation as mismatch -- without any supporting documentation of Equals. This is violating our coding conventions.

I would suggest to change Equals to the established signature returning a boolean.

Copy link
Member Author

Choose a reason for hiding this comment

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

Agree with improving docs.

The reason I return error instead of boolean is so that the error can include the actual field that is mismatching including both values, it is useful for debugging.

Otherwise, a boolean would only tell they are different without telling why. And we might miss the chance to print the difference if we didn't save both value, and even if we print both value, it can be hard to find the actual mismatching field, especially like chunk data pack, which is usually very large.

func TestChunkDataPacks_StoreTwice(t *testing.T) {
WithChunkDataPacks(t, 2, func(t *testing.T, chunkDataPacks []*flow.ChunkDataPack, chunkDataPackStore *store.ChunkDataPacks, pdb *pebble.DB) {
WithChunkDataPacks(t, 2, func(t *testing.T, chunkDataPacks []*flow.ChunkDataPack, chunkDataPackStore *store.ChunkDataPacks, pdb *pebble.DB, lockManager storage.LockManager) {
db := pebbleimpl.ToDB(pdb)
Copy link
Member

Choose a reason for hiding this comment

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

I would move this line into WithChunkDataPacks

Copy link
Member Author

Choose a reason for hiding this comment

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

Done in #7939

if err != nil {
return fmt.Errorf("can not store multiple chunk data pack: %w", err)
}
// Acquire both locks to ensure it's concurrent safe when inserting the execution results and chunk data packs.
Copy link
Member

Choose a reason for hiding this comment

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

They're in different databases, so we can't commit them atomically in one write batch.

return sc
}

func (c StoredChunkDataPack) Equals(other StoredChunkDataPack) error {
Copy link
Member

Choose a reason for hiding this comment

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

I see the reason for using error here: conceptually we want to return a boolean and some context to include in an error message if necessary. Using a boolean only strips all context from the resulting error.

The problem with error is that it usually means something else. What about returning bool, string? If you return false, you can optionally include some extra context in the string. Otherwise it can be ignored.

@zhangchiqing zhangchiqing added this pull request to the merge queue Sep 30, 2025
Merged via the queue into master with commit ca73541 Oct 1, 2025
57 checks passed
@zhangchiqing zhangchiqing deleted the leo/refactor-insert-chunk-data-pack branch October 1, 2025 00:14
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.

4 participants