When stopping the private state thread, there was a race condition between the time the thread exits (resetting the HostThread object) and the time a Join was attempted, especially in the case of a timeout.
The previous workaround of copying the HostThread object is not enough, since on a Reset the internal thread stuff gets nulled out regardless of which HostThread object actually has Reset called on it, resulting in an attempt to dereference a null pointer on the subsequent call to Join from the copy as well.
Additionally, there was a race between the detach (called when stopping the process) and the stop itself, causing the stop to time out because it was waiting for the private state thread to see the stop state, but it had exited immediately after entering the detached state.
In our environment (custom target), we were hitting these races every single time we stopped debugging.
If you are going to do IsJoinable(), Cancel(), and Join() then this is still racy in that someone else could do the same thing on another thread. So this code should probably take a mutex to protect:
This would need to live in HostNativeThreadBase because HostThread contains a shared pointer to a HostNativeThreadBase and HostThread can be copied.