Допустим, я хочу странного: использовать edge-triggered epoll (всегда мечтал, но руки все не доходили) + splice для работы с диском (отправка/получение файлов). При этом splice между (pipe => disk) и (net => pipe) происходят в отдельных тредах (в linux же не могут завести человеческое дисковое aio).
Проблема в том, что когда splice возвращает EAGAIN как-то очень нетривиально догадаться, кто виноват. Если использовать level-triggered IO, все просто: мониторим pipe и source/destination socket на готовность к чтению/записи и пишем/читаем. Получили EAGAIN — не паримся и уходим в epoll, позже видно будет, кто виноват.
Если использовать ioctl, чтобы понять, не переполнился ли пайп, можно наступить на race (splice file => pipe и pipe => socket происходят в разных потоках). Если узнать максимальный размер буфера pipe, также пойдут race, потому как операции «атомарно сделать splice и изменить userspace счетчик» не завезли. Можно обмазаться mutex'ами, но это убьет производительность (splice сможет делать только один поток).
С level-triggered все просто: получил событие, позвал splice и если наткнулся на EAGAIN, значит виновата «другая сторона», т. к. файловый дескриптор готов к чтению записи — гарантировано epoll'ом. Но level-triggered делают все, и в нем время от времени придется делать EPOLL_CTL_DEL. В edge-triggered не нужно в принципе изменять набор дескрипторов, но splice нужно проводить до победного EAGAIN, а там уже фиг поймешь, кто виноват.
Может есть какой-то красивый трюк, о котором никто не пишет?
P. S. Любители nginx и прочих готовых «быстрых веб-серверов» идут лесом — у меня душа странного требует.