I don't think it's that complicated. After the copy has completed, the old table doesn't contain any references anymore, and can be freed without actually traversing it.
It's not enough to verify that the table isn't useful anymore; one must ascertain that no other thread is still looking inside it. For that a (concurrent) ref counter is required, and something like this is still not safe when releasing it:
if (decrement_counter (cnt) == 0) { // Race from here void *p = old_array_ptr; old_array_ptr = NULL; // to here. free (p); }
To sort that out I think it's best to take the same approach as Cliff Click and reason about it as a state machine with states made up of {old_array_ptr, cnt}:
{NULL, 0} {NULL, >= 1} {*, 0} {*, >= 1}
We did it on a whiteboard today - a good excercise.
And of course, cnt (the index/"handle" of the concurrent counter) cannot be stored inside the old hash table block - it has to be stored alongside the pointer to it.
So it's solvable, but still requires some thought.