Skip to content

Conversation

@skletsun
Copy link
Contributor

@skletsun skletsun commented Feb 21, 2019

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

  • There are no TODOs left in the code
  • Change is covered by automated tests
  • The coding guidelines are followed
  • Public API has Javadoc
  • Method preconditions are checked and documented in the Javadoc of the method
  • Changelog is updated if needed (in case of notable or breaking changes)
  • The continuous integration build passes
.write()
.expect("Unable to obtain write-lock")
.remove(&handle);
HANDLES_MAP.remove(&handle);
Copy link
Contributor

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;
Copy link
Contributor Author

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.

Copy link
Contributor

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?

Copy link
Contributor Author

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.

@coveralls
Copy link

coveralls commented Feb 21, 2019

Coverage Status

Coverage remained the same at 85.53% when pulling 6bbb072 on skletsun:concurrent-hashmap-resource-manager-ecr-2934 into 89f15e6 on exonum:master.

Copy link
Contributor

@dmitry-timofeev dmitry-timofeev left a 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;
Copy link
Contributor

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}",
Copy link
Contributor

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)?

Copy link
Contributor Author

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()

Copy link
Contributor

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) {
Copy link
Contributor

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) 
@dmitry-timofeev
Copy link
Contributor

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.

Copy link
Contributor

@dmitry-timofeev dmitry-timofeev left a 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>,
Copy link
Contributor

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();
Copy link
Contributor

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.

Copy link
Contributor

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>(
Copy link
Contributor

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

  1. Create a fresh HandleStorage.
  2. Setup the test data using public API.
  3. Perform the action we'd like to test.
  4. Verify.
    ?
Some(HandleOwnershipType::NativeOwned),
);
#[test]
fn manage_handles() {
Copy link
Contributor

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 {
Copy link
Contributor

Choose a reason for hiding this comment

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

create_storage?

@dmitry-timofeev dmitry-timofeev added the work-in-progress 👷‍♂️ Do not expect reviewers to provide any feedback on WIP PRs — please ask for it explicitly! label May 20, 2019
@vitvakatu vitvakatu removed their request for review August 16, 2019 11:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

work-in-progress 👷‍♂️ Do not expect reviewers to provide any feedback on WIP PRs — please ask for it explicitly!

4 participants