Most likely this indicates that the reader
thread either has completed, or has been unresponsive for an unacceptably long time. One possibility is that the reader thread has not yet started - perhaps all the threads in WorkerThreadPool.LONG_DAEMON_TASKS are busy, and the read task is still scheduled to begin whenever a thread becomes available? Which could take awhile if all the other tasks are long-running daemon tasks.
Another possibility is that the reader thread has completed due to an interrupt(). This could happen because someone has intentionally called stop() - but it could also happen because somewhere in the thread's past life some other component sent it an interrupt, and this may be the first opportunity the thread has had to handle it. Or another component may have merely retained a reference to the thread, and sent the interrupt much later. I would add a boolean flag (e.g. _isStopped) that can be set when stop() is called, and checked when an interrupt is detected. That way you can ignore interrupts sent for any other reason.
I'd also add a little debug-level logging to tell you when the reader runnable starts, and when it stops (for any reason). Does the broken pipe occur before the runnable starts, after it stops, or (unlikely) in between? I would also add a try/catch just inside run() to catch any unchecked throwables and make sure they're logged. Depending on how your logging is set up and what happens to System.err output, it's possible that uncaught exceptions may be overlooked. E.g. if you log all "normal" exceptions with a logger going to a log file, and don't normally have any reason to look at the standard error stuff, you may miss uncaught exceptions which simply bubble to the top of the stack, kill the thread, and print a stack trace to standard error.