A
A. Farber
Hello,
I have a multiplayer game (at preferans.de)
as a non-forking server in Perl (v5.10.0 under
OpenBSD 4.5) and it runs mostly okay with
average of 10 connected users and uses 0.2% CPU.
However once in a week the perl process would
"spin up" up to 98% CPU and would stop responding.
It is difficult to find the reason for this and
I can't reproduce it while testing myself.
I've made the listening TCP socket
non-blocking to prevent DOS attacks and
the main loop in my server looks like this:
sub prepare {
my $pkg = shift;
for my $child (values %Kids) {
my $fh = $child->{FH};
# the outgoing buffer is not empty - add POLLOUT
if (length $child->{RESPONSE} != 0) {
$Poll->mask($fh => POLLIN|POLLERR|POLLHUP|
POLLOUT);
} else {
$Poll->mask($fh => POLLIN|POLLERR|POLLHUP);
}
}
}
sub loop {
my $pkg = shift;
LOOP:
while (not $Quit) {
$pkg->prepare();
if ($Poll->poll(TIMEOUT) < 0) {
warn "poll error: $!\n";
next LOOP;
}
# add the new client
if ($Poll->events($tcpSocket) & POLLIN) {
$pkg->add($tcpSocket);
}
for my $child (values %Kids) {
my $fh = $child->{FH};
my $mask = $Poll->events($fh);
if ($mask & (POLLERR|POLLHUP)) {
$child->remove();
next LOOP;
} elsif ($mask & POLLOUT) {
unless ($child->write()) {
$child->remove();
next LOOP;
}
} elsif ($mask & POLLIN) {
unless ($child->read()) {
$child->remove();
next LOOP;
}
}
}
}
}
The client sockets are non-blocking too
and I try to ignore (i.e. retry sysread/write)
on signals and would-block situations:
sub write {
my $child = shift;
my $fh = $child->{FH};
my $len = bytes::length $child->{RESPONSE};
my $nbytes;
$nbytes = $fh->syswrite($child->{RESPONSE}, $len);
unless (defined $nbytes) {
# would block - retry later
return 1 if $!{EAGAIN} || $!{EWOULDBLOCK};
# interrupted by signal - retry later
return 1 if $!{EINTR};
# connection interrupted
return 0;
}
# connection closed
return 0 if 0 == $nbytes;
substr $child->{RESPONSE}, 0, $nbytes, '';
return 1;
}
I wonder, if this retrying (as shown above)
is the real reason for the "spin-ups" somehow?
Should I maybe clear $!{EINTR} etc.
manually whenever $nbytes is undefined?
Another suspicious spot for me is the
main loop, where I remove clients while looping
(that's why I've added "next LOOP" everywhere)
Does anybody have any advices?
Regards
Alex
I have a multiplayer game (at preferans.de)
as a non-forking server in Perl (v5.10.0 under
OpenBSD 4.5) and it runs mostly okay with
average of 10 connected users and uses 0.2% CPU.
However once in a week the perl process would
"spin up" up to 98% CPU and would stop responding.
It is difficult to find the reason for this and
I can't reproduce it while testing myself.
I've made the listening TCP socket
non-blocking to prevent DOS attacks and
the main loop in my server looks like this:
sub prepare {
my $pkg = shift;
for my $child (values %Kids) {
my $fh = $child->{FH};
# the outgoing buffer is not empty - add POLLOUT
if (length $child->{RESPONSE} != 0) {
$Poll->mask($fh => POLLIN|POLLERR|POLLHUP|
POLLOUT);
} else {
$Poll->mask($fh => POLLIN|POLLERR|POLLHUP);
}
}
}
sub loop {
my $pkg = shift;
LOOP:
while (not $Quit) {
$pkg->prepare();
if ($Poll->poll(TIMEOUT) < 0) {
warn "poll error: $!\n";
next LOOP;
}
# add the new client
if ($Poll->events($tcpSocket) & POLLIN) {
$pkg->add($tcpSocket);
}
for my $child (values %Kids) {
my $fh = $child->{FH};
my $mask = $Poll->events($fh);
if ($mask & (POLLERR|POLLHUP)) {
$child->remove();
next LOOP;
} elsif ($mask & POLLOUT) {
unless ($child->write()) {
$child->remove();
next LOOP;
}
} elsif ($mask & POLLIN) {
unless ($child->read()) {
$child->remove();
next LOOP;
}
}
}
}
}
The client sockets are non-blocking too
and I try to ignore (i.e. retry sysread/write)
on signals and would-block situations:
sub write {
my $child = shift;
my $fh = $child->{FH};
my $len = bytes::length $child->{RESPONSE};
my $nbytes;
$nbytes = $fh->syswrite($child->{RESPONSE}, $len);
unless (defined $nbytes) {
# would block - retry later
return 1 if $!{EAGAIN} || $!{EWOULDBLOCK};
# interrupted by signal - retry later
return 1 if $!{EINTR};
# connection interrupted
return 0;
}
# connection closed
return 0 if 0 == $nbytes;
substr $child->{RESPONSE}, 0, $nbytes, '';
return 1;
}
I wonder, if this retrying (as shown above)
is the real reason for the "spin-ups" somehow?
Should I maybe clear $!{EINTR} etc.
manually whenever $nbytes is undefined?
Another suspicious spot for me is the
main loop, where I remove clients while looping
(that's why I've added "next LOOP" everywhere)
Does anybody have any advices?
Regards
Alex