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

Added Clone method to Txn to create an independent copy of a transaction #26

Merged
merged 2 commits into from
Mar 18, 2020
Merged

Conversation

feldgendler
Copy link
Contributor

This PR adds a Clone method to Txn.

A subsequent PR will add similar functionality to Txn in memdb (txn.Snapshot returning a read-only transaction), dependent on this one.

@hashicorp-cla
Copy link

hashicorp-cla commented Sep 13, 2019

CLA assistant check
All committers have signed the CLA.

iradix.go Outdated Show resolved Hide resolved
@banks
Copy link
Member

banks commented Mar 18, 2020

@feldgendler thanks for the PR!

Can you share a bit more about what you are trying to achieve with this? iradix and memdb by extension are inherently single-writer systems. You can't have a copy-on-write radix tree that can be independently modified by multiple writers without totally changing the design to be lock free etc.

It could be possible to safely clone a read-only transaction in MemDB, but iradix has no notion of read-only transactions since transactions are only ever used for mutations. So I'm not sure this method is ever going to be correct. It would be possible to add a Clone method to memdb transactions provided that it always returned a read-only transaction (even if called on a read-write transaction) and so only ever preserved the snapshot of the current transation and not any of it's mutable state. That means you'd never need to clone internal iradix transactions to build it.

@feldgendler
Copy link
Contributor Author

We are already using forked memdb and iradix, and my plan was to create a PR for memdb as soon as this one is merged.

This is our memdb fork: https://github.com/ridge/go-memdb

What we need is indeed what you say: a memdb.Txn.Clone method that always returns a read-only transaction even if called on a read-write one. It takes a snapshot of the current transaction. That's what we need.

But I don't see a way to implement memdb.Txn.Clone without first introducing Clone to iradix. Do you?

@banks
Copy link
Member

banks commented Mar 18, 2020

Got it. The part I was missing is that the RO clone of a RW transaction still needs to see the writes that have already taken place in that transaction so far. I was assuming since the MemDB would be read only it wouldn't need a write set or any table-level transactions at all, just the old memdb root.

I think that's OK and this implementation is good but would you be able to update the Clone function docs to make this design more explicit - it seems important that users would need to understand the semantics.

@banks
Copy link
Member

banks commented Mar 18, 2020

Actually, I'm not sure it is OK.

Currently iradix assumes that any radix nodes it has already written (in the LRU cache) in the transaction are not yet visible to any other thread and so mutates them in place. Once you add this clone method even if the other thread never attempts to mutate (which isn't prevented by the API) it can still race since the writing thread modifies the new radix nodes in place and so a reader following the root node might hit a node bing mutated in another thread.

The only way to do this safely would be effectively to Commit the transaction and get a root pointer for the new snapshot including current writes, share that as the "read-only" copy, and then reset the write transaction (clear LRU of modified nodes, reset snapshot and root to the new snapshot) to point to the intermediate snapshot and track further mutations.

Have you tried running your fork concurrently with -race detector on?

@feldgendler
Copy link
Contributor Author

would you be able to update the Clone function docs to make this design more explicit

Not sure what I should write there. The cloned iradix transaction is as good as the original. Both can be used independently to produce new immutable trees.

Currently iradix assumes that any radix nodes it has written in the transaction are not yet visible to any other thread.

That's why I clear t.writable in Clone. I was indeed running into concurrency issues until I realized I need to do that. With t.writable cleared, writeNode will treat all existing nodes as immutable, and won't attempt to write into them.

Have you tried running your fork concurrently with -race detector on?

Yes, our fork is a central part of a project that's tested extensively with -race enabled.

Copy link
Member

@banks banks left a comment

Choose a reason for hiding this comment

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

Thanks for bearing with me through a few context switches... you're right this does reset the LRU cache which means that it's only copy ing pointers to radix tress which won't be mutated further by either transaction.

I have a suggestion for the doc comment - if you agree I'll add that before merge.

iradix.go Outdated Show resolved Hide resolved
@banks banks merged commit e47f517 into hashicorp:master Mar 18, 2020
@feldgendler
Copy link
Contributor Author

Thanks a lot! I'll now submit a PR for memdb to complete the loop.

@feldgendler
Copy link
Contributor Author

Updated PR hashicorp/go-memdb#66, ready to merge.

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