Фон:
Я пытаюсь написать небольшой скрипт, который одновременно регистрирует JSONS, если размеры файлов небольшие, все в порядке. Но когда размеры файлов большие, процессы начинают перезаписывать друг друга. Этот пост SO полезен для указания правильного направления: PIPE_BUFF. Кажется, в Windows установлено значение 1024, в Linux больше Насколько велик PIPE_BUFF?.
PS: я на WSL2 Ubuntu 20.04
Проблема
Я попытался установить значение PIPE_BUFF с помощью Константа Fcntl F_SETPIPE_SZ, однако мне это не удалось:
#!/usr/bin/perl
use strict;
use warnings;
use utf8;
use Data::Dumper qw(Dumper);
use File::Basename qw(basename dirname);
use File::Spec qw(catfile file_name_is_absolute catdir);
use feature qw(say current_sub);
use Cwd qw(abs_path);
use lib dirname(abs_path $0);
use Fcntl qw(F_SETPIPE_SZ F_GETPIPE_SZ F_GETFL F_GETFD F_SETFL O_NONBLOCK O_WRONLY O_APPEND O_CREAT O_RDWR F_DUPFD F_SETOWN F_GETOWN);
open(my $fileH,">", File::Spec -> catfile(dirname(__FILE__), "blabla.txt"));
#does F_GETFD work?
my $val = fcntl($fileH, F_GETFD, 0);
say $val; #outputs 1, works
#does F_GETFL work?
my $val2 = fcntl($fileH, F_GETFL, 0);
say $val2; #outputs 32769, works
#does F_GETOWN work?
my $val3 = fcntl($fileH, F_GETOWN, 0);
say $val3; #outputs 0 but true, so it works too.
my $pid = +$$;#process id
#does F_SETOWN work?
my $val4 = fcntl($fileH, F_SETOWN, $pid) or die("error: $!");;
say $val4; #outputs 0 but true
#does getting pipe buffer work?
my $val5 = fcntl($fileH, F_GETPIPE_SZ, 0) or die("error: $!"); #"Bad file descriptor"
say $val5; #Use of uninitialized..so $val5 is undef, did not work
#does setting pipe buffer work?
my $val6 = fcntl($fileH, F_SETPIPE_SZ, 1000 * 1000) or die("error: $!"); #"Bad file descriptor"
say $val6; #undef
Некоторые константы, такие как F_GETFD или F_GETFL, работают, поэтому я предполагаю, что Fcntl работает правильно.
Однако F_SETPIPE_SZ и некоторые другие, похоже, вообще не работают. Передача fcntl
fileno($fileH)
вместо $fileH
приводит к ошибке «дескриптор неоткрытого файла», так что это тоже ничего не меняет.
Какова основная причина этого?
Я знаю о стаях. Поскольку я могу подтвердить, что сохранение PIPE_BUFF в пределах разумного предела, такого как 1024, никогда не приводит к перезаписи (поскольку записи являются атомарными), я отказываюсь от «Y». Меня особенно интересует, почему я не могу заставить Fcntl работать и в чем причина журналов ошибок. «Неверный файловый дескриптор» — ужасно общее сообщение.
Если вы используете $!
в неправильном контексте, это может привести к странным ошибкам. Вы не можете полагаться на $!
, чтобы сказать, когда вы сделали что-то не так.
Попробуйте например perl -lwe'print $a++ . $!; open F, "a.txt" or die $!; print $a++ . $!;'
. В моей системе отображается ошибка Inappropriate I/O control operation
, хотя явно все в порядке.
@TLP Обновлен пример, чтобы убедиться, что я ловлю $! в правильном контексте. Все равно "Неверный файловый дескриптор". Хотя я могу нормально печатать на этот дескриптор.
Я не нашел никакой документации, описывающей F_GETPIPE_SZ
. Кроме того, я получаю Your vendor has not defined Fcntl macro F_GETPIPE_SZ
, если пытаюсь его использовать. Это похоже на мутную воду, в которой вы ловите рыбу.
@TLP Они специфичны для Linux. OP связан с соответствующей справочной страницей.
@Shawn нет, файл не является именованным каналом, mkfifo вообще НЕ используется. Я думал, что смогу контролировать, сколько байтов записывается в обычные файлы, используя fcntl.... Ой.. Если это не так, вы можете ответить, и я приму
Флаги Linux fcntl()
F_GETPIPE_SZ
и F_SETPIPE_SZ
, как следует из их названий, относятся к трубы. Вы пытаетесь использовать их с обычным файлом, отсюда и сбои.
Для ясности:
#!/usr/bin/env perl
use strict;
use warnings;
use feature qw/say/;
use Fcntl qw/F_GETPIPE_SZ/;
open my $file, ">", "/tmp/foo.txt" or die "Unable to open /tmp/foo.txt: $!\n";
pipe my $reader, my $writer or die "Unable to create pipe: $!\n";
if (my $size = fcntl($file, F_GETPIPE_SZ, 0)) {
say "filehandle size: $size";
} else {
say "fcntl of file failed: $!";
}
if (my $size = fcntl($reader, F_GETPIPE_SZ, 0)) {
say "pipe size: $size";
} else {
say "fcntl of pipe filed: $!";
}
Возможный вывод:
fcntl of file failed: Bad file descriptor
pipe size: 65536
Я хотел бы иметь аналогичную систему для ОС, чтобы контролировать, сколько байтов записывается в обычный файл, но я думаю, что это невозможно. Отличный ответ, ти.
Вам нужно использовать блокировку файлов или какой-либо другой метод синхронизации.
flock поддерживается не во всех системах, файл с записанным в него pid не работал согласованно при высокой нагрузке (более 500 процессов), возможно, потому, что -e является системным вызовом и не всегда точен. наличие небольшого размера журнала <10 КБ никогда не создает проблем. Что-то большее, чем это, процессы начинают перезаписывать друг друга, возможно, JSON:: XS также не является потокобезопасным. Так по крайней мере я пытался..
Вы вряд ли найдете реально используемую ОС, которая не поддерживает flock
ни напрямую, ни через эмуляцию (даже работает в Windows). Но с таким количеством процессов серверная система журналов может работать лучше, чтобы избежать конкуренции за блокировки. Запись в какую-либо очередь сообщений (Posix, SysV, ZeroMQ и т. д.) или в локальные сокеты дейтаграммы с помощью программы на принимающей стороне, которая ведет фактическую регистрацию.
Что касается "Я хотел бы иметь аналогичную систему для ОС, чтобы контролировать, сколько байтов записывается в обычный файл.", это не то, что делает F_SETPIPE_SZ
. (Я имею в виду, даже не для каналов.) Он устанавливает ограничение на количество непрочитанных байтов, которые могут быть в канале. (Простые файлы, очевидно, не имеют концепции непрочитанных байтов, поэтому она не применяется к простым файлам.)
Это очень похоже на XY-задача. Ваша главная проблема в том, что разные процессы записывают в один и тот же файл...?