Re-work linked shutdown between NC and listener threads

Using `throwTo` seems sensible, but holding on to a ThreadId can prevent
BlockedIndefinitely exceptions (such as BlockedIndefinitelyOnMVar) from
propagating. Instead we ensure that once the NC terminates, the endpoint
gets closed, and vice versa. The NC handles its termination signal with
throwIO, avoiding this problem.