perl-users-jp / issues

有志で既知のバグや要望を検討・管理し、オフィシャルへの還元をしていきます。
20 stars 0 forks source link

accept4 #17

Closed kazeburo closed 11 years ago

kazeburo commented 11 years ago

linuxだけだけど、accept4が欲しい

tokuhirom commented 11 years ago

Socket.pm にたせばいいのかしら?

gfx commented 11 years ago

Linux特有のsocket機能はわりとたくさんあるので、Linux::Socketのメンテナンスを誰かが引き継ぐかSocket::Linuxを新設するのがいいとおもわれる。Linux::Socketはいま @hirose31 さんがパッチ当てて使ってるはず。

kazeburo commented 11 years ago

https://metacpan.org/release/Socket-Linux これか、今のところ constants だけっぽい

tokuhirom commented 11 years ago

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 .

tokuhirom commented 11 years ago

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 .

tokuhirom commented 11 years ago

コアにいれたいっていう理由なら Socket.pm にいれたらいいとおもったけど、そうじゃないなら Socket::Linux なりにたせばよさそう。 あと、SALVA はアクティブだから、@hirose31 さんがパッチおくってない理由がよくわからない。

hirose31 commented 11 years ago

@gfx パッチってなんだっけ?w

tokuhirom commented 11 years ago

@gfx の記憶違いで FA でした。

hirose31 commented 11 years ago

:cry:

Songmu commented 11 years ago

:(

tokuhirom commented 11 years ago

accept4 を実装するの自体はすごく簡単だとおもう。どこにつけるかは考える必要あるかもだけど。

tokuhirom commented 11 years ago

Perl5 の pp_sys.c の pp_accept を参考にやればできるとおもう。

gfx commented 11 years ago

Perl本体に実装すると使うためにPerlのアップデートが必要なので、Linux::Socketで十分だと思います。

tokuhirom commented 11 years ago

そもそも、accept4 の利点は fcntl 発行せずにO_CLOEXECかO_NONBLOCK発行できるという点です。 原状の Perl5 の pp_accept は、可能ならば CLOEXEC する実装になっている。

if defined(HAS_FCNTL) && defined(F_SETFD)

  fcntl(fd, F_SETFD, fd > PL_maxsysfd);       /\* ensure close-on-exec */

endif

たぶん 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 指定したいっていうならだいぶ的をはずしているけど)

参考: reintroducing accept4

tokuhirom commented 11 years ago

ちなみに python もおなじような実装になっています。

tokuhirom commented 11 years ago

https://github.com/tokuhirom/Socket-Accept4 まあ XS で実装するならこんなかんじ。

tokuhirom commented 11 years ago

名前かえた。 https://github.com/tokuhirom/Linux-Socket-Accept4

kazeburo commented 11 years ago

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 なんですかねぇ

kazeburo commented 11 years ago

とりあえず、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)
tokuhirom commented 11 years ago

ちなみに PL_ppaddr さしかえるのもかいてみた。 https://github.com/tokuhirom/Devel-Accept4

kazeburo commented 11 years ago

Starletでのベンチマークでは、PL_ppaddr 差し替えバージョンでも差はでていない様子 SOCK_NONBLOCK までやると差が出てくるので、Linux::Socket::Accept4 がCPANに上がると嬉しいです。 Monoceros で使います

tokuhirom commented 11 years ago

お、なるほど。 テストケースないので、テストケース書いていただけると助かります!

syohex commented 11 years ago

テストは以下のようなものでいいでしょうか ?

不可解なのは 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;
kazeburo commented 11 years ago

同じの書いてた。 https://github.com/kazeburo/Linux-Socket-Accept4/commit/f14b2a8b40f5f8829204d9632ec474a546520cc7 nonblockなので、select使った方が良いですね

syohex commented 11 years ago

なるほど(manで確認したところ connectionが queuing されたら acceptから抜けると ありました. readできる状態の connectionが存在して acceptを抜けるものと思っていました.)

tokuhirom commented 11 years ago

ちなみに、gfx 案の Linux::Socket でやらないのは、ソケットに関する非常に広範な知識とかないとメンテが不可能だからです!

tokuhirom commented 11 years ago

ネームスペースの話ね。

tokuhirom commented 11 years ago

もろもろ documentation などすませたので、kazeburo さんに最終確認をいただいた後、Linux::Socket::Accept4 を CPAN にアップしようとおもいます。

tokuhirom commented 11 years ago

(XS のコード多少いじってるので、パフォーマンスに変化があるかもしれないです)

syohex commented 11 years ago

accept4は Linux 2.6.28からのサポートですが, それのチェックは必要になるのでしょうか ? 今時使っている人がいるかよくわかりませんが, CentOS5(2.6.18を使用)等では利用できないです.

tokuhirom commented 11 years ago

チェック入れたいけど、どうやっていれたものかな、とおもっておりました!

hirose31 commented 11 years ago

こんな感じでいいのかしら?

cat <<EOF | gcc -E -
#include <sys/syscall.h>

#if defined __NR_accept4
aru
#else
nai
#endif

EOF
syohex commented 11 years ago

epollや inotifyといった Linux固有のインタフェースを提供するモジュールを調べてみましたが, チェックしていても Linuxかどうかのチェックだけで, サポートしているカーネルのバージョンまでは チェックしていないですね. ビルドでコケればわかるだろうというスタンスでしょうか.

tokuhirom commented 11 years ago

理想的には BOOT: のタイミングでランタイムにチェックできるといいんだけどなー。

tokuhirom commented 11 years ago

なお、現状の実装だと、バイナリを古いカーネルの環境にコピーしたら、ENANTOKA になってしまう、っていう問題はあります。

tokuhirom commented 11 years ago

@kazeburo IO::Socket::INET がきたときにちがう状態になるのなおしました。

syohex commented 11 years ago

その問題を適切に対処するというのは難しいと思います.

linux/version.hKERNEL_VERSIONというマクロがあり, それでバージョンチェックを 行なっているというモジュールはありました.

https://metacpan.org/source/VPIT/Linux-SysInfo-0.14/SysInfo.xs#L18

LINUX_VERSION_CODEというのが利用しているカーネルのバージョンを示すようです (その定義を変えられると通ってしまうとか考えるとキリがないですが).

tokuhirom commented 11 years ago

↑↑のは、コンパイルタイムの処理だから、とくになにかの解決にはなってないように思います。

kazeburo commented 11 years ago

IO::Socket::INET大丈夫そう @tokuhirom++

syohex commented 11 years ago

まあそうなんですが, リンクできないといったエラーメッセージが出るのでなく, #errorでそれらしいエラーメッセージが出せるというだけです.

tokuhirom commented 11 years ago

なるほど。コンパイル時のは、configure 時にチェックしようかとおもってて、 https://github.com/tokuhirom/Linux-Socket-Accept4/blob/master/builder/MyBuilder.pm こんなかんじで実装してみました。

CentOS5 とかで、ちゃんと exit するのを確認しないといけませんが。。

kazeburo commented 11 years ago

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)したほうが良い?

tokuhirom commented 11 years ago

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 .

tokuhirom commented 11 years ago

Build.PL で N/A なときは、exit 0 にしないといけなかったような気がしてますが。

kazeburo commented 11 years ago

なるほど

syohex commented 11 years ago

Devel::CheckCompilerのバージョンが指定されていなかったので, pull requestしました.

https://github.com/tokuhirom/Linux-Socket-Accept4/pull/2

tokuhirom commented 11 years ago

Thanks! Shipped Linux-Socket-Accept4 0.01!!

tokuhirom commented 11 years ago

出したけど、PAUSE不調。。

syohex commented 11 years ago

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);
}
tokuhirom commented 11 years ago

Oh. p-r 希望。