Closed X-Ryl669 closed 5 years ago
Hey there! Sorry for the delay, just getting to this. I'm reading through your counterexample, and the one thing I'm not sure about is around steps 3 and 4.
It is possible that while thread 1 is unlocking all of its locks, thread 2 can start taking the locks. Which means that once thread 1 completely unlocks the old locks array (but hasn't finished unlocking the new locks), thread 2 can finish taking all of the locks on the old locks array.
But I'm not sure thread 2 would then proceed to actually resize the table. Because if thread 1 has successfully completed a resize, then thread 2 should run check_resize_validity
, and see that its hashpowers are out of date and abort the resize. It is not possible to shrink the table with cuckoo_fast_double
, so it should always be the case that if thread 1 completes the resize, the hashpowers for any concurrently-executing resize will be out-of-date.
Furthermore, I think all updates made by thread 1 should become visible to thread 2, as soon as thread 2 takes any of the locks, because releasing and acquiring the locks will issue a memory barrier that should ensure that all of thread 1's updates are seen by thread 2.
Appreciate your thinking critically about the concurrency story here, it's definitely possible I'm still missing something, so please let me know if my explanation makes sense!
-Manu
Closing for now. Please feel free to reopen if you have more questions!
Let's say you want to insert to a hash map that's almost full.
insert_loop
fails to insert withtable_full
and callscuckoo_fast_double<TABLE_MODE, automatic_resize>(hp)
on line 1086cuckoo_fast_double
worked and calledmaybe_resize_locks
on line 1598maybe_resize_locks
create a new lock array, locks all locks in it, append to the locks list's tail and return (line 1701)cuckoo_fast_double
exits, and theall_locks_manager
destructor release the all the locks from the previous lock array (yet, the new lock array is still completely locked)insert_loop
retry on line 1087 to take some locks from the new array. Since they are all locked and no-one released them, how could that work ?I've seen the
snapshot_and_lock_all
at line 975, but as far as I understand, because it's being called before the new lock array creation, the part that iterate to the next lock array happens before it can find the new array. So, as I understand this, when theAllLocksManager
is destructed, it's unlocking all past and future locks array. Because of this, 2 threads that created an AllLocksManager could interfere because there is no ordering guarantee that setting the new lock array pointers in thread 1 is visible in thread 2. Worst, the order of actions seems wrong...For example, let's see this case:
AllLocker
's for loop and happily starts to release the locks in this new arrayIMHO, the AllLocker's is a mistake since it does not guarantee it's unlocking the lock that were locked beforehand. It should remember which lock array was used when creating the
AllLockManager
. Themaybe_resize_locks
should returns an AllLockManager for the new lock array so that the resize method hold both array's lock and release them in this order: new then old. When the swap is done, the new array's lock are released (so there are no thread that could still use the old array since the old array is still locked but they can start using the new array right away), then the old array.