refactor timeout evaluation

This should be less fragile than using the `threading` module.