[#38121] regex performace tuning and ABI compatibility — Yukihiro Matsumoto <matz@...>

まつもと ゆきひろです

13 messages 2009/03/03

[#38191] big time — Tanaka Akira <akr@...>

思い立って、time_t を越える範囲を Time で扱うことに挑戦して

31 messages 2009/03/27
[#38194] Re: big time — Tadayoshi Funaba <tadf@...> 2009/03/28

> 思い立って、time_t を越える範囲を Time で扱うことに挑戦して

[#38196] Re: big time — Tanaka Akira <akr@...> 2009/03/28

In article <20090328.134401.209982445.tadf@dotrb.org>,

[#38202] Re: big time — Urabe Shyouhei <shyouhei@...> 2009/03/29

卜部です。

[#38205] Re: big time — Tanaka Akira <akr@...> 2009/03/29

In article <49CF6641.7010204@ruby-lang.org>,

[#38218] rinda/eval.rb — Masatoshi SEKI <m_seki@...>

咳といいます。

20 messages 2009/03/30
[#38219] Re: rinda/eval.rb — Tanaka Akira <akr@...> 2009/03/31

In article <F01982B3-FBB5-497F-BA36-38FA250E7D69@mva.biglobe.ne.jp>,

[#38223] Re: rinda/eval.rb — Masatoshi SEKI <m_seki@...> 2009/03/31

咳といいます。

[#38229] Re: rinda/eval.rb — "U.Nakamura" <usa@...> 2009/04/01

こんにちは、なかむら(う)です。

[#38233] Re: rinda/eval.rb — Tanaka Akira <akr@...> 2009/04/01

In article <20090401095853.B00A.C613B076@garbagecollect.jp>,

[#38222] *BSD で fork できない理由 — "KISHIMOTO, Makoto" <ksmakoto@...4u.or.jp>

きしもとです

12 messages 2009/03/31

[ruby-dev:38142] Re: IO::WantRead and IO::WantWrite module for nonblocking exceptions

From: Martin Duerst <duerst@...>
Date: 2009-03-06 01:54:56 UTC
List: ruby-dev #38142
「WantRead」と「WantWrite」は英語的にちょっと微妙な面があるので、
名前については一回 ruby-core で議論した方がいいかもしれません。

よろしくお願いします。    Martin.

At 18:48 09/03/05, you wrote:
>IO::WantRead と IO::WantWrite というモジュールを作って、
>EAGAIN や EWOULDBLOCK などが発生したときに、例外オブジェクト
>に extend しておくのはどうでしょうか。
>
>理由はふたつあります。
>
>* rescue Errno::EAGAIN, Errno::EWOULDBLOCK と並べるのは面倒
>
>  nonblocking I/O でのエラーを rescue するときは、こうやって
>  ふたつ並べるのが行儀が良いとされていますが、ruby 側で、
>  read_nonblock で起きた EAGAIN と EWOULDBLOCK で
>  IO::WantRead を extend しておけば、
>    rescue IO::WantRead
>  というひとつ書けば済むようになります。
>
>  また、read_nonblock と write_nonblock では EAGAIN と
>  EWOULDBLOCK ですが、connect_nonblock では EINPROGRESS,
>  accept_nonblock では ... などという細かいことを覚えなくて
>  済むようになります。IO::WantRead か IO::WantWrite を
>  rescue して、それに対応して IO.select の read か write で
>  待てばいいわけです。
>
>* openssl の read_nonblock の場合、read と write のどちらを
>  待つべきかわからない
>
>  1.9.2 では openssl に read_nonblock があり、実装としては
>  openssl の nonblocking I/O を使うわけですが、これは内部で
>  read(2) だけでなく write(2) を行う可能性があるようです。そ
>  して、write(2) がブロックしそうなときというのもありえるわ
>  けで、そのときにはブロックせずにエラーになるわけですが、そ
>  のときにアプリケーションは writable になるのを待ってやりな
>  おさなければなりません。
>
>  http://www.openssl.org/support/faq.html#PROG10
>
>  現在は read(2) でブロックしそうなときも write(2) でブロッ
>  クしそうなときも Errno::EWOULDBLOCK 例外が発生するので、
>  readable になるのを待つか writable になるのを待つかを例外
>  のクラスで区別することはできません。
>
>  read_nonblock で EWOULDBLOCK になったからといって readable
>  になるのを待つと、処理が進まなくなる可能性があります。
>
>  ここでどちらを待つべきかに応じて IO::WantRead と
>  IO::WantWrite を例外に extend しておけば、区別することが出
>  来ます。
>
>  なお、openssl のエラーは内部的には SSL_ERROR_WANT_READ と
>  SSL_ERROR_WANT_WRITE で、WantRead, WantWrite という名前は
>  ここに由来しています。
>
>というわけでどうでしょうか。
>
>% svn diff --diff-cmd diff -x '-u -p'
>Index: include/ruby/ruby.h
>===================================================================
>--- include/ruby/ruby.h        (revision 22781)
>+++ include/ruby/ruby.h        (working copy)
>@@ -979,6 +979,7 @@ PRINTF_ARGS(NORETURN(void rb_raise(VALUE
> PRINTF_ARGS(NORETURN(void rb_fatal(const char*, ...)), 1, 2);
> PRINTF_ARGS(NORETURN(void rb_bug(const char*, ...)), 1, 2);
> NORETURN(void rb_sys_fail(const char*));
>+NORETURN(void rb_sys_fail_with_mod(VALUE, const char*));
> NORETURN(void rb_iter_break(void));
> NORETURN(void rb_exit(int));
> NORETURN(void rb_notimplement(void));
>@@ -1033,6 +1034,8 @@ RUBY_EXTERN VALUE rb_mFileTest;
> RUBY_EXTERN VALUE rb_mGC;
> RUBY_EXTERN VALUE rb_mMath;
> RUBY_EXTERN VALUE rb_mProcess;
>+RUBY_EXTERN VALUE rb_mWantRead;
>+RUBY_EXTERN VALUE rb_mWantWrite;
> 
> RUBY_EXTERN VALUE rb_cBasicObject;
> RUBY_EXTERN VALUE rb_cObject;
>Index: io.c
>===================================================================
>--- io.c       (revision 22781)
>+++ io.c       (working copy)
>@@ -110,6 +110,8 @@ extern void Init_File(void);
> VALUE rb_cIO;
> VALUE rb_eEOFError;
> VALUE rb_eIOError;
>+VALUE rb_mWantRead;
>+VALUE rb_mWantWrite;
> 
> VALUE rb_stdin, rb_stdout, rb_stderr;
> VALUE rb_deferr;              /* rescue VIM plugin */
>@@ -1755,7 +1757,7 @@ io_getpartial(int argc, VALUE *argv, VAL
>             if (!nonblock && rb_io_wait_readable(fptr->fd))
>                 goto again;
>             if (nonblock && errno == EWOULDBLOCK)
>-                rb_sys_fail("WANT_READ");
>+                rb_sys_fail_with_mod(rb_mWantRead, "WANT_READ");
>             rb_sys_fail_path(fptr->pathv);
>         }
>       else if (n == 0) {
>@@ -1956,7 +1958,7 @@ rb_io_write_nonblock(VALUE io, VALUE str
> 
>     if (n == -1) {
>         if (errno == EWOULDBLOCK)
>-            rb_sys_fail("WANT_WRITE");
>+            rb_sys_fail_with_mod(rb_mWantWrite, "WANT_WRITE");
>         rb_sys_fail_path(fptr->pathv);
>     }
> 
>@@ -8635,6 +8637,9 @@ Init_IO(void)
>     rb_cIO = rb_define_class("IO", rb_cObject);
>     rb_include_module(rb_cIO, rb_mEnumerable);
> 
>+    rb_mWantRead = rb_define_module_under(rb_cIO, "WantRead");
>+    rb_mWantWrite = rb_define_module_under(rb_cIO, "WantWrite");
>+
> #if 0
>     /* This is necessary only for forcing rdoc handle File::open */
>     rb_define_singleton_method(rb_cFile, "open",  rb_io_s_open, -1);
>Index: ext/openssl/ossl_ssl.c
>===================================================================
>--- ext/openssl/ossl_ssl.c     (revision 22781)
>+++ ext/openssl/ossl_ssl.c     (working copy)
>@@ -1115,14 +1115,14 @@ ossl_ssl_read_internal(int argc, VALUE *
>           case SSL_ERROR_WANT_WRITE:
>                 if (nonblock) {
>                     errno = EWOULDBLOCK;
>-                    rb_sys_fail("SSL_ERROR_WANT_WRITE");
>+                    rb_sys_fail_with_mod(rb_mWantWrite, "SSL_ERROR_WANT_WRITE");
>                 }
>                 rb_io_wait_writable(FPTR_TO_FD(fptr));
>                 continue;
>           case SSL_ERROR_WANT_READ:
>                 if (nonblock) {
>                     errno = EWOULDBLOCK;
>-                    rb_sys_fail("SSL_ERROR_WANT_READ");
>+                    rb_sys_fail_with_mod(rb_mWantRead, "SSL_ERROR_WANT_READ");
>                 }
>                 rb_io_wait_readable(FPTR_TO_FD(fptr));
>               continue;
>Index: ext/socket/init.c
>===================================================================
>--- ext/socket/init.c  (revision 22781)
>+++ ext/socket/init.c  (working copy)
>@@ -200,7 +200,7 @@ rsock_s_recvfrom_nonblock(VALUE sock, in
> #if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN
>         case EWOULDBLOCK:
> #endif
>-            rb_sys_fail("recvfrom(2) WANT_READ");
>+            rb_sys_fail_with_mod(rb_mWantRead, "recvfrom(2) WANT_READ");
>       }
>       rb_sys_fail("recvfrom(2)");
>     }
>@@ -472,7 +472,7 @@ rsock_s_accept_nonblock(VALUE klass, rb_
> #if defined EPROTO
>         case EPROTO:
> #endif
>-            rb_sys_fail("accept(2) WANT_READ");
>+            rb_sys_fail_with_mod(rb_mWantRead, "accept(2) WANT_READ");
>       }
>         rb_sys_fail("accept(2)");
>     }
>Index: ext/socket/socket.c
>===================================================================
>--- ext/socket/socket.c        (revision 22781)
>+++ ext/socket/socket.c        (working copy)
>@@ -312,7 +312,7 @@ sock_connect_nonblock(VALUE sock, VALUE 
>     n = connect(fptr->fd, (struct sockaddr*)RSTRING_PTR(addr), 
>RSTRING_LEN(addr));
>     if (n < 0) {
>         if (errno == EINPROGRESS)
>-            rb_sys_fail("connect(2) WANT_WRITE");
>+            rb_sys_fail_with_mod(rb_mWantWrite, "connect(2) WANT_WRITE");
>       rb_sys_fail("connect(2)");
>     }
> 
>Index: ext/socket/ancdata.c
>===================================================================
>--- ext/socket/ancdata.c       (revision 22781)
>+++ ext/socket/ancdata.c       (working copy)
>@@ -1280,7 +1280,7 @@ bsock_sendmsg_internal(int argc, VALUE *
> 
>     if (ss == -1) {
>         if (nonblock && errno == EWOULDBLOCK)
>-            rb_sys_fail("sendmsg(2) WANT_WRITE");
>+            rb_sys_fail_with_mod(rb_mWantWrite, "sendmsg(2) WANT_WRITE");
>       rb_sys_fail("sendmsg(2)");
>     }
> 
>@@ -1565,7 +1565,7 @@ bsock_recvmsg_internal(int argc, VALUE *
> 
>     if (ss == -1) {
>         if (nonblock && errno == EWOULDBLOCK)
>-            rb_sys_fail("recvmsg(2) WANT_READ");
>+            rb_sys_fail_with_mod(rb_mWantRead, "recvmsg(2) WANT_READ");
> #if defined(HAVE_ST_MSG_CONTROL)
>         if (!gc_done && (errno == EMFILE || errno == EMSGSIZE)) {
>           /*
>Index: error.c
>===================================================================
>--- error.c    (revision 22781)
>+++ error.c    (working copy)
>@@ -1129,8 +1129,8 @@ rb_fatal(const char *fmt, ...)
>     rb_exc_fatal(rb_exc_new3(rb_eFatal, mesg));
> }
> 
>-void
>-rb_sys_fail(const char *mesg)
>+static VALUE
>+make_errno_exc(const char *mesg)
> {
>     int n = errno;
>     VALUE arg;
>@@ -1141,7 +1141,21 @@ rb_sys_fail(const char *mesg)
>     }
> 
>     arg = mesg ? rb_str_new2(mesg) : Qnil;
>-    rb_exc_raise(rb_class_new_instance(1, &arg, get_syserr(n)));
>+    return rb_class_new_instance(1, &arg, get_syserr(n));
>+}
>+
>+void
>+rb_sys_fail(const char *mesg)
>+{
>+    rb_exc_raise(make_errno_exc(mesg));
>+}
>+
>+void
>+rb_sys_fail_with_mod(VALUE mod, const char *mesg)
>+{
>+    VALUE exc = make_errno_exc(mesg);
>+    rb_extend_object(exc, mod);
>+    rb_exc_raise(exc);
> }
> 
> void
>Index: test/openssl/test_pair.rb
>===================================================================
>--- test/openssl/test_pair.rb  (revision 22781)
>+++ test/openssl/test_pair.rb  (working copy)
>@@ -147,7 +147,7 @@ class OpenSSL::TestPair < Test::Unit::Te
>   def test_read_nonblock
>     ssl_pair {|s1, s2|
>       err = nil
>-      assert_raise(Errno::EWOULDBLOCK) {
>+      assert_raise(IO::WantRead) {
>         begin
>           s2.read_nonblock(10)
>         ensure
>Index: test/openssl/test_ssl.rb
>===================================================================
>--- test/openssl/test_ssl.rb   (revision 22781)
>+++ test/openssl/test_ssl.rb   (working copy)
>@@ -172,12 +172,12 @@ class OpenSSL::TestSSL < Test::Unit::Tes
>       ssl = OpenSSL::SSL::SSLSocket.new(sock)
>       ssl.sync_close = true
>       ssl.connect
>-      assert_raise(Errno::EAGAIN, Errno::EWOULDBLOCK) { ssl.read_nonblock(100) }
>+      assert_raise(IO::WantRead) { ssl.read_nonblock(100) }
>       ssl.write("abc\n")
>       IO.select [ssl]
>       assert_equal('a', ssl.read_nonblock(1))
>       assert_equal("bc\n", ssl.read_nonblock(100))
>-      assert_raise(Errno::EAGAIN, Errno::EWOULDBLOCK) { ssl.read_nonblock(100) }
>+      assert_raise(IO::WantRead) { ssl.read_nonblock(100) }
>     }
>   end
> 
>Index: test/socket/test_nonblock.rb
>===================================================================
>--- test/socket/test_nonblock.rb       (revision 22781)
>+++ test/socket/test_nonblock.rb       (working copy)
>@@ -12,13 +12,13 @@ class TestSocketNonblock < Test::Unit::T
>     serv = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
>     serv.bind(Socket.sockaddr_in(0, "127.0.0.1"))
>     serv.listen(5)
>-    assert_raise(Errno::EAGAIN, Errno::EWOULDBLOCK) { serv.accept_nonblock }
>+    assert_raise(IO::WantRead) { serv.accept_nonblock }
>     c = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
>     c.connect(serv.getsockname)
>     begin
>       s, sockaddr = serv.accept_nonblock
>-    rescue Errno::EWOULDBLOCK
>-      IO.select nil, [serv]
>+    rescue IO::WantRead
>+      IO.select [serv]
>       s, sockaddr = serv.accept_nonblock
>     end
>     assert_equal(Socket.unpack_sockaddr_in(c.getsockname), 
>Socket.unpack_sockaddr_in(sockaddr))
>@@ -57,8 +57,8 @@ class TestSocketNonblock < Test::Unit::T
>     u1 = UDPSocket.new
>     u2 = UDPSocket.new
>     u1.bind("127.0.0.1", 0)
>-    assert_raise(Errno::EAGAIN, Errno::EWOULDBLOCK) { 
>u1.recvfrom_nonblock(100) }
>-    assert_raise(Errno::EAGAIN, Errno::EWOULDBLOCK, Errno::EINVAL) { 
>u2.recvfrom_nonblock(100) }
>+    assert_raise(IO::WantRead) { u1.recvfrom_nonblock(100) }
>+    assert_raise(IO::WantRead, Errno::EINVAL) { u2.recvfrom_nonblock(100) }
>     u2.send("aaa", 0, u1.getsockname)
>     IO.select [u1]
>     mesg, inet_addr = u1.recvfrom_nonblock(100)
>@@ -67,7 +67,7 @@ class TestSocketNonblock < Test::Unit::T
>     af, port, host, addr = inet_addr
>     u2_port, u2_addr = Socket.unpack_sockaddr_in(u2.getsockname)
>     assert_equal(u2_port, port)
>-    assert_raise(Errno::EAGAIN, Errno::EWOULDBLOCK) { 
>u1.recvfrom_nonblock(100) }
>+    assert_raise(IO::WantRead) { u1.recvfrom_nonblock(100) }
>     u2.send("", 0, u1.getsockname)
>     assert_nothing_raised("cygwin 1.5.19 has a problem to send an empty 
>UDP packet. [ruby-dev:28915]") {
>       timeout(1) { IO.select [u1] }
>@@ -83,13 +83,13 @@ class TestSocketNonblock < Test::Unit::T
>     u1 = UDPSocket.new
>     u2 = UDPSocket.new
>     u1.bind("127.0.0.1", 0)
>-    assert_raise(Errno::EAGAIN, Errno::EWOULDBLOCK) { u1.recv_nonblock(100) }
>-    assert_raise(Errno::EAGAIN, Errno::EWOULDBLOCK, Errno::EINVAL) { 
>u2.recv_nonblock(100) }
>+    assert_raise(IO::WantRead) { u1.recv_nonblock(100) }
>+    assert_raise(IO::WantRead, Errno::EINVAL) { u2.recv_nonblock(100) }
>     u2.send("aaa", 0, u1.getsockname)
>     IO.select [u1]
>     mesg = u1.recv_nonblock(100)
>     assert_equal("aaa", mesg)
>-    assert_raise(Errno::EAGAIN, Errno::EWOULDBLOCK) { u1.recv_nonblock(100) }
>+    assert_raise(IO::WantRead) { u1.recv_nonblock(100) }
>     u2.send("", 0, u1.getsockname)
>     assert_nothing_raised("cygwin 1.5.19 has a problem to send an empty 
>UDP packet. [ruby-dev:28915]") {
>       timeout(1) { IO.select [u1] }
>@@ -105,8 +105,8 @@ class TestSocketNonblock < Test::Unit::T
>     s1 = Socket.new(Socket::AF_INET, Socket::SOCK_DGRAM, 0)
>     s1.bind(Socket.sockaddr_in(0, "127.0.0.1"))
>     s2 = Socket.new(Socket::AF_INET, Socket::SOCK_DGRAM, 0)
>-    assert_raise(Errno::EAGAIN, Errno::EWOULDBLOCK) { 
>s1.recvfrom_nonblock(100) }
>-    assert_raise(Errno::EAGAIN, Errno::EWOULDBLOCK, Errno::EINVAL) { 
>s2.recvfrom_nonblock(100) }
>+    assert_raise(IO::WantRead) { s1.recvfrom_nonblock(100) }
>+    assert_raise(IO::WantRead, Errno::EINVAL) { s2.recvfrom_nonblock(100) }
>     s2.send("aaa", 0, s1.getsockname)
>     IO.select [s1]
>     mesg, sockaddr = s1.recvfrom_nonblock(100) 
>@@ -140,13 +140,13 @@ class TestSocketNonblock < Test::Unit::T
> 
>   def test_tcp_recv_nonblock
>     c, s = tcp_pair
>-    assert_raise(Errno::EAGAIN, Errno::EWOULDBLOCK) { c.recv_nonblock(100) }
>-    assert_raise(Errno::EAGAIN, Errno::EWOULDBLOCK) { s.recv_nonblock(100) }
>+    assert_raise(IO::WantRead) { c.recv_nonblock(100) }
>+    assert_raise(IO::WantRead) { s.recv_nonblock(100) }
>     c.write("abc")
>     IO.select [s]
>     assert_equal("a", s.recv_nonblock(1))
>     assert_equal("bc", s.recv_nonblock(100))
>-    assert_raise(Errno::EAGAIN, Errno::EWOULDBLOCK) { s.recv_nonblock(100) }
>+    assert_raise(IO::WantRead) { s.recv_nonblock(100) }
>   ensure
>     c.close if c
>     s.close if s
>@@ -154,13 +154,13 @@ class TestSocketNonblock < Test::Unit::T
> 
>   def test_read_nonblock
>     c, s = tcp_pair
>-    assert_raise(Errno::EAGAIN, Errno::EWOULDBLOCK) { c.read_nonblock(100) }
>-    assert_raise(Errno::EAGAIN, Errno::EWOULDBLOCK) { s.read_nonblock(100) }
>+    assert_raise(IO::WantRead) { c.read_nonblock(100) }
>+    assert_raise(IO::WantRead) { s.read_nonblock(100) }
>     c.write("abc")
>     IO.select [s]
>     assert_equal("a", s.read_nonblock(1))
>     assert_equal("bc", s.read_nonblock(100))
>-    assert_raise(Errno::EAGAIN, Errno::EWOULDBLOCK) { s.read_nonblock(100) }
>+    assert_raise(IO::WantRead) { s.read_nonblock(100) }
>   ensure
>     c.close if c
>     s.close if s
>@@ -175,7 +175,7 @@ class TestSocketNonblock < Test::Unit::T
>     ret = c.write_nonblock(str)
>     assert_operator(ret, :>, 0)
>     loop {
>-      assert_raise(Errno::EAGAIN, Errno::EWOULDBLOCK) {
>+      assert_raise(IO::WantWrite) {
>         loop {
>           ret = c.write_nonblock(str)
>           assert_operator(ret, :>, 0)
>@@ -196,7 +196,7 @@ class TestSocketNonblock < Test::Unit::T
>         loop {
>           c.sendmsg_nonblock("a" * 100000)
>         }
>-      rescue Errno::EWOULDBLOCK
>+      rescue IO::WantWrite
>         assert_match(/WANT_WRITE/, $!.message)
>       end
>     }
>@@ -206,7 +206,7 @@ class TestSocketNonblock < Test::Unit::T
>     tcp_pair {|c, s|
>       begin
>         c.recvmsg_nonblock(4096)
>-      rescue Errno::EWOULDBLOCK
>+      rescue IO::WantRead
>         assert_match(/WANT_READ/, $!.message)
>       end
>     }
>@@ -216,7 +216,7 @@ class TestSocketNonblock < Test::Unit::T
>     tcp_pair {|c, s|
>       begin
>         c.recv_nonblock(4096)
>-      rescue Errno::EWOULDBLOCK
>+      rescue IO::WantRead
>         assert_match(/WANT_READ/, $!.message)
>       end
>     }
>@@ -243,7 +243,7 @@ class TestSocketNonblock < Test::Unit::T
>     port = serv.local_address.ip_port
>     begin
>       s, _ = serv.accept_nonblock
>-    rescue Errno::EWOULDBLOCK, Errno::ECONNABORTED, Errno::EPROTO
>+    rescue IO::WantRead
>       assert_match(/WANT_READ/, $!.message)
>     end
>   ensure
>Index: test/socket/test_addrinfo.rb
>===================================================================
>--- test/socket/test_addrinfo.rb       (revision 22781)
>+++ test/socket/test_addrinfo.rb       (working copy)
>@@ -236,7 +236,7 @@ class TestSocketAddrinfo < Test::Unit::T
>     c.connect(serv.local_address)
>     begin
>       ret = serv.accept_nonblock
>-    rescue Errno::EAGAIN, Errno::EWOULDBLOCK, Errno::ECONNABORTED, 
>Errno::EPROTO, Errno::EINTR
>+    rescue IO::WantRead, Errno::EINTR
>       IO.select([serv])
>       retry
>     end
>@@ -299,7 +299,7 @@ class TestSocketAddrinfo < Test::Unit::T
>     s2.send("test-socket-recvfrom", 0, s1.getsockname)
>     begin
>       data, ai = s1.recvfrom_nonblock(100)
>-    rescue Errno::EWOULDBLOCK
>+    rescue IO::WantRead
>       IO.select([s1])
>       retry
>     end
>-- 
>[田中 哲][たなか あきら][Tanaka Akira]


#-#-#  Martin J. Du"rst, Assoc. Professor, Aoyama Gakuin University
#-#-#  http://www.sw.it.aoyama.ac.jp       mailto:duerst@it.aoyama.ac.jp     


In This Thread

Prev Next