- Notifications
You must be signed in to change notification settings - Fork 30
Switched to concurrent HashMap implementation in the resource-manager [ECR-2934] #727
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
base: master
Are you sure you want to change the base?
Switched to concurrent HashMap implementation in the resource-manager [ECR-2934] #727
Conversation
| .write() | ||
| .expect("Unable to obtain write-lock") | ||
| .remove(&handle); | ||
| HANDLES_MAP.remove(&handle); |
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.
Interesting, CHashMap provides methods with internal mutability
| | ||
| use utils::Handle; | ||
| | ||
| const MAP_CAPACITY: usize = 512; |
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've chosen this value with the idea that reasonably big value will keep the load factor small enough spending not much memory to the initial pre-allocation.
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.
Wait, how the map can determine the load factor from the capacity (for an empty map)? These are supposedly different things. Why don't we either pass a load factor (if you have a really good intuition of which is right) or don't pass anything?
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.
Sorry for confusing you, I just meant that CHashMap::with_capacity(N) pre-allocates some amount of buckets depending on N, thus I supposed that defining some reasonable value here could prevent it from expensive re-allocations in future. In other words, it's rather a question if it is possible to estimate this or it is fully unpredictable and there is no sense in such optimizations.
dmitry-timofeev left a comment
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.
The code does not seem to be properly synchronized.
| | ||
| use utils::Handle; | ||
| | ||
| const MAP_CAPACITY: usize = 512; |
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.
Wait, how the map can determine the load factor from the capacity (for an empty map)? These are supposedly different things. Why don't we either pass a load factor (if you have a really good intuition of which is right) or don't pass anything?
| .expect("Unable to obtain write-lock") | ||
| .insert(handle, HandleInfo::new(TypeId::of::<T>(), ownership)) | ||
| .is_none(), | ||
| "Trying to add the same handle for the second time: {:X}", |
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.
Assuming insert returns the previous value, does there happen an overwrite of the previous value (i.e., the change of the state of the map in case of an invalid argument)?
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.
Re-written using CHashMap.alter()
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.
FYI, I bumped into another library, DashMap, it has proper get_or_insert in its API.
Hovewer, even though it is 1.0+, there are no concurrent tests and even complete functional tests 👎
| /// # Panics | ||
| /// | ||
| /// See `check_handle_impl` for details. | ||
| fn remove_handle_impl<T: 'static>(handle: Handle, ownership: HandleOwnershipType) { |
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 method is not correctly synchronized, as it won't detect concurrent double-free:
T1 T2 c->OK c->OK r->removed r->nothing to remove (OK) | Why don't we also push all these operations in a struct that can be instantiated in both lazy_static and unit tests? Otherwise it is quite hard to test operations on a static shared state. |
dmitry-timofeev left a comment
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, such implementation, given all operations in chashmap are properly synchronized, would work. However, I think tests need improvements. Also, shan't there be any concurrent tests?
| fn check_handle_type_and_ownership<T: 'static>( | ||
| handle: Handle, | ||
| handle_info: &HandleInfo, | ||
| ownership: Option<HandleOwnershipType>, |
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.
Why is it an Option? I see that the only usage with None comes from check_handle — why is that?
| MANAGE_HANDLES_FIRST_HANDLE, | ||
| Some(HandleOwnershipType::JavaOwned), | ||
| fn check_handle_type_and_ownership_ok() { | ||
| let handle_info = prepare_handle_info(); |
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.
Here and elsewhere: I do not think prepare_handle_info works in such cases because subsequent assertions do rely on what it puts there. Either
- This method must be parameterized.
- Converted to a builder, relevant to the test data must be set up in each test that depends on that data
- If no parameterization needed — given a name that reflects what it creates.
Currently it is not clear what all these tests using prepare_handle_info test.
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.
| Some(HandleOwnershipType::JavaOwned), | ||
| fn check_handle_type_and_ownership_ok() { | ||
| let handle_info = prepare_handle_info(); | ||
| HandlesStorage::check_handle_type_and_ownership::<HandleFooType>( |
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.
Do we need tests using this internal static method? Wouldn't it be simpler to
- Create a fresh HandleStorage.
- Setup the test data using public API.
- Perform the action we'd like to test.
- Verify.
?
| Some(HandleOwnershipType::NativeOwned), | ||
| ); | ||
| #[test] | ||
| fn manage_handles() { |
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.
Please split into more focused tests for operations of rm. There are already some good focused tests below — I'd consider identifying which cases are currently missing and extending the suite of such focused tests.
| add_handle::<T>(WRONG_TYPE_HANDLE); | ||
| enum OtherT {} | ||
| check_handle::<OtherT>(WRONG_TYPE_HANDLE); | ||
| fn prepare_storage() -> HandlesStorage { |
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.
create_storage?
Overview
Switched from RwLock to chashmap::CHashMap for the handles storage in the resources manager.
See: https://jira.bf.local/browse/ECR-XYZ
Definition of Done