Closed kazeburo closed 11 years ago
Socket.pm にたせばいいのかしら?
Linux特有のsocket機能はわりとたくさんあるので、Linux::Socketのメンテナンスを誰かが引き継ぐかSocket::Linuxを新設するのがいいとおもわれる。Linux::Socketはいま @hirose31 さんがパッチ当てて使ってるはず。
https://metacpan.org/release/Socket-Linux これか、今のところ constants だけっぽい
SALVA https://metacpan.org/author/SALVA $B$O%"%/%F%#%V$@$+$i!"%Q%C%A$$/$l$P$h$5$=$&$@$1$I!"(B@hirose31 $B$5$s$O$J$s$G%Q%C%A$"$F$F$D$+$C$F$k$s$@$m$2!#(B*
tokuhirom
On Mon, Sep 30, 2013 at 10:06 AM, Masahiro Nagano notifications@github.comwrote:
https://metacpan.org/release/Socket-Linux $B$3$l$+!":#$N$H$3$m(B constants $B$@$1$C$]$$(B
$B!=(B Reply to this email directly or view it on GitHubhttps://github.com/perl-users-jp/issues/issues/17#issuecomment-25333137 .
accept4 $B$,%3%"$K$[$7$$$C$FOC$8$c$J$/$F!"JL%i%$%V%i%j$G$$$$$C$F$O$J$7$@$C$?$N$+!<(B!
tokuhirom
2013/9/30 Tokuhiro Matsuno tokuhirom@gmail.com
SALVA https://metacpan.org/author/SALVA $B$O%"%/%F%#%V$@$+$i!"%Q%C%A$$/$l$P$h$5$=$&$@$1$I!"(B@hirose31 $B$5$s$O$J$s$G%Q%C%A$"$F$F$D$+$C$F$k$s$@$m$2!#(B*
tokuhirom
On Mon, Sep 30, 2013 at 10:06 AM, Masahiro Nagano < notifications@github.com> wrote:
https://metacpan.org/release/Socket-Linux $B$3$l$+!":#$N$H$3$m(B constants $B$@$1$C$]$$(B
$B!=(B Reply to this email directly or view it on GitHubhttps://github.com/perl-users-jp/issues/issues/17#issuecomment-25333137 .
コアにいれたいっていう理由なら Socket.pm にいれたらいいとおもったけど、そうじゃないなら Socket::Linux なりにたせばよさそう。 あと、SALVA はアクティブだから、@hirose31 さんがパッチおくってない理由がよくわからない。
@gfx パッチってなんだっけ?w
@gfx の記憶違いで FA でした。
:cry:
:(
accept4 を実装するの自体はすごく簡単だとおもう。どこにつけるかは考える必要あるかもだけど。
Perl5 の pp_sys.c の pp_accept を参考にやればできるとおもう。
Perl本体に実装すると使うためにPerlのアップデートが必要なので、Linux::Socketで十分だと思います。
そもそも、accept4 の利点は fcntl 発行せずにO_CLOEXECかO_NONBLOCK発行できるという点です。 原状の Perl5 の pp_accept は、可能ならば CLOEXEC する実装になっている。
fcntl(fd, F_SETFD, fd > PL_maxsysfd); /\* ensure close-on-exec */
たぶん kazeburo さんがやりたいのは、この fcntl をけずりたいってだと思います。たぶん。
Ruby でも accept4 があれば、O_CLOEXEC を accept4 で発行する仕組みになっている。 http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?revision=33596&view=revision https://github.com/ruby/ruby/blob/trunk/ext/socket/init.c#L504
以上の点から、Perl5 のコアに accept4 をつかうパッチをいれこむのが本筋だとおもいます。 accept4 の利用でパフォーマンスに差がでるんなら、ここかえればすべての server program のパフォーマンスが改善するのでよい(やってみないとわからないけど)。
(そして、kazeburo さんがやりたいのが O_NONBLOCK 指定したいっていうならだいぶ的をはずしているけど)
ちなみに python もおなじような実装になっています。
https://github.com/tokuhirom/Socket-Accept4 まあ XS で実装するならこんなかんじ。
Starletで動いた
diff
--- a/lib/Starlet/Server.pm
+++ b/lib/Starlet/Server.pm
@@ -13,6 +13,7 @@ use Plack::Util;
use Plack::TempBuffer;
use POSIX qw(EINTR EAGAIN EWOULDBLOCK);
use Socket qw(IPPROTO_TCP TCP_NODELAY);
+use Linux::Socket::Accept4;
use Try::Tiny;
use Time::HiRes qw(time);
@@ -103,10 +104,9 @@ sub accept_loop {
local $SIG{PIPE} = 'IGNORE';
while (! defined $max_reqs_per_child || $proc_req_count < $max_reqs_per_child) {
- if (my ($conn,$peer) = $self->{listen_sock}->accept) {
+ my $conn = IO::Socket::INET->new;;
+ if ( my $peer = accept4($conn, $self->{listen_sock}, SOCK_CLOEXEC|SOCK_NONBLOCK) ) {
$self->{_is_deferred_accept} = $self->{_using_defer_accept};
- $conn->blocking(0)
- or die "failed to set socket to nonblocking mode:$!";
$conn->setsockopt(IPPROTO_TCP, TCP_NODELAY, 1)
or die "setsockopt(TCP_NODELAY) failed:$!";
my ($peerport,$peerhost) = unpack_sockaddr_in $peer;
strace
01:54:50.906114 accept4(4, {sa_family=AF_INET, sin_port=htons(59594), sin_addr=inet_addr("127.0.0.1")}, [16], SOCK_CLOEXEC|SOCK_NONBLOCK) = 5
01:54:50.907065 ioctl(5, SNDCTL_TMR_TIMEBASE or TCGETS, 0x7fff13e34e50) = -1 EINVAL (Invalid argument)
01:54:50.907128 lseek(5, 0, SEEK_CUR) = -1 ESPIPE (Illegal seek)
01:54:50.907290 ioctl(5, SNDCTL_TMR_TIMEBASE or TCGETS, 0x7fff13e34e50) = -1 EINVAL (Invalid argument)
01:54:50.907347 lseek(5, 0, SEEK_CUR) = -1 ESPIPE (Illegal seek)
01:54:50.907434 setsockopt(5, SOL_TCP, TCP_NODELAY, [1], 4) = 0
01:54:50.907650 read(5, "GET /5 HTTP/1.1\r\nUser-Agent: curl/7.19.7 (x86_64-pc-linux-gnu) libcurl/7.19.7 OpenSSL/0.9.8k zlib/1.2.3.3 libidn/1.15\r\nHost: 127.0.0.1:5005\r\nAccept: */*\r\n\r\n", 131072) = 156
01:54:50.907819 gettimeofday({1380646490, 907843}, NULL) = 0
01:54:50.907925 write(5, "HTTP/1.1 200 OK\r\nDate: Tue, 01 Oct 2013 16:54:50 GMT\r\nServer: Plack::Handler::Starlet\r\nTransfer-Encoding: chunked\r\nConnection: close\r\n\r\n2\r\nOK\r\n0\r\n\r\n", 148) = 148
01:54:50.908045 close(5) = 0
01:54:50.908179 accept4(4, 0x7fff13e350a0, [4096], SOCK_CLOEXEC|SOCK_NONBLOCK) = ? ERESTARTSYS (To be restarted)
01:55:17.084853 --- SIGINT (Interrupt) @ 0 (0) ---
元はこんな感じ
02:00:17.879032 accept(4, {sa_family=AF_INET, sin_port=htons(59607), sin_addr=inet_addr("127.0.0.1")}, [16]) = 5
02:00:25.453784 ioctl(5, SNDCTL_TMR_TIMEBASE or TCGETS, 0x7fff9e04f8b0) = -1 EINVAL (Invalid argument)
02:00:25.453871 lseek(5, 0, SEEK_CUR) = -1 ESPIPE (Illegal seek)
02:00:25.453950 ioctl(5, SNDCTL_TMR_TIMEBASE or TCGETS, 0x7fff9e04f8b0) = -1 EINVAL (Invalid argument)
02:00:25.454108 lseek(5, 0, SEEK_CUR) = -1 ESPIPE (Illegal seek)
02:00:25.454161 fcntl(5, F_SETFD, FD_CLOEXEC) = 0
02:00:25.454436 fcntl(5, F_GETFL) = 0x2 (flags O_RDWR)
02:00:25.454494 fcntl(5, F_SETFL, O_RDWR|O_NONBLOCK) = 0
あとは、accept後のioctlとlseekが無くなれば... これは PerlIO_fdopen なんですかねぇ
とりあえず、Starletとabを使ったベンチマーク。xs版では速度的なメリットはあまり出ていない感じ サーバはさくらのVPS。cpu core 2個
Starletの起動
plackup -Ilib --max-reqs-per-child=50000 --max-keepalive-reqs=1000 --max-workers 4 -E production -s Starlet -p 5005 -e 'sub{[200,[],["OK"]]}'
abのオプション
ab -c 1 -n 50000 http://127.0.0.1:5005/
Starletオリジナル
Requests per second: 2761.76 [#/sec] (mean)
Starlet accept4 + SOCK_CLOEXEC (nonblockはあとから指定)
Requests per second: 2757.46 [#/sec] (mean)
Starlet accept4 SOCK_CLOEXEC|SOCK_NONBLOCK
Requests per second: 2807.26 [#/sec] (mean)
ちなみに PL_ppaddr さしかえるのもかいてみた。 https://github.com/tokuhirom/Devel-Accept4
Starletでのベンチマークでは、PL_ppaddr 差し替えバージョンでも差はでていない様子 SOCK_NONBLOCK までやると差が出てくるので、Linux::Socket::Accept4 がCPANに上がると嬉しいです。 Monoceros で使います
お、なるほど。 テストケースないので、テストケース書いていただけると助かります!
テストは以下のようなものでいいでしょうか ?
不可解なのは clientから 1回しか書き込みを行なっていないのに, accept4が 3度実行されることです.
accept4の部分を accept($conn, $server)
とすると acceptは 1度しか呼ばれない
ようですが, accept4はそういうものなのでしょうか ? それと accept4から抜けて, socketから
何も読み出せなかった際, sleepをしないと 3度目の accept4がブロックされてテストが終了しない
というケースがみられました. 以下の環境で上記のようなことが起こることを確認しました.
問題等ございましたらご指摘ください.
use strict;
use warnings;
use Test::More;
use IO::Socket::INET;
use Linux::Socket::Accept4;
use Fcntl qw(F_GETFL F_GETFD O_NONBLOCK FD_CLOEXEC);
use Test::TCP;
test_tcp(
server => sub {
my $port = shift;
my $server = IO::Socket::INET->new(
LocalPort => $port,
LocalAddr => "127.0.0.1",
Proto => "tcp",
Listen => 5,
Type => SOCK_STREAM,
) or die $!;
my $conn = IO::Socket::INET->new;
while (my $peer = accept4($conn, $server, SOCK_CLOEXEC|SOCK_NONBLOCK)) {
if (defined(my $data = <$conn>)) {
my $status_flag = fcntl($conn, F_GETFL, 0);
print {$conn} ($status_flag & O_NONBLOCK), "\n";
my $fd_flag = fcntl($conn, F_GETFD, 0);
print {$conn} ($fd_flag & FD_CLOEXEC), "\n";
} else {
sleep 1;
}
}
},
client => sub {
my $port = shift;
my $sock = IO::Socket::INET->new(
PeerPort => $port,
PeerAddr => '127.0.0.1',
Proto => 'tcp'
) or die "Cannot open client socket: $!";
print {$sock} "dummy\n";
my $status_flag_test = <$sock>;
chomp $status_flag_test;
ok $status_flag_test, "set NON_BLOCK flag";
my $fd_flag_test = <$sock>;
chomp $fd_flag_test;
ok $fd_flag_test, "set close-on-exec flag";
},
);
done_testing;
同じの書いてた。 https://github.com/kazeburo/Linux-Socket-Accept4/commit/f14b2a8b40f5f8829204d9632ec474a546520cc7 nonblockなので、select使った方が良いですね
なるほど(manで確認したところ connectionが queuing されたら acceptから抜けると ありました. readできる状態の connectionが存在して acceptを抜けるものと思っていました.)
ちなみに、gfx 案の Linux::Socket でやらないのは、ソケットに関する非常に広範な知識とかないとメンテが不可能だからです!
ネームスペースの話ね。
もろもろ documentation などすませたので、kazeburo さんに最終確認をいただいた後、Linux::Socket::Accept4 を CPAN にアップしようとおもいます。
(XS のコード多少いじってるので、パフォーマンスに変化があるかもしれないです)
accept4
は Linux 2.6.28からのサポートですが, それのチェックは必要になるのでしょうか ?
今時使っている人がいるかよくわかりませんが, CentOS5(2.6.18を使用)等では利用できないです.
チェック入れたいけど、どうやっていれたものかな、とおもっておりました!
こんな感じでいいのかしら?
cat <<EOF | gcc -E -
#include <sys/syscall.h>
#if defined __NR_accept4
aru
#else
nai
#endif
EOF
epollや inotifyといった Linux固有のインタフェースを提供するモジュールを調べてみましたが, チェックしていても Linuxかどうかのチェックだけで, サポートしているカーネルのバージョンまでは チェックしていないですね. ビルドでコケればわかるだろうというスタンスでしょうか.
理想的には BOOT: のタイミングでランタイムにチェックできるといいんだけどなー。
なお、現状の実装だと、バイナリを古いカーネルの環境にコピーしたら、ENANTOKA になってしまう、っていう問題はあります。
@kazeburo IO::Socket::INET がきたときにちがう状態になるのなおしました。
その問題を適切に対処するというのは難しいと思います.
linux/version.h
に KERNEL_VERSION
というマクロがあり, それでバージョンチェックを
行なっているというモジュールはありました.
https://metacpan.org/source/VPIT/Linux-SysInfo-0.14/SysInfo.xs#L18
LINUX_VERSION_CODE
というのが利用しているカーネルのバージョンを示すようです
(その定義を変えられると通ってしまうとか考えるとキリがないですが).
↑↑のは、コンパイルタイムの処理だから、とくになにかの解決にはなってないように思います。
IO::Socket::INET大丈夫そう @tokuhirom++
まあそうなんですが, リンクできないといったエラーメッセージが出るのでなく,
#error
でそれらしいエラーメッセージが出せるというだけです.
なるほど。コンパイル時のは、configure 時にチェックしようかとおもってて、 https://github.com/tokuhirom/Linux-Socket-Accept4/blob/master/builder/MyBuilder.pm こんなかんじで実装してみました。
CentOS5 とかで、ちゃんと exit するのを確認しないといけませんが。。
centos5でのテスト
$ perl ./Build.PL
/tmp/75K_QmDbPu.c:4:2: error: #error "Your environment does not supports accept4."
This module only supports linux 2.6.28+ and glibc 2.10+.
$ echo $?
0
./Build.PLはexit(1)したほうが良い?
Build.PL $B$O(B N/A $B$N$H$-$O(B 0 $B$K$9$Y$-!"$H(B @charsbar $B$5$s$,$$$C$F$$$?$h$&$J!#!#(B
tokuhirom
On Fri, Oct 4, 2013 at 2:28 PM, Masahiro Nagano notifications@github.comwrote:
centos5$B$G$N%F%9%H(B
$ perl ./Build.PL /tmp/75K_QmDbPu.c:4:2: error: #error "Your environment does not supports accept4." This module only supports linux 2.6.28+ and glibc 2.10+. $ echo $? 0
./Build.PL$B$O(Bexit(1)$B$7$?$[$&$,NI$$!)(B
$B!=(B Reply to this email directly or view it on GitHubhttps://github.com/perl-users-jp/issues/issues/17#issuecomment-25677229 .
Build.PL で N/A なときは、exit 0 にしないといけなかったような気がしてますが。
なるほど
Devel::CheckCompiler
のバージョンが指定されていなかったので, pull requestしました.
Thanks! Shipped Linux-Socket-Accept4 0.01!!
出したけど、PAUSE不調。。
Ubuntu 12.04 32bitだと Configure時に accept4
がないと判定されてしまって
います(実際には存在し, 無理やりその部分を通せばテストも全部通る).
sys/syscall/h
-> asm/unistd.h
-> asm/unistd_32.h
と includeされていきますが, asm/unistd_32.h
には __NR_accept4
が定義されていません.
(64bit環境であれば asm/unistd_64.h
が includeされるが, そこでは定義されている).
asm-generic/unistd.h
を includeするのであれば, 32bit環境でも __NR_accept4
が
定義されましたが, ユーザランドのプログラムが includeするのが適切なファイルでは
ないようにも思えます.
check_compiler
に渡すコードを, マクロの有無のチェックでなく, configure
が
やっているように, 以下のようなコードを渡した方が良いのかもしれません.
#define _GNU_SOURCE
#include <sys/socket.h>
int main(void)
{
return accept4(0, (void*)0, (void*)0, 0);
}
Oh. p-r 希望。
linuxだけだけど、accept4が欲しい