Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cache_flat_mutation_reader: use the correct schema in prepare_hash #14305

Merged
merged 3 commits into from
Jun 20, 2023

Conversation

michoecho
Copy link
Contributor

Since mvcc: make schema upgrades gentle (51e3b93),
rows pointed to by the cursor can have different (older) schema
than the schema of the cursor's snapshot.

However, one place in the code wasn't updated accordingly,
causing a row to be processed with the wrong schema in the right
circumstances.

This passed through unit testing because it requires
a digest-computing cache read after a schema change,
and no test exercised this.

This series fixes the bug and adds a unit test which reproduces the issue.

Fixes #14110

@@ -24,6 +25,7 @@ class mutation_cleaner_impl final {
snapshot_list snapshots;
logalloc::allocating_section alloc_section;
bool done = false; // true means the worker was abandoned and cannot access the mutation_cleaner_impl instance.
rwlock merging_enabled; // Allows for pausing the background merging. Used only for testing purposes.
Copy link
Member

Choose a reason for hiding this comment

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

Is a rwlock really needed? Will you have multiple "reader"s?

In fact I'm not sure a lock is needed at all, since the non-test size only uses trylock. Isn't it enough to have a bool?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In fact I'm not sure a lock is needed at all, since the non-test size only uses trylock. Isn't it enough to have a bool?

On the non-test size, the worker blocks on the lock.

It's possible to implement this with a bool too. But then I would have to make the returned guard be a separate type, which signals the condition variable when it's destroyed. I figured rwlock was cleaner.

Is a rwlock really needed? Will you have multiple "reader"s?

No, probably not. In fact, I first used a mutex for this. But then I decided that a reentrant/nestable API for pausing would be nicer.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed to an int (like a bool, but reentrant) in v2.

}
merge_some();
return stop_iteration::no;
return with_lock(w->merging_enabled.for_write(), [this] () noexcept {
Copy link
Contributor

Choose a reason for hiding this comment

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

This adds a deferring point after w->done is checked. We must not attempt merge_some() when done is true because region() may no longer be valid after ~mutation_cleaner_impl().

Copy link
Contributor Author

Choose a reason for hiding this comment

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

True. The w->done check has to be inside the lock for this to be correct.

That's what I get for playing with locks...

Copy link
Contributor Author

@michoecho michoecho Jun 19, 2023

Choose a reason for hiding this comment

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

I'm deeply ashamed of having authored a concurrency bug. Thank you for saving me from the disgrace of getting it into master.

I hope v2 is correct.

In unit tests, we would want to delay the merging of some MVCC
versions to test the transient scenarios with multiple versions present.

In many cases this can be done by holding snapshots to all versions.
But sometimes (i.e. during schema upgrades) versions are added and
scheduled for merge immediately, without a window for the test to
grab a snapshot to the new version.

This patch adds a pause() method to mutation_cleaner, which ensures
that no asynchronous/implicit MVCC version merges happen within
the scope of the call.

This functionality will be used by a test added in an upcoming patch.
Since `mvcc: make schema upgrades gentle` (51e3b93),
rows pointed to by the cursor can have different (older) schema
than the schema of the cursor's snapshot.

However, one place in the code wasn't updated accordingly,
causing a row to be processed with the wrong schema in the right
circumstances.

This passed through unit testing because it requires
a digest-computing cache read after a schema change,
and no test exercised this.

Fixes scylladb#14110
@michoecho
Copy link
Contributor Author

v2:

  • Got rid of rwlock to avoid having 2 concurrency primitives in one place. Replaced it with a counter of active pauses. pause() increments the counter and the guard returned from pause() decrements it and signals the condition variable.
  • The above also fixes (I hope I didn't make any mistakes this time) the incorrect algorithm of v1 which could access the dead mutation_cleaner_impl.

@scylladb-promoter
Copy link
Contributor

@scylladb-promoter scylladb-promoter merged commit 5fa08ad into scylladb:master Jun 20, 2023
3 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.

During add_drop_column multiple crash due to on_internal_error() at schema::column_at() on all nodes
4 participants