Why do I seem to lose data using this bash pipe construction?

Because the nc command inside <(...) will also read from stdin.

Simpler example:

$ nc -l 9999 >/tmp/foo &
[1] 5659

$ echo text | cat <(nc -N localhost 9999) -
[1]+  Done                    nc -l 9999 > /tmp/foo

Where did the text go? Through the netcat.

$ cat /tmp/foo

Your program and nc compete for the same stdin, and nc gets some of it.

epoll() or poll() returning with E/POLLIN will only tell you that a single read() may not block.

Not that you will be able to do a lot of one byte read()s up to a newline, as you do.

I say may because a read() after epoll() returned with E/POLLIN may still block.

Your code will also try to read past EOF, and completely ignores any read() errors.