-
Notifications
You must be signed in to change notification settings - Fork 94
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
The Pull Request for Issue 101 for no_std #102
base: main
Are you sure you want to change the base?
Conversation
The WriteHandle now stores a function pointer to a yield function, which will be called to avoid simply spinning while waiting for all the Readers to move on. This function can be specified when creating the WriteHandle and will default to std::thread::yield_now.
Codecov Report
📢 Thoughts on this report? Let us know!. |
Added the "std" feature to the Crate, which is enabled by default, to allow for switching between no_std and std. Then replaced most uses of "std" with either "core" or "alloc" depending on the Type, however it is still unclear how we deal with the Mutex/MutexGuard Types as these are currently needed but not supported in no_std directly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall this looks good. I left some minor nits, plus a TODO for the Mutex
bit. We'll also want to update CI to test compiling in a no_std environment. You can use this CI file as a starting point.
src/sync.rs
Outdated
pub(crate) use std::sync::{Arc, Mutex, MutexGuard}; | ||
pub(crate) use core::sync::atomic::{fence, AtomicPtr, AtomicUsize, Ordering}; | ||
#[cfg(not(loom))] | ||
pub(crate) use std::sync::{Mutex, MutexGuard}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a note to self that this is still a std
dependency as we've discussed in #101.
Made the small adjustments from the initial review on the Pull-Request
A basic implementation of a Lock-Free HandleList
This includes all the basic changes needed to swap to the HandleList and remove the Mutex. However there are a lot of TODOs that still need to be resolved
Fixed some more TODOs that were still open
Updated the Tests to now also work in a no_std environment, which mainly was just swapping out function names and using the core/alloc versions of std items. However the main doc test is still failing and Im not entirely sure on how to mitigate this Problem in an elegant way
Fixed all the Notes from the Review and now also added the Lock-Free list, called HandleList. Otherwise it should now be fully no_std compliant as far as I can tell and just needs some minor finishing touches in terms of internal comments and style in certain places |
Hmm, I think this is going to end up leaking all the entries since there's no |
While using an existing crate for a lock free list would certainly make it a bit easier since we dont need to maintain our own implementation. However I dont know how we could then have these "snapshots" for the write handle, because the crate would need to support it or something similiar. I think for now I might try to improve the List I wrote and see if I can improve it a bit, but if that does not work out we should consider using some other crate. |
I think basically any singly-linked atomic linked list gives you "snapshot" read iterators. They sort of have to by nature — if you only add to the head, then any read has to start at the head at the time of the first read, and just walk "backwards in time" from then. |
Yeah right, didnt really think about that, but I think there was a Problem, where want to iterate over the same list of handles in the Writer, while we wait for them to move on (new line where we create a new Iterator in a loop vs. old line where we create a new Iterator in the loop but based on the MutexGuard so its always the same). If we were to just create a new Iterator for every time where we check them, there might be a new Handle added and we need to keep track of which handles we saw last time and where they are now (mapping between current list state and last seen "sub-list") |
That's a good point. However, I think you'll generally find the iterators to be |
Reworked a little bit of the Structure for the new HandleList to now handle dropping the List correctly and free all the Entries of the List. This meant we need to create a new InnerList that is wrapped in an Arc, which then allows us to know that once InnerList is dropped, no one else can have access to it or any of its Entries anymore
Now fixed the previous Drop-Problem and also found and fixed another minor Problem with the List-Snapshot. I first fixed the Drop-Problem for the HandleList, by moving the actual ownership of the List into a new InnerList, which is then wrapped in an Arc by the HandleList. This makes sure that we know once InnerList is dropped, no one can still hold a reference to any of its entries. So we should be able to safely free all the entries of the List once InnerList is dropped. This also revealed the Problem that ListSnapshot was not tied to the actual list in any way, by a lifetime or anything like that, which would have resulted in possible use-after frees with the new Drop behaviour. To address this the ListSnapshot now also stores a clone of the Arc which wraps the InnerList, this makes sure that even if every other handle to the list is dropped the snapshot itself keeps the list alive and therefore all the entries also not yet freed and we can safely access them. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for checking back in on this! I left some more in-depth notes this time.
// We dont need a Drop implementation as of now, because the Epoch for this Handle will not | ||
// be freed again |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it's okay for us to only drop a handle only after all handles have been dropped. Doing so would effectively mean that the application has unbounded memory growth, and if it for example creates read a new read handle regularly or in a loop or something, it'll run into problems. I think we have to support removal, even if it's maybe delayed or periodic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I also thought about this but we would also run in the Problem of having to deal with memory reclaimation. What would you think about not actually removing the entries but instead be able to reuse entries in the List, I haven't thought about it too much yet and it would not entirely solve the Problem with unbounded memory usage but It would at least help mitigate most cases. Just something to maybe consider as well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, yes, reuse would be a nice way to go about it!
src/write.rs
Outdated
self.last_epochs.resize(epochs.capacity(), 0); | ||
|
||
// make sure we have enough space for all the epochs in the current snapshot | ||
self.last_epochs.resize(epochs.iter().count(), 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a little unfortunate to end up having to traverse the list twice. It'd be better if we could avoid that.
src/handle_list.rs
Outdated
// We also have no one mutating Entries on the List and therefore we can access this without | ||
// any extra synchronization needed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will no longer be true once we have removal of individual elements.
Fixed most of the basic Problems picked out on the last PR comment, mostly the ones that had very straight forward solutions and didnt require any big changes or new ideas/features.
Fixed most of the Comments you made for the last commit. Although there are still some open questions for me, especially how we would want to do the removal of entries in the List, which I already touched on in one of my comments. My main concern is that we would need to either have nodes be able to represent an "empty" state, which would certainly make it more complicated and would mean that we would never be able to actually reduce our memory usage. However if we start removing entries we would need to worry about memory reclaimation, which Im sure you know can be very tricky (especially when targeting no_std i think). Besides that there is only the small question of how we could avoid having to traverse the List twice in |
I think having a way to represent "unused" is the way to go here. We don't actually have to empty them, since the whole You may find this code from the
Maybe we could even take that implementation and extract it into its own crate so we can re-use it! |
I extracted that list into its own crate: |
Hopefully I can find some time this week to properly look into it and integrate that into it. Will give an update then |
I would love to use left-right in a no-std project of mine, but development on no-std support seems to have stalled, what is the current state of development and is there any way I could help out? |
Im not exactly sure anymore what exactly the stopping factor was, but from a quick look there seem to be two points that need to be addressed:
|
Every list entry now also stores the number of following entries, which is relatively cheap to do, as we only ever append new entries at the start. This allows us to easily get the current length of the list, as we can simply read the current head and read how many followers it has.
Added a first iteration of the reuse of entries in the new HandleList
This is the corresponding for Issue #101.
Currently this only contains the changes for the yielding of the WriteHandle
This change is