Pipa "Leaky" di linux

12

Anggap Anda memiliki saluran pipa seperti berikut:

$ a | b

Jika bberhenti memproses stdin, setelah beberapa saat pipa terisi, dan menulis, dari ake stdout, akan memblokir (sampai bmulai memproses lagi atau mati).

Jika saya ingin menghindari ini, saya bisa tergoda untuk menggunakan pipa yang lebih besar (atau, lebih sederhana, buffer(1)) seperti:

$ a | buffer | b

Ini hanya akan memberi saya lebih banyak waktu, tetapi pada akhirnya aakan berhenti.

Apa yang ingin saya miliki (untuk skenario yang sangat spesifik yang saya bahas) adalah memiliki pipa "bocor" yang, ketika penuh, akan menjatuhkan beberapa data (idealnya, baris demi baris) dari buffer untuk amelanjutkan pemrosesan (seperti yang dapat Anda bayangkan, data yang mengalir di pipa dapat dibuang, yaitu memiliki data yang diproses bkurang penting daripada harus adapat berjalan tanpa memblokir).

Singkatnya, saya ingin memiliki sesuatu seperti buffer yang dibatasi dan bocor:

$ a | leakybuffer | b

Saya mungkin bisa mengimplementasikannya dengan mudah dalam bahasa apa pun, saya hanya ingin tahu apakah ada sesuatu yang "siap digunakan" (atau sesuatu seperti bash one-liner) yang saya lewatkan.

Catatan: dalam contoh saya menggunakan pipa biasa, tetapi pertanyaannya sama berlaku untuk pipa bernama


Sementara saya memberikan jawaban di bawah ini, saya juga memutuskan untuk menerapkan perintah leakybuffer karena solusi sederhana di bawah ini memiliki beberapa keterbatasan: https://github.com/CAFxX/leakybuffer

CAFxX
sumber
Apakah pipa bernama benar-benar terisi? Saya akan berpikir pipa bernama adalah solusi untuk ini, tetapi saya tidak bisa mengatakan dengan pasti.
Wildcard
3
Pipa yang dinamai memiliki (secara default) kapasitas yang sama dengan pipa yang tidak disebutkan namanya, AFAIK
CAFxX

Jawaban:

14

Cara termudah adalah dengan menyalurkan melalui beberapa program yang menetapkan output nonblocking. Berikut ini perl oneliner sederhana (yang dapat Anda simpan sebagai leakybuffer ) yang melakukannya:

jadi Anda a | bmenjadi:

a | perl -MFcntl -e \
    'fcntl STDOUT,F_SETFL,O_NONBLOCK; while (<STDIN>) { print }' | b

apa yang dilakukan adalah membaca input dan menulis ke output (sama seperti cat(1)) tetapi output nonblocking - artinya jika penulisan gagal, itu akan mengembalikan kesalahan dan kehilangan data, tetapi proses akan melanjutkan dengan baris input berikutnya karena kita dengan mudah mengabaikan kesalahan. Proses semacam-buffer line seperti yang Anda inginkan, tetapi lihat peringatan di bawah ini.

Anda dapat menguji dengan misalnya:

seq 1 500000 | perl -w -MFcntl -e \
    'fcntl STDOUT,F_SETFL,O_NONBLOCK; while (<STDIN>) { print }' | \
    while read a; do echo $a; done > output

Anda akan mendapatkan outputfile dengan garis yang hilang (output yang tepat tergantung pada kecepatan shell Anda dll.) seperti ini:

12768
12769
12770
12771
12772
12773
127775610
75611
75612
75613

Anda melihat di mana shell kehilangan garis setelahnya 12773, tetapi juga anomali - perl tidak memiliki cukup buffer untuk 12774\ntetapi melakukannya untuk 1277itu hanya menulis itu - dan nomor berikutnya 75610tidak dimulai pada awal baris, membuatnya sedikit jelek.

Itu dapat ditingkatkan dengan memiliki perl mendeteksi ketika menulis tidak berhasil sepenuhnya, dan kemudian mencoba untuk menyiram sisa baris sambil mengabaikan baris baru yang masuk, tetapi itu akan menyulitkan skrip perl lebih banyak, sehingga dibiarkan sebagai latihan untuk pembaca yang tertarik :)

Update (untuk file biner): Jika Anda tidak memproses baris baru dihentikan garis (seperti file log atau serupa), Anda perlu perintah perubahan sedikit, atau perl akan mengkonsumsi sejumlah besar memori (tergantung seberapa sering karakter baris baru muncul di masukan Anda):

perl -w -MFcntl -e 'fcntl STDOUT,F_SETFL,O_NONBLOCK; while (read STDIN, $_, 4096) { print }' 

itu akan berfungsi dengan benar untuk file biner juga (tanpa menggunakan memori tambahan).

Update2 - output file teks yang lebih bagus: Menghindari buffer output ( syswritebukan print):

seq 1 500000 | perl -w -MFcntl -e \
    'fcntl STDOUT,F_SETFL,O_NONBLOCK; while (<STDIN>) { syswrite STDOUT,$_ }' | \
    while read a; do echo $a; done > output

tampaknya memperbaiki masalah dengan "garis gabungan" untuk saya:

12766
12767
12768
16384
16385
16386

(Catatan: orang dapat memverifikasi di mana garis keluaran dipotong dengan: perl -ne '$c++; next if $c==$_; print "$c $_"; $c=$_' outputoneliner)

Matija Nalis
sumber
Saya suka oneliner: Saya bukan ahli perl, kalau ada yang bisa menyarankan perbaikan di atas itu akan luar biasa
CAFxX
1
Ini tampaknya berhasil sampai batas tertentu . Tetapi ketika saya memperhatikan perintah saya perl -w -MFcntl -e 'fcntl STDOUT,F_SETFL,O_WRONLY|O_NONBLOCK; while (<STDIN>) { print }' | aplay -t raw -f dat --buffer-size=16000, perl tampaknya terus mengalokasikan lebih banyak memori hingga terbunuh oleh manajer OOM.
Ponkadoodle
@ Wallalloloo terima kasih untuk menunjukkan hal itu, kasus saya adalah streaming file log ... Lihat jawaban yang diperbarui untuk sedikit perubahan yang diperlukan untuk mendukung file biner.
Matija Nalis
Lihat juga GNU dd's dd oflag=nonblock status=none.
Stéphane Chazelas
1
Maaf, buruk saya lagi, sebenarnya menulis kurang dari PIPE_BUF byte (4096 di Linux, harus setidaknya 512 oleh POSIX) dijamin atom, jadi $| = 1dan syswrite()pendekatan Anda mencegah penulisan pendek memang selama garis cukup pendek.
Stéphane Chazelas