【问题标题】:fork and waitpid fail on linux. Without hitting the hard or soft limitsfork 和 waitpid 在 Linux 上失败。没有达到硬或软限制
【发布时间】:2011-09-27 07:59:59
【问题描述】:

我有一个必须按需创建和关闭线程的进程。

每个线程使用 open2 派生一个新进程。有时open2在执行程序很长时间后有时会无法fork进程并给出“无法分配内存错误”,有时线程也会发生这种情况。我知道Linux有软硬限制,但并发数我的服务器的线程和进程不超过这些值。

是否有类似进程和线程数量的计数器,可以在一段时间后消除线程和进程创建?

如果是这样,像 Postgres 这样的服务器如何长时间工作? 该项目有多个使用 TCP 进行通信的进程,但导致我在 mplayer 的前端描述的错误的部分是用 Perl 编写的。代码如下:

use strict;
use warnings;
use IO::Socket::INET;
use IO::Select;
use POSIX ":sys_wait_h";
use IPC::Open2;
use 5.010;
use Config;
BEGIN
{
    if(!$Config{useithreads})
    {
        die "Your perl does not compiled with threading support.";    
    }
}
use threads;
use threads::shared;
use constant
{
    SERVER_PORT=>5000,
    #Remote request packet fields
    PACKET_REQTYPE=>0,
    PACKET_FILENAM=>1,
    PACKET_VOLMLVL=>2,
    PACKET_ENDPOSI=>3,
    PACKET_SEEKPOS=>4,
    #our request typs
    PLAY_REQUEST=>1,
    STOP_REQUEST=>2,
    INFO_REQUEST=>3,
    VOCH_REQUEST=>4,
    PAUS_REQUEST=>5,
    PLPA_REQUEST=>6,
    SEEK_REQUEST=>7,
    #Play states
    STATE_PAUS=>0,
    STATE_PLAY=>1,
    STATE_STOP=>2,
};

#The following line must be added because of a bad behavior in the perl thread library that causes a SIGPIPE to be generated under heavy usage of the threads.
$SIG{PIPE} = 'IGNORE';
#This variable holds the server socket object
my $server_socket;
#This array is used to hold objects of our all threads
my @thread_objects;
#create the server socket
$server_socket=IO::Socket::INET->new(LocalPort=>SERVER_PORT,Listen=>20,Proto=>'tcp',Reuse=>1) or
die "Creating socket error ($@)";
#Now try to accept remote connections
print "Server socket created successfully now try to accept remote connections on port: ".SERVER_PORT."\n";
while(my $client_connection=$server_socket->accept())
{
    push @thread_objects,threads->create(\&player_thread,$client_connection);
    $thread_objects[$#thread_objects]->detach();
}



#This subroutine is used to play something using tcp-based commands
sub player_thread
{
    my $client_socket=shift;
    #create a new select object
    my $selector=IO::Select->new($client_socket);
    #this variabe is used to pars our request
    my @remote_request;
    #getting th thread id of the current thread
    my $tid=threads->self()->tid;
    #This variable is used to hold the pid of mplayer child
    my $mp_pid=-1;
    #Mplayer stdin and stdout file descriptors
    my ($MP_STDIN,$MP_STDOUT);
    #This variable is used to check if we are playing something now or not
    my $is_playing=STATE_STOP;


    print "Client thread $tid created.\n";
    while(1)
    {
        #check to see if we can read anything from our handler
        #print "Before select\n";
        #my @ready=$selector->can_read();
        #print "After select: @ready\n";
        #now the data is ready for reading so we read it here
        my $data=<$client_socket>;
        #This means if the connection is closed by the remote end
        if(!defined($data))
        {
            print "Remote connection has been closed in thread $tid mplayer id is: $mp_pid and state is: $is_playing.\n";
            #if we have an mplayer child when remote connection is closed we must wait for it
            #so that is work is done
            if($mp_pid!=-1 and $is_playing ==STATE_PLAY)
            {
                waitpid $mp_pid,0;
                $is_playing=STATE_STOP;
            }
            elsif($is_playing==STATE_PAUS and $mp_pid!=-1)
            {
                print "thread $tid is in the paused state, we must kill mplayer.\n";
                print $MP_STDIN "quit\n";
                waitpid $mp_pid,0;
                $is_playing=STATE_STOP;
            }
            last;
        }#if

        #FIXME:: Here we must validate our argument
        #Now we try to execute the command
        chomp($data);
        @remote_request=split ",",$data;
        print "@remote_request\n";

        #Trying to reap the death child and change the state of the thread
        my $dead_child=-1;
        $dead_child=&reaper($mp_pid);
        if($dead_child)
        {
            $is_playing=STATE_STOP;
            $mp_pid=-1;
        }

        given($remote_request[PACKET_REQTYPE])
        {
            when($_==PLAY_REQUEST)
            {
                print "Play request\n";
                if($is_playing==STATE_STOP)
                {
                    eval{$mp_pid=open2($MP_STDOUT,$MP_STDIN,"mplayer -slave -really-quiet -softvol -volume ".$remote_request[PACKET_VOLMLVL]." -endpos ".$remote_request[PACKET_ENDPOSI]." ./".$remote_request[PACKET_FILENAM]);};
                    print "Some error occurred in open2 system call: $@\n" if $@;
                    $is_playing=STATE_PLAY;
                    print "Mplayer pid: $mp_pid.\n";
                }
            }
            when($_==STOP_REQUEST)
            {
                print "Stop request\n";
                if($is_playing != STATE_STOP)
                {
                    print $MP_STDIN "pausing_keep stop\n";

                    #FIXME:: Maybe we should use WNOHANG here
                    my $id=waitpid $mp_pid,0;
                    print "Mplayer($id) stopped.\n";
                    $is_playing=STATE_STOP;
                    $mp_pid=-1;
                }
            }
            when($_==PAUS_REQUEST)
            {
                print "pause request\n";
                if($is_playing !=STATE_STOP)
                {
                    print $MP_STDIN "pausing_keep pause\n";
                    $is_playing=STATE_PAUS;
                }
            }
            when($_==VOCH_REQUEST)
            {
                print "volume change request\n";
                if($is_playing !=STATE_STOP)
                {
                    print $MP_STDIN "pausing_keep volume ".$remote_request[PACKET_VOLMLVL]." 1\n";
                }
            }
            when($_==INFO_REQUEST)
            {
                my $id;
                $id=&reaper($mp_pid);
                if($id > 0)
                {
                    print "Mplayer($id) stopped.\n";
                    $is_playing=STATE_STOP;
                    $mp_pid=-1;
                }

                given($is_playing)
                {
                    when($_==STATE_STOP)
                    {
                        print $client_socket "Stopped\n";
                    }
                    when($_==STATE_PAUS)
                    {
                        print $client_socket "Paused\n";
                    }
                    when($_==STATE_PLAY)
                    {
                        print $client_socket "Playing\n";
                    }
                }
            }
            when ($_==PLPA_REQUEST)
            {
                print "play paused request\n";
                if($is_playing==STATE_STOP)
                {
                    eval{$mp_pid=open2($MP_STDOUT,$MP_STDIN,"mplayer -slave -really-quiet -softvol -volume ".$remote_request[PACKET_VOLMLVL]." -endpos ".$remote_request[PACKET_ENDPOSI]." ./".$remote_request[PACKET_FILENAM]);};
                    print "Some error occurred in open2 system call: $@\n" if $@;
                    print $MP_STDIN "pausing_keep pause\n";
                    $is_playing=STATE_PAUS;
                }
            }
            when ($_==SEEK_REQUEST)
            {
                print "Seek request\n";
                if($is_playing != STATE_STOP)
                {
                    my $seek_pos=abs $remote_request[PACKET_SEEKPOS];
                    print $MP_STDIN "seek $seek_pos 2\n";
                    $is_playing=STATE_PLAY;
                }
            }
            default
            {
                warn "Invalid request($_)!!!";
                next;
            }
        }#Given

    }#while
    $client_socket->close();
    print "Thread $tid is exiting now, the child mplayer pid is: $mp_pid and state is: $is_playing.\n";
}
#The following subroutine takes a pid and if that pid is grater than 0 it tries to reap it
#if it is successful returns pid of the reaped process else 0
sub reaper
{
    my $pid=shift;
    if($pid > 0)
    {
        my $id=waitpid($pid,WNOHANG);
        if($id > 0)
        {
            return $id;               
        }
    }
    return 0;
}

【问题讨论】:

  • 我建议你展示一些代码。您的问题措辞怀疑内核的稳定性,这显然不是问题。显示代码将使我们能够看到问题,而无需依赖您对它的理解。
  • 代码不完整,但测试已经足够了。

标签: linux perl fork out-of-memory


【解决方案1】:

"Can not allocate memory error" 就是它所说的,要么用户超出了它的内存配额(检查ulimit -m,比较ps ux),要么你真的内存不足(free)。

最大用户进程的限制只是间接连接的 - 如果你 fork() 更多的进程然后用户的内存配额允许,fork() 将因 ENOMEM 失败。

您可能还想查看: What are some conditions that may cause fork() or system() calls to fail on Linux?

【讨论】:

  • 我查看了ulimit -m,在系统中它设置为'unlimited'。如上所述,我不直接使用 fork,而是使用 open2 调用。
  • 您是否在 perl 脚本运行的环境中检查过?如果它是守护进程,在屏幕下运行,在 at/cron 下运行,在 env 下运行等。它可能有不同的参数。
  • 是的,我检查了所有这些,我使用“screen”命令在 Gentoo linux 下运行了大约 24 小时。我也有其他进程,但只有这一个会导致问题。
  • 也没有使用 Cron 或 At 来运行它。它是在屏幕中手动执行的。
  • 作为旁注,当前的 linux 内核完全忽略 ulimit -m。
【解决方案2】:

我终于找到了问题,是因为Perl的线程模块中的内存泄漏,导致内存在很长一段时间后增长。然后open2无法分配内存,失败。

【讨论】:

    猜你喜欢
    • 2017-07-23
    • 2012-11-09
    • 1970-01-01
    • 2012-08-29
    • 2021-05-13
    • 1970-01-01
    • 1970-01-01
    • 2019-08-29
    • 1970-01-01
    相关资源
    最近更新 更多