LINUX.ORG.RU

Буфер вывода в Perl - как отключить? (вывод от wine)

 , ,


0

2

На моем сервера (Debian squeeze 6 AMD64) под wine работают 2 серверных консольных приложения. Запускаю их обычно с SSH с помощью perl скрипта (совместил несколько образцов давным давно), что бы не зависеть от ssh сессий, и писать вывод в файл (фактически получается лог работы приложения). Но, как я понял, пишет в файл он какими-то кусками, причём если приложение аварийно завершилось, последний кусок не вписывается (а он-то как раз нужнее всех), а если приложение было закрыто совсем быстро, так выходной файл вообще оказывался пуст.

вот мой скрипт:

#!/usr/bin/perl
use POSIX qw(setsid);
chdir '*рабочая директория приложений*'       or die "Can't chdir to /: $!";
umask 0;
($sec, $min, $hour, $day, $month, $year) = (localtime)[0..5];
my $dt=($year+1900).'-'.($month+1).'-'.$day.'_'.$hour.':'.$min.':'.$sec.'.log';
open STDIN, '/dev/null'   or die "Can't read /dev/null: $!";
open STDOUT, '>out_'.$dt or die "Can't write to stdout: $!";
open STDERR, '>er_'.$dt or die "Can't write to stderr: $!";
defined(my $pid = fork)   or die "Can't fork: $!";
exit if $pid;
setsid                    or die "Can't start a new session: $!";
system("wine приложение1.exe &");
system("wine приложение2.exe &");
честно, я пытался найти решение в google - и нашёл 2 варианта -
1 - поставить $| = 1;
я пробовал ставить во все места кода, в конце концов вставил между всех строк:
#!/usr/bin/perl
use POSIX qw(setsid);
$| = 1;
chdir '*рабочая директория приложений*'       or die "Can't chdir to /: $!";
umask 0;

($sec, $min, $hour, $day, $month, $year) = (localtime)[0..5];
my $dt=($year+1900).'-'.($month+1).'-'.$day.'_'.$hour.':'.$min.':'.$sec.'.log';
$| = 1;
open STDIN, '/dev/null'   or die "Can't read /dev/null: $!";
$| = 1;
open STDOUT, '>out_'.$dt or die "Can't write to stdout: $!";
$| = 1;
open STDERR, '>er_'.$dt or die "Can't write to stderr: $!";
$| = 1;
defined(my $pid = fork)   or die "Can't fork: $!";
exit if $pid;
setsid                    or die "Can't start a new session: $!";
$| = 1;
system("wine приложение1.exe &");
$| = 1;
system("wine приложение2.exe &");
$| = 1;
и не помогает - всё в прежнем виде.
другой вариант -

если вас не пугают последствия, вообще запретите буферизацию вызовом метода autoflush из модулей IO: use IO::Handle; OUTPUT_HANDLE->autoflush(1);

увы тоже не подошёл (вставил с 2й строки):

Can't locate object method «autoflush» via package «OUTPUT_HANDLE» (perhaps you forgot to load «OUTPUT_HANDLE»?) at tstart.pl line 3.

такие вот последствия, и никакого запуска, разумеется.
Огромная просьба помочь с решением этой проблемы вывода



Последнее исправление: BigBan (всего исправлений: 4)

($sec, $min, $hour, $day, $month, $year) = (localtime)[0..5];
my $dt=($year+1900).'-'.($month+1).'-'.$day.'_'.$hour.':'.$min.':'.$sec.'.log';

use POSIX 'strftime';

system(«wine приложение1.exe &»);

use IPC::Run;

А так у Вас из-под Perl запускается шелл и запускает программы, да ещё и в фоне. Естественно, что часть текста в таких условиях может потеряться.

Также можно попробовать Capture::Tiny для отлова STDOUT и STDERR произвольного кода.

AITap ★★★★★
()
Последнее исправление: AITap (всего исправлений: 1)

Can't locate object method «autoflush» via package OUTPUT_HANDLE" (perhaps you forgot to load «OUTPUT_HANDLE»?) at tstart.pl line 3.

use IO::Handle;
STDERR->autoflush(1);
STDOUT->autoflush(1);
disarmer ★★★
()
Ответ на: комментарий от disarmer

А вообще можно использовать select, вроде такого:

use IO::Select;
use IO::Handle;
my $select=IO::Select->new();
for my $cmd(@cmds){
    open ($fh, "$cmd |") or die $cmd;
    $select->add($fh);
    $fh->autoflush(1);
}
while(1){
    while(my @ready = $select->can_read){
        for my $read(@ready){
            my $name=$pipes{$read};
            $_=<$read>;
            #обработка
        }
    }
}
disarmer ★★★
()

Вкратце: библиотека glibc включает режим «полной буферизации», если видит, что соответствующий stdout/stderr не является устройством терминала.

Способов обойти это несколько. Самый очевидный (и не самый лучший на мой взгляд) — использовать псевдотерминалы. Возможно, в перлячке уже есть что-то встроенное на этот счет, что не отменяет тяжеловесности и общей отстойности этого способа.

Можно поправить исходники программы — вставить setlinebuf(stdout) в первых строчках main — не всегда приемлемо.

Можно позвать setlinebuf через трюк с LD_PRELOAD:

$ cat novbuf.c 
#include <stdio.h>
#include <termios.h>

static  __attribute__((constructor)) void setbufs()
{
	setlinebuf(stdout);
	setlinebuf(stderr);
}
$ gcc -fPIC -Wall -shared -o novbuf.so novbuf.c
# system("LD_PRELOAD=/full-path-to-novbuf.so wine бла-бла-бла") в перлячке

red_eyed_peguin
()

я пробовал ставить во все места кода, в конце концов вставил между всех строк

Senior developer detected!

DELIRIUM ☆☆☆☆☆
()

> 1 - поставить $| = 1;
> я пробовал ставить во все места кода, в конце концов вставил между всех строк

«$| = 1;» включает autoflush для текущего дефолтного хендлера (по дефолту это STDOUT). чтобы таким способом включить autoflush, тебе нужно сделать его дефолтным (а потом не забыть вернуть всё взад, если необходимо):

select OUTPUT_HANDLE;
$| = 1;
select STDOUT;

или же в функцию обернуть:

sub set_autoflush {
    my $fh = shift;
    my $old = select $fh;
    $| = 1;
    select $old;
}

set_autoflush OUTPUT_HANDLE;

а включать его нужно только раз, сразу после открытия файла, например.

arsi ★★★★★
()
Ответ на: комментарий от disarmer

увы, это не помогло: вставлял соответственно перед «open STDOUT» и «open STDERR », пробовал и после них. ошибок нет, но и результата нет. посмотрю ещё другое

BigBan
() автор топика

В следующем порядке: форк, смерть родителя, создание новой сессии, закрытие потока вывода + открытие нового с редиректом в файл.

defined( my $pid = fork ) or die "can't fork: $!";
$pid and exit;

# detach ourselves from the terminal
POSIX::setsid()
    or die "cannot detach from controlling terminal: $!";

close STDIN;
open STDIN, '+>/dev/null' or die "can't read /dev/null: $!";

close STDOUT;
open STDOUT, "+>out_$dt" or die "can't write to stdout: $!";

close STDERR;
open STDERR, "+>er_$dt" or die "can't write to stderr: $!";
outtaspace ★★★
()

И еще. Задача ваша мне непонятна. Вызов встроенной system давно перестал использовать, для сурового продакшена использую следующий кейс:

use English qw( -no_match_vars );

# много сахара
use IPC::Open3::Utils qw( put_cmd_in :err );

put_cmd_in( "wine приложение1.exe &", \my $stdout, \my $stderr, {
    close_stdin                       => 1,
    carp_open3_errors          => 1,
    stop_read_on_open3_err => 1,
} );

if ( $CHILD_ERROR ) {
    warn 'command exited with signal: '.
        child_error_exit_signal( $CHILD_ERROR );
    warn 'command exited with value: '.
        child_error_exit_value( $CHILD_ERROR );
}
else {
    say $stdout;
}


Зачем вам демонизация основного (перлового) процесса?
outtaspace ★★★
()
Ответ на: комментарий от outtaspace

не получилось.
вот код (после вычисления $dt) :

defined( my $pid = fork ) or die "can't fork: $!";
$pid and exit;

# detach ourselves from the terminal
POSIX::setsid()
    or die "cannot detach from controlling terminal: $!";

    close STDIN;
    open STDIN, '+>/dev/null' or die "can't read /dev/null: $!";

    close STDOUT;
    open STDOUT, "+>out_$dt" or die "can't write to stdout: $!";
    STDOUT->autoflush(1);
    close STDERR;
    open STDERR, "+>er_$dt" or die "can't write to stderr: $!";
    STDERR->autoflush(1);
system('wine tMainServer.exe >> "'.$dt.'" &');
system('wine tServer.exe >> "'.$dt.'" &');
ни ошибок, ни результата.
попробую следующий совет.

BigBan
() автор топика
Ответ на: комментарий от BigBan

Вот этот код у меня прекрасно работает (revision 5 version 10 subversion 1):

#!/usr/bin/perl

use strict;
use warnings;
use POSIX qw();
use autodie qw( open close );
use IO::Handle;

my $dt = '____.log';

defined( my $pid = fork ) or die "can't fork: $!";
$pid and exit;

# detach ourselves from the terminal
POSIX::setsid() or die "cannot detach from controlling terminal: $!";

close STDIN;
open STDIN, '+>/dev/null';

close STDOUT;
open STDOUT, "+>out_$dt";
STDOUT->autoflush(1);

close STDERR;
open STDERR, "+>er_$dt";
STDERR->autoflush(1);

for ( 1 .. 5 ) {
    print STDOUT 'bip bip';
    print STDERR 'vap vap';
    sleep 5;
    qx{echo 'mau mau' >> out_$dt &};
}

outtaspace ★★★
()
Ответ на: комментарий от outtaspace

тоже ничего не вышло
про демонизацию не очень понял. с горем пополам установил IPC::Open3::Utils, вот полный код

#!/usr/bin/perl
use POSIX qw(strftime);
use POSIX qw(setsid);
use IO::Handle;
chdir '*директория*'       or die "Can't chdir to /: $!";
umask 0;
my $dt=strftime("%Y-%m-%d %H:%M:%S.log", localtime);

defined( my $pid = fork ) or die "can't fork: $!";
$pid and exit;

my $stdout ='out_' .$dt;
my $stderr ='err_' .$dt;

use English qw( -no_match_vars );

# много сахара
use IPC::Open3::Utils qw( put_cmd_in :err );

put_cmd_in( "wine пр1.exe &", /my $stdout, /my $stderr, {
    close_stdin                       => 1,
    carp_open3_errors          => 1,
    stop_read_on_open3_err => 1,
} );


put_cmd_in( "wine пр2.exe &", /my $stdout,  $stderr, {
    close_stdin                       => 1,
    carp_open3_errors          => 1,
    stop_read_on_open3_err => 1,
} );


if ( $CHILD_ERROR ) {
    warn 'command exited with signal: '.
        child_error_exit_signal( $CHILD_ERROR );
    warn 'command exited with value: '.
        child_error_exit_value( $CHILD_ERROR );
}
else {
    say $stdout;
}
более того, теперь в процессах вист ещё и «perl *мой скрипт*», и зпускается вначале только процесс 1, после я запускаю свой скрипт на kill обоих процессов, и убивается 1й, смотрю на список - опа, а тут и второй запустился (после kill первого). при kill второго убивается 2й и «perl *мой скрипт*».
выходные же файлы не создаются вовсе.
сейчас попробую следующий совет

BigBan
() автор топика
Ответ на: комментарий от outtaspace

этот пример у меня тоже работает.
а вот что не работает:

#!/usr/bin/perl
use POSIX qw(strftime);
use strict;
use warnings;
use POSIX qw();
use autodie qw( open close );
use IO::Handle;
chdir 'директория'       or die "Can't chdir to /: $!";
umask 0;
my $dt=strftime("%Y-%m-%d %H:%M:%S.log", localtime);

defined( my $pid = fork ) or die "can't fork: $!";
$pid and exit;

# detach ourselves from the terminal
POSIX::setsid() or die "cannot detach from controlling terminal: $!";

close STDIN;
open STDIN, '+>/dev/null';

close STDOUT;
open STDOUT, "+>out_$dt";
STDOUT->autoflush(1);

close STDERR;
open STDERR, "+>er_$dt";
STDERR->autoflush(1);

system('wine приложение1.exe &');
system('wine приложение2.exe &');
создаются пустые файлы, процессы запускаются, но запись выхода не происходит

BigBan
() автор топика
Ответ на: комментарий от BigBan

про демонизацию не очень понял

Я интересовался целесообразностью демонизации гравного процесса - напишите взаимодействие дочерних процессов («пр1.exe» и «пр2.exe») с главным в минималистичном примере, а затем уже решайте более общую проблему.

Также у вас неряшливый стиль разработки. Включите уже strict и warnings семантику интерпретатора. Может что-то новое узнаем о вашей проблеме.

outtaspace ★★★
()
Ответ на: комментарий от outtaspace

Поискал про неё.. как я понял, это операции с pid и fork.
Взаимодействуют между собой только exe-шники по udp.
А зачем тут она?.. наверно, что бы процессы не останавливались при завершении ssh сессии, точнее сказать не могу, если есть что-то лишнее - могу убрать.

По поводу задачи и стиля - возможно, мне надо было создать тему по-другому, т.к. мне не важно, как будет выполнена задача (perl, sh или что ещё), важен итог. А стиль - честно говоря, этот скрипт - моё первое, и пока что единственное знакомство с перлом.

BigBan
() автор топика
Ответ на: комментарий от red_eyed_peguin

итак, сделал то, что Вы писали в своём предыдущем комменте: получил в err_.. вот что:

ERROR: ld.so: object '/home/*путь*/novbuf.so' from LD_PRELOAD cannot be preloaded: ignored.
ERROR: ld.so: object '/home/*путь*/novbuf.so' from LD_PRELOAD cannot be preloaded: ignored.
ERROR: ld.so: object '/home/*путь*/novbuf.so' from LD_PRELOAD cannot be preloaded: ignored.
ERROR: ld.so: object '/home/*путь*/novbuf.so' from LD_PRELOAD cannot be preloaded: ignored.

путь верный, я перепроверил
в out_ - пусто (что и не удивительно с такой ошибкой)

BigBan
() автор топика
Ответ на: комментарий от BigBan

наверно, что бы процессы не останавливались при завершении ssh сессии

А обертка из `nohup perl tratata.pl &` будет работать через ssh? Если будет, то лучше такой вариант (более модульный) использовать, а не городить лишний код на Perl. Советую на nohup посмотреть.

outtaspace ★★★
()

2 all:
похоже, проблема не в перле, а в wine - я попробовал запустить просто
wine приложение.exe > 12345.log &
и ничего не попало в лог, равно то же в wine cmd

BigBan
() автор топика
Ответ на: комментарий от red_eyed_peguin

система - 64 (писал в 1м сообщении)
насчёт wine не знаю, но запускаемые exe - 32
попробовал, попутно установив g++-multilib

gcc -m32 -fPIC -Wall -shared -o novbuf.so novbuf.c
итог - почти такой же, разве что сообщений теперь в 2 раза больше (перезапускал для проверки несколько раз, каждый раз формируются новые файлы)
ERROR: ld.so: object '/home/путь/novbuf.so' from LD_PRELOAD cannot be preloaded: ignored.
ERROR: ld.so: object '/home/путь/novbuf.so' from LD_PRELOAD cannot be preloaded: ignored.
ERROR: ld.so: object '/home/путь/novbuf.so' from LD_PRELOAD cannot be preloaded: ignored.
ERROR: ld.so: object '/home/путь/novbuf.so' from LD_PRELOAD cannot be preloaded: ignored.
ERROR: ld.so: object '/home/путь/novbuf.so' from LD_PRELOAD cannot be preloaded: ignored.
ERROR: ld.so: object '/home/путь/novbuf.so' from LD_PRELOAD cannot be preloaded: ignored.
ERROR: ld.so: object '/home/путь/novbuf.so' from LD_PRELOAD cannot be preloaded: ignored.
ERROR: ld.so: object '/home/путь/novbuf.so' from LD_PRELOAD cannot be preloaded: ignored.

BigBan
() автор топика
Ответ на: комментарий от BigBan

А

LD_PRELOAD=/full-path-to-novbuf.so wine приложение.exe > 12345.log
Как себя ведет?

Что показывают

$ file `which wine`
$ file /full-path-to-novbuf.so
?

red_eyed_peguin
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.