Tuberías con fugas en linux

12

Supongamos que tiene una tubería como la siguiente:

$ a | b

Si bdeja de procesar la entrada estándar, después de un tiempo la tubería se llena y escribe desde asu entrada estándar, se bloqueará (hasta que bcomience a procesar nuevamente o muera).

Si quisiera evitar esto, podría sentir la tentación de usar una tubería más grande (o, más simplemente buffer(1)) de esta manera:

$ a | buffer | b

Esto simplemente me daría más tiempo, pero al final afinalmente se detendría.

Lo que me encantaría tener (para un escenario muy específico que estoy abordando) es tener una tubería "con fugas" que, cuando está llena, dejaría caer algunos datos (idealmente, línea por línea) desde el búfer para permitir acontinuar procesamiento (como probablemente pueda imaginar, los datos que fluyen en la tubería son prescindibles, es decir, tener los datos procesados bes menos importante que apoder ejecutarlos sin bloquearlos).

Para resumir, me encantaría tener algo así como un búfer limitado y con fugas:

$ a | leakybuffer | b

Probablemente podría implementarlo con bastante facilidad en cualquier idioma, me preguntaba si hay algo "listo para usar" (o algo así como un bash one-liner) que me estoy perdiendo.

Nota: en los ejemplos estoy usando tuberías regulares, pero la pregunta se aplica igualmente a las tuberías con nombre


Si bien obtuve la respuesta a continuación, también decidí implementar el comando leakybuffer porque la solución simple a continuación tenía algunas limitaciones: https://github.com/CAFxX/leakybuffer

CAFxX
fuente
¿Las tuberías con nombre realmente se llenan? Pensé que las tuberías con nombre son la solución a esto, pero no podría decirlo con certeza.
Comodín
3
Las tuberías con nombre tienen (por defecto) la misma capacidad que las tuberías sin nombre, AFAIK
CAFxX

Respuestas:

14

La forma más fácil sería canalizar a través de algún programa que establezca la salida sin bloqueo. Aquí hay un simple perl oneliner (que puede guardar como leakybuffer ) que lo hace:

entonces tu se a | bconvierte en:

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

lo que hace es leer la entrada y escribir en la salida (igual que cat(1)) pero la salida no se bloquea, lo que significa que si la escritura falla, devolverá un error y perderá datos, pero el proceso continuará con la siguiente línea de entrada, ya que ignoramos convenientemente error. El proceso tiene el tipo de línea de búfer que desea, pero vea la advertencia a continuación.

puedes probar con, por ejemplo:

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

obtendrá un outputarchivo con líneas perdidas (el resultado exacto depende de la velocidad de su shell, etc.) de esta manera:

12768
12769
12770
12771
12772
12773
127775610
75611
75612
75613

puedes ver dónde perdió el shell líneas después 12773, pero también una anomalía: el perl no tenía suficiente búfer, 12774\npero sí 1277lo hizo, así que escribió exactamente eso, por lo que el siguiente número 75610no comienza al comienzo de la línea, por lo que es poco feo.

Eso podría mejorarse haciendo que Perl detecte cuándo la escritura no tuvo éxito por completo, y luego intente eliminar el resto de la línea mientras ignora las nuevas líneas entrantes, pero eso complicaría mucho más el guión de Perl, por lo que se deja como un ejercicio para el lector interesado :)

Actualización (para archivos binarios): si no está procesando líneas terminadas en nueva línea (como archivos de registro o similares), debe cambiar ligeramente el comando, o Perl consumirá grandes cantidades de memoria (dependiendo de la frecuencia con la que aparecen caracteres de nueva línea en su entrada):

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

funcionará correctamente también para archivos binarios (sin consumir memoria adicional).

Actualización2: salida de archivo de texto más agradable: evitar búferes de salida (en syswritelugar de 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

parece solucionar problemas con "líneas combinadas" para mí:

12766
12767
12768
16384
16385
16386

(Nota: se puede verificar en qué líneas se cortó la salida con: perl -ne '$c++; next if $c==$_; print "$c $_"; $c=$_' outputoneliner)

Matija Nalis
fuente
Me encanta el oneliner: no soy un experto en Perl, si alguien pudiera sugerir las mejoras anteriores sería increíble
CAFxX
1
Esto parece funcionar hasta cierto punto . Pero mientras veo mi comando, que es perl -w -MFcntl -e 'fcntl STDOUT,F_SETFL,O_WRONLY|O_NONBLOCK; while (<STDIN>) { print }' | aplay -t raw -f dat --buffer-size=16000, Perl parece asignar continuamente más memoria hasta que el administrador de OOM lo mata.
Ponkadoodle
@Wallacoloo, gracias por señalarlo, mi caso fue la transmisión de archivos de registro ... Vea la respuesta actualizada para un ligero cambio necesario para admitir archivos binarios.
Matija Nalis
Ver también GNU dd's dd oflag=nonblock status=none.
Stéphane Chazelas
1
Lo siento, mi error otra vez, en realidad las escrituras de menos de PIPE_BUF bytes (4096 en Linux, se requiere que sea al menos 512 por POSIX) se garantiza que sean atómicas, por lo que $| = 1su syswrite()enfoque evita escrituras cortas siempre que las líneas sean razonablemente cortas.
Stéphane Chazelas