Mengapa saya sepertinya kehilangan data menggunakan konstruksi pipa bash ini?

11

Saya mencoba untuk menggabungkan beberapa program seperti itu (abaikan semua tambahan, ini adalah pekerjaan berat yang sedang berjalan):

pv -q -l -L 1  < input.csv | ./repeat <(nc "host" 1234)

Di mana sumber dari program berulang terlihat sebagai berikut:

#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include <iostream>
#include <string>

inline std::string readline(int fd, const size_t len, const char delim = '\n')
{
    std::string result;
    char c = 0;
    for(size_t i=0; i < len; i++)
    {
        const int read_result = read(fd, &c, sizeof(c));
        if(read_result != sizeof(c))
            break;
        else
        {
            result += c;
            if(c == delim)
                break;
        }
    }
    return result;
}

int main(int argc, char ** argv)
{
    constexpr int max_events = 10;

    const int fd_stdin = fileno(stdin);
    if (fd_stdin < 0)
    {
        std::cerr << "#Failed to setup standard input" << std::endl;
        return -1;
    }


    /* General poll setup */
    int epoll_fd = epoll_create1(0);
    if(epoll_fd == -1) perror("epoll_create1: ");
    {
        struct epoll_event event;
        event.events = EPOLLIN;
        event.data.fd = fd_stdin;
        const int result = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd_stdin, &event);
        if(result == -1) std::cerr << "epoll_ctl add for fd " << fd_stdin << " failed: " << strerror(errno) << std::endl;
    }

    if (argc > 1)
    {
        for (int i = 1; i < argc; i++)
        {
            const char * filename = argv[i];
            const int fd = open(filename, O_RDONLY);
            if (fd < 0)
                std::cerr << "#Error opening file " << filename << ": error #" << errno << ": " << strerror(errno) << std::endl;
            else
            {
                struct epoll_event event;
                event.events = EPOLLIN;
                event.data.fd = fd;
                const int result = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event);
                if(result == -1) std::cerr << "epoll_ctl add for fd " << fd << "(" << filename << ") failed: " << strerror(errno) << std::endl;
                else std::cerr << "Added fd " << fd << " (" << filename << ") to epoll!" << std::endl;
            }
        }
    }

    struct epoll_event events[max_events];
    while(int event_count = epoll_wait(epoll_fd, events, max_events, -1))
    {
        for (int i = 0; i < event_count; i++)
        {
            const std::string line = readline(events[i].data.fd, 512);                      
            if(line.length() > 0)
                std::cout << line << std::endl;
        }
    }
    return 0;
}

Saya perhatikan ini:

  • Ketika saya hanya menggunakan pipa ./repeat, semuanya berfungsi sebagaimana mestinya.
  • Ketika saya hanya menggunakan proses substitusi, semuanya berfungsi sebagaimana dimaksud.
  • Ketika saya merangkum pv menggunakan proses substitusi, semuanya berfungsi sebagaimana dimaksud.
  • Namun, ketika saya menggunakan konstruksi spesifik, saya tampaknya kehilangan data (karakter individu) dari stdin!

Saya sudah mencoba yang berikut ini:

  • Saya telah mencoba untuk menonaktifkan buffering di antara pvdan ./repeatmenggunakan pipa stdbuf -i0 -o0 -e0pada semua proses, tapi itu sepertinya tidak berhasil.
  • Saya telah bertukar epoll untuk polling, tidak berhasil.
  • Ketika saya melihat aliran antara pvdan ./repeatdengan tee stream.csv, ini terlihat benar.
  • Saya biasa stracemelihat apa yang sedang terjadi, dan saya melihat banyak bacaan single-byte (seperti yang diharapkan) dan mereka juga menunjukkan bahwa data akan hilang.

Saya bertanya-tanya apa yang sedang terjadi? Atau apa yang bisa saya lakukan untuk menyelidiki lebih lanjut?

Roel Baardman
sumber

Jawaban:

16

Karena ncperintah di dalam <(...)juga akan dibaca dari stdin.

Contoh sederhana:

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

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

Kemana perginya text? Melalui netcat.

$ cat /tmp/foo
text

Program Anda dan ncbersaing untuk stdin yang sama, dan ncdapatkan sebagian.

mosvy
sumber
Kamu benar! Terima kasih! Bisakah Anda menyarankan cara bersih untuk melepaskan stdin di <(...)? Apakah ada cara yang lebih baik daripada <( 0<&- ...)?
Roel Baardman
5
<(... </dev/null). jangan gunakan 0<&-: itu akan menyebabkan yang pertama open(2)kembali 0sebagai fd baru. Jika Anda ncmendukungnya, Anda juga dapat menggunakan -dopsi ini.
Mosvy
3

epoll () atau polling () yang dikembalikan dengan E / POLLIN hanya akan memberi tahu Anda bahwa satu pembacaan () mungkin tidak mencekal.

Bukan berarti Anda akan dapat melakukan banyak membaca satu byte () hingga baris baru, seperti yang Anda lakukan.

Saya katakan mungkin karena read () setelah epoll () dikembalikan dengan E / POLLIN masih dapat memblokir.

Kode Anda juga akan mencoba membaca EOF yang lalu, dan sepenuhnya mengabaikan kesalahan read ().

memilih pizza
sumber
Meskipun ini tidak menjadi solusi langsung untuk masalah saya, terima kasih telah memberikan komentar. Saya menyadari bahwa kode ini memiliki kekurangan, dan deteksi EOF hadir dalam versi yang lebih sederhana (melalui penggunaan POLLHUP / POLLNVAL). Saya berjuang dengan menemukan cara unbuffered untuk membaca baris dari beberapa deskriptor file. repeatProgram saya pada dasarnya memproses data NMEA (berbasis garis dan tanpa indikator panjang) dari berbagai sumber. Karena saya menggabungkan data dari berbagai sumber langsung, saya ingin solusi saya tidak terbaca. Bisakah Anda menyarankan cara yang lebih efisien untuk melakukan ini?
Roel Baardman
fwiw, melakukan system call (baca) untuk setiap byte adalah cara seefisien mungkin. Pengecekan EOF dapat dilakukan dengan hanya memeriksa nilai balik dari pembacaan, tidak perlu untuk POLLHUP (dan POLLNVAL hanya akan dikembalikan ketika Anda memberikannya palsu, bukan pada EOF). Tapi bagaimanapun, tetap disini. Saya memiliki gagasan tentang ypeeutilitas yang membaca dari beberapa fds dan mencampurkannya ke fd lain, sambil menjaga catatan (menjaga garis tetap utuh).
pizdelect
Saya perhatikan bahwa konstruksi bash ini harus melakukan itu, tetapi saya tidak tahu bagaimana menggabungkan stdin di dalamnya: { cmd1 & cmd2 & cmd3; } > fileFile akan berisi apa yang Anda gambarkan. Namun, dalam kasus saya, saya menjalankan semuanya dari tcpserver (3), jadi saya ingin memasukkan stdin (yang berisi data klien) juga. Saya tidak yakin bagaimana melakukan itu.
Roel Baardman
1
Tergantung pada apa cmd1, cmd2, ... adalah. Jika nc atau cat dan data Anda berorientasi garis, hasilnya mungkin salah - Anda akan mendapatkan garis yang terdiri dari awal garis yang dicetak oleh cmd1 dan ujung garis yang dicetak oleh cmd2.
pizdelect