use a self-pipe to implement timeouts (instead of an interval-check loop)

This uses a the "self-pipe trick" to signal when the child process
is no longer running.  The read output loop will break out once
the child process is no longer running and will write to the self-pipe
signaling the child process is done running.  The wait function then
selects on the self-pipe with the given timeout value.  If no signal
comes in on the pipe within the given timeout, a timeout error is
raised.

This is a more efficient and responsive way of handling timeouts.