dumping ground for random patches and texts
 help / color / mirror / Atom feed
From: Eric Wong <e@80x24.org>
To: spew@80x24.org
Subject: [PATCH 3/5] socket: avoid arg parsing in rsock_s_accept_nonblock
Date: Fri, 13 Nov 2015 04:10:10 +0000	[thread overview]
Message-ID: <20151113041012.27235-4-e@80x24.org> (raw)
In-Reply-To: <20151113041012.27235-1-e@80x24.org>

* ext/socket/init.c (rsock_s_accept_nonblock): avoid parsing args
  [ruby-core:71439] [Feature #11339]
* ext/socket/rubysocket.h: adjust prototype
* ext/socket/socket.c (sock_accept_nonblock): make private
* ext/socket/tcpserver.c (tcp_accept_nonblock): ditto
* ext/socket/unixserver.c (unix_accept_nonblock): ditto
* ext/socket/lib/socket.rb (Socket#accept_nonblock):
  implement as wrapper, move RDoc
  (TCPServer#accept_nonblock): ditto
  (UNIXServer#accept_nonblock): ditto

target 0: a (ruby 2.3.0dev (2015-11-12 trunk 52550) [x86_64-linux])
target 1: b (ruby 2.3.0dev (2015-11-12 avoid-kwarg-capi 52550) [x86_64-linux]

-----------------------------------------------------------
accept_nonblock

require 'tempfile'
require 'socket'
require 'io/wait'
nr = 500000
Tempfile.create(%w(accept_nonblock .sock)) do |tmp|
  path = tmp.path
  File.unlink(path)
  s = UNIXServer.new(path)
  addr = Socket.sockaddr_un(path).freeze
  nr.times do
    s.accept_nonblock(exception: false)
    c = UNIXSocket.new(path)
    s.wait_readable
    s.accept_nonblock(exception: false).close
    c.close
  end
end

-----------------------------------------------------------
raw data:

[["accept_nonblock",
  [[4.807877402752638,
    4.930681671947241,
    4.738454818725586,
    4.69268161803484,
    4.684675686061382],
   [4.253904823213816,
    4.255124930292368,
    4.295955188572407,
    4.248479191213846,
    4.213303029537201]]]]

Elapsed time: 45.123040065 (sec)
-----------------------------------------------------------
benchmark results:
minimum results in each 5 measurements.
Execution time (sec)
name            a       b
accept_nonblock   4.685   4.213

Speedup ratio: compare with the result of `a' (greater is better)
name            b
accept_nonblock   1.112
---
 benchmark/bm_accept_nonblock.rb |  17 +++++
 ext/socket/init.c               |  10 +--
 ext/socket/lib/socket.rb        | 140 ++++++++++++++++++++++++++++++++++++++++
 ext/socket/rubysocket.h         |   3 +-
 ext/socket/socket.c             |  66 +++----------------
 ext/socket/tcpserver.c          |  48 ++------------
 ext/socket/unixserver.c         |  47 ++------------
 7 files changed, 183 insertions(+), 148 deletions(-)
 create mode 100644 benchmark/bm_accept_nonblock.rb

diff --git a/benchmark/bm_accept_nonblock.rb b/benchmark/bm_accept_nonblock.rb
new file mode 100644
index 0000000..8e537f9
--- /dev/null
+++ b/benchmark/bm_accept_nonblock.rb
@@ -0,0 +1,17 @@
+require 'tempfile'
+require 'socket'
+require 'io/wait'
+nr = 500000
+Tempfile.create(%w(accept_nonblock .sock)) do |tmp|
+  path = tmp.path
+  File.unlink(path)
+  s = UNIXServer.new(path)
+  addr = Socket.sockaddr_un(path).freeze
+  nr.times do
+    s.accept_nonblock(exception: false)
+    c = UNIXSocket.new(path)
+    s.wait_readable
+    s.accept_nonblock(exception: false).close
+    c.close
+  end
+end
diff --git a/ext/socket/init.c b/ext/socket/init.c
index 350ba65..45070d5 100644
--- a/ext/socket/init.c
+++ b/ext/socket/init.c
@@ -29,7 +29,7 @@ VALUE rb_cSOCKSSocket;
 #endif
 
 int rsock_do_not_reverse_lookup = 1;
-static VALUE sym_exception, sym_wait_readable;
+static VALUE sym_wait_readable;
 
 void
 rsock_raise_socket_error(const char *reason, int error)
@@ -544,13 +544,10 @@ cloexec_accept(int socket, struct sockaddr *address, socklen_t *address_len,
 }
 
 VALUE
-rsock_s_accept_nonblock(int argc, VALUE *argv, VALUE klass, rb_io_t *fptr,
+rsock_s_accept_nonblock(VALUE klass, VALUE ex, rb_io_t *fptr,
 			struct sockaddr *sockaddr, socklen_t *len)
 {
     int fd2;
-    VALUE opts = Qnil;
-
-    rb_scan_args(argc, argv, "0:", &opts);
 
     rb_io_set_nonblock(fptr);
     fd2 = cloexec_accept(fptr->fd, (struct sockaddr*)sockaddr, len, 1);
@@ -564,7 +561,7 @@ rsock_s_accept_nonblock(int argc, VALUE *argv, VALUE klass, rb_io_t *fptr,
 #if defined EPROTO
 	  case EPROTO:
 #endif
-            if (rsock_opt_false_p(opts, sym_exception))
+            if (ex == Qfalse)
 		return sym_wait_readable;
             rb_readwrite_sys_fail(RB_IO_WAIT_READABLE, "accept(2) would block");
 	}
@@ -673,6 +670,5 @@ rsock_init_socket_init(void)
     rsock_init_socket_constants();
 
 #undef rb_intern
-    sym_exception = ID2SYM(rb_intern("exception"));
     sym_wait_readable = ID2SYM(rb_intern("wait_readable"));
 }
diff --git a/ext/socket/lib/socket.rb b/ext/socket/lib/socket.rb
index 1f55323..edee69a 100644
--- a/ext/socket/lib/socket.rb
+++ b/ext/socket/lib/socket.rb
@@ -396,6 +396,63 @@ class Socket < BasicSocket
     __recvfrom_nonblock(len, flag, str, exception)
   end
 
+  # call-seq:
+  #   socket.accept_nonblock([options]) => [client_socket, client_addrinfo]
+  #
+  # Accepts an incoming connection using accept(2) after
+  # O_NONBLOCK is set for the underlying file descriptor.
+  # It returns an array containing the accepted socket
+  # for the incoming connection, _client_socket_,
+  # and an Addrinfo, _client_addrinfo_.
+  #
+  # === Example
+  #   # In one script, start this first
+  #   require 'socket'
+  #   include Socket::Constants
+  #   socket = Socket.new(AF_INET, SOCK_STREAM, 0)
+  #   sockaddr = Socket.sockaddr_in(2200, 'localhost')
+  #   socket.bind(sockaddr)
+  #   socket.listen(5)
+  #   begin # emulate blocking accept
+  #     client_socket, client_addrinfo = socket.accept_nonblock
+  #   rescue IO::WaitReadable, Errno::EINTR
+  #     IO.select([socket])
+  #     retry
+  #   end
+  #   puts "The client said, '#{client_socket.readline.chomp}'"
+  #   client_socket.puts "Hello from script one!"
+  #   socket.close
+  #
+  #   # In another script, start this second
+  #   require 'socket'
+  #   include Socket::Constants
+  #   socket = Socket.new(AF_INET, SOCK_STREAM, 0)
+  #   sockaddr = Socket.sockaddr_in(2200, 'localhost')
+  #   socket.connect(sockaddr)
+  #   socket.puts "Hello from script 2."
+  #   puts "The server said, '#{socket.readline.chomp}'"
+  #   socket.close
+  #
+  # Refer to Socket#accept for the exceptions that may be thrown if the call
+  # to _accept_nonblock_ fails.
+  #
+  # Socket#accept_nonblock may raise any error corresponding to accept(2) failure,
+  # including Errno::EWOULDBLOCK.
+  #
+  # If the exception is Errno::EWOULDBLOCK, Errno::EAGAIN, Errno::ECONNABORTED or Errno::EPROTO,
+  # it is extended by IO::WaitReadable.
+  # So IO::WaitReadable can be used to rescue the exceptions for retrying accept_nonblock.
+  #
+  # By specifying `exception: false`, the options hash allows you to indicate
+  # that accept_nonblock should not raise an IO::WaitReadable exception, but
+  # return the symbol :wait_readable instead.
+  #
+  # === See
+  # * Socket#accept
+  def accept_nonblock(exception: true)
+    __accept_nonblock(exception)
+  end
+
   # :call-seq:
   #   Socket.tcp(host, port, local_host=nil, local_port=nil, [opts]) {|socket| ... }
   #   Socket.tcp(host, port, local_host=nil, local_port=nil, [opts])
@@ -1084,3 +1141,86 @@ class UDPSocket < IPSocket
     __recvfrom_nonblock(len, flag, str, exception)
   end
 end
+
+class TCPServer < TCPSocket
+
+  # call-seq:
+  #   tcpserver.accept_nonblock([options]) => tcpsocket
+  #
+  # Accepts an incoming connection using accept(2) after
+  # O_NONBLOCK is set for the underlying file descriptor.
+  # It returns an accepted TCPSocket for the incoming connection.
+  #
+  # === Example
+  # 	require 'socket'
+  # 	serv = TCPServer.new(2202)
+  # 	begin # emulate blocking accept
+  # 	  sock = serv.accept_nonblock
+  # 	rescue IO::WaitReadable, Errno::EINTR
+  # 	  IO.select([serv])
+  # 	  retry
+  # 	end
+  # 	# sock is an accepted socket.
+  #
+  # Refer to Socket#accept for the exceptions that may be thrown if the call
+  # to TCPServer#accept_nonblock fails.
+  #
+  # TCPServer#accept_nonblock may raise any error corresponding to accept(2) failure,
+  # including Errno::EWOULDBLOCK.
+  #
+  # If the exception is Errno::EWOULDBLOCK, Errno::EAGAIN, Errno::ECONNABORTED, Errno::EPROTO,
+  # it is extended by IO::WaitReadable.
+  # So IO::WaitReadable can be used to rescue the exceptions for retrying accept_nonblock.
+  #
+  # By specifying `exception: false`, the options hash allows you to indicate
+  # that accept_nonblock should not raise an IO::WaitReadable exception, but
+  # return the symbol :wait_readable instead.
+  #
+  # === See
+  # * TCPServer#accept
+  # * Socket#accept
+  def accept_nonblock(exception: true)
+    __accept_nonblock(exception)
+  end
+end
+
+class UNIXServer < UNIXSocket
+  # call-seq:
+  #   unixserver.accept_nonblock([options]) => unixsocket
+  #
+  # Accepts an incoming connection using accept(2) after
+  # O_NONBLOCK is set for the underlying file descriptor.
+  # It returns an accepted UNIXSocket for the incoming connection.
+  #
+  # === Example
+  # 	require 'socket'
+  # 	serv = UNIXServer.new("/tmp/sock")
+  # 	begin # emulate blocking accept
+  # 	  sock = serv.accept_nonblock
+  # 	rescue IO::WaitReadable, Errno::EINTR
+  # 	  IO.select([serv])
+  # 	  retry
+  # 	end
+  # 	# sock is an accepted socket.
+  #
+  # Refer to Socket#accept for the exceptions that may be thrown if the call
+  # to UNIXServer#accept_nonblock fails.
+  #
+  # UNIXServer#accept_nonblock may raise any error corresponding to accept(2) failure,
+  # including Errno::EWOULDBLOCK.
+  #
+  # If the exception is Errno::EWOULDBLOCK, Errno::EAGAIN, Errno::ECONNABORTED or Errno::EPROTO,
+  # it is extended by IO::WaitReadable.
+  # So IO::WaitReadable can be used to rescue the exceptions for retrying accept_nonblock.
+  #
+  # By specifying `exception: false`, the options hash allows you to indicate
+  # that accept_nonblock should not raise an IO::WaitReadable exception, but
+  # return the symbol :wait_readable instead.
+  #
+  # === See
+  # * UNIXServer#accept
+  # * Socket#accept
+  def accept_nonblock(exception: true)
+    __accept_nonblock(exception)
+  end
+end
diff --git a/ext/socket/rubysocket.h b/ext/socket/rubysocket.h
index 9fa16ec..d39de0a 100644
--- a/ext/socket/rubysocket.h
+++ b/ext/socket/rubysocket.h
@@ -354,7 +354,8 @@ VALUE rsock_s_recvfrom(VALUE sock, int argc, VALUE *argv, enum sock_recv_type fr
 int rsock_connect(int fd, const struct sockaddr *sockaddr, int len, int socks);
 
 VALUE rsock_s_accept(VALUE klass, int fd, struct sockaddr *sockaddr, socklen_t *len);
-VALUE rsock_s_accept_nonblock(int argc, VALUE *argv, VALUE klass, rb_io_t *fptr, struct sockaddr *sockaddr, socklen_t *len);
+VALUE rsock_s_accept_nonblock(VALUE klass, VALUE ex, rb_io_t *fptr,
+			      struct sockaddr *sockaddr, socklen_t *len);
 VALUE rsock_sock_listen(VALUE sock, VALUE log);
 
 VALUE rsock_sockopt_new(int family, int level, int optname, VALUE data);
diff --git a/ext/socket/socket.c b/ext/socket/socket.c
index a410625..cc5cbe1 100644
--- a/ext/socket/socket.c
+++ b/ext/socket/socket.c
@@ -800,63 +800,9 @@ sock_accept(VALUE sock)
     return rb_assoc_new(sock2, rsock_io_socket_addrinfo(sock2, &buf.addr, len));
 }
 
-/*
- * call-seq:
- *   socket.accept_nonblock([options]) => [client_socket, client_addrinfo]
- *
- * Accepts an incoming connection using accept(2) after
- * O_NONBLOCK is set for the underlying file descriptor.
- * It returns an array containing the accepted socket
- * for the incoming connection, _client_socket_,
- * and an Addrinfo, _client_addrinfo_.
- *
- * === Example
- *   # In one script, start this first
- *   require 'socket'
- *   include Socket::Constants
- *   socket = Socket.new(AF_INET, SOCK_STREAM, 0)
- *   sockaddr = Socket.sockaddr_in(2200, 'localhost')
- *   socket.bind(sockaddr)
- *   socket.listen(5)
- *   begin # emulate blocking accept
- *     client_socket, client_addrinfo = socket.accept_nonblock
- *   rescue IO::WaitReadable, Errno::EINTR
- *     IO.select([socket])
- *     retry
- *   end
- *   puts "The client said, '#{client_socket.readline.chomp}'"
- *   client_socket.puts "Hello from script one!"
- *   socket.close
- *
- *   # In another script, start this second
- *   require 'socket'
- *   include Socket::Constants
- *   socket = Socket.new(AF_INET, SOCK_STREAM, 0)
- *   sockaddr = Socket.sockaddr_in(2200, 'localhost')
- *   socket.connect(sockaddr)
- *   socket.puts "Hello from script 2."
- *   puts "The server said, '#{socket.readline.chomp}'"
- *   socket.close
- *
- * Refer to Socket#accept for the exceptions that may be thrown if the call
- * to _accept_nonblock_ fails.
- *
- * Socket#accept_nonblock may raise any error corresponding to accept(2) failure,
- * including Errno::EWOULDBLOCK.
- *
- * If the exception is Errno::EWOULDBLOCK, Errno::EAGAIN, Errno::ECONNABORTED or Errno::EPROTO,
- * it is extended by IO::WaitReadable.
- * So IO::WaitReadable can be used to rescue the exceptions for retrying accept_nonblock.
- *
- * By specifying `exception: false`, the options hash allows you to indicate
- * that accept_nonblock should not raise an IO::WaitReadable exception, but
- * return the symbol :wait_readable instead.
- *
- * === See
- * * Socket#accept
- */
+/* :nodoc: */
 static VALUE
-sock_accept_nonblock(int argc, VALUE *argv, VALUE sock)
+sock_accept_nonblock(VALUE sock, VALUE ex)
 {
     rb_io_t *fptr;
     VALUE sock2;
@@ -865,7 +811,7 @@ sock_accept_nonblock(int argc, VALUE *argv, VALUE sock)
     socklen_t len = (socklen_t)sizeof buf;
 
     GetOpenFile(sock, fptr);
-    sock2 = rsock_s_accept_nonblock(argc, argv, rb_cSocket, fptr, addr, &len);
+    sock2 = rsock_s_accept_nonblock(rb_cSocket, ex, fptr, addr, &len);
 
     if (SYMBOL_P(sock2)) /* :wait_readable */
 	return sock2;
@@ -2073,7 +2019,11 @@ Init_socket(void)
     rb_define_method(rb_cSocket, "bind", sock_bind, 1);
     rb_define_method(rb_cSocket, "listen", rsock_sock_listen, 1);
     rb_define_method(rb_cSocket, "accept", sock_accept, 0);
-    rb_define_method(rb_cSocket, "accept_nonblock", sock_accept_nonblock, -1);
+
+    /* for ext/socket/lib/socket.rb use only: */
+    rb_define_private_method(rb_cSocket,
+			     "__accept_nonblock", sock_accept_nonblock, 1);
+
     rb_define_method(rb_cSocket, "sysaccept", sock_sysaccept, 0);
 
     rb_define_method(rb_cSocket, "recvfrom", sock_recvfrom, -1);
diff --git a/ext/socket/tcpserver.c b/ext/socket/tcpserver.c
index a38faf8..1bbb31a 100644
--- a/ext/socket/tcpserver.c
+++ b/ext/socket/tcpserver.c
@@ -64,53 +64,16 @@ tcp_accept(VALUE sock)
     return rsock_s_accept(rb_cTCPSocket, fptr->fd, &from.addr, &fromlen);
 }
 
-/*
- * call-seq:
- *   tcpserver.accept_nonblock([options]) => tcpsocket
- *
- * Accepts an incoming connection using accept(2) after
- * O_NONBLOCK is set for the underlying file descriptor.
- * It returns an accepted TCPSocket for the incoming connection.
- *
- * === Example
- * 	require 'socket'
- * 	serv = TCPServer.new(2202)
- * 	begin # emulate blocking accept
- * 	  sock = serv.accept_nonblock
- * 	rescue IO::WaitReadable, Errno::EINTR
- * 	  IO.select([serv])
- * 	  retry
- * 	end
- * 	# sock is an accepted socket.
- *
- * Refer to Socket#accept for the exceptions that may be thrown if the call
- * to TCPServer#accept_nonblock fails.
- *
- * TCPServer#accept_nonblock may raise any error corresponding to accept(2) failure,
- * including Errno::EWOULDBLOCK.
- *
- * If the exception is Errno::EWOULDBLOCK, Errno::EAGAIN, Errno::ECONNABORTED, Errno::EPROTO,
- * it is extended by IO::WaitReadable.
- * So IO::WaitReadable can be used to rescue the exceptions for retrying accept_nonblock.
- *
- * By specifying `exception: false`, the options hash allows you to indicate
- * that accept_nonblock should not raise an IO::WaitReadable exception, but
- * return the symbol :wait_readable instead.
- *
- * === See
- * * TCPServer#accept
- * * Socket#accept
- */
+/* :nodoc: */
 static VALUE
-tcp_accept_nonblock(int argc, VALUE *argv, VALUE sock)
+tcp_accept_nonblock(VALUE sock, VALUE ex)
 {
     rb_io_t *fptr;
     union_sockaddr from;
-    socklen_t fromlen;
+    socklen_t len = (socklen_t)sizeof(from);
 
     GetOpenFile(sock, fptr);
-    fromlen = (socklen_t)sizeof(from);
-    return rsock_s_accept_nonblock(argc, argv, rb_cTCPSocket, fptr, &from.addr, &fromlen);
+    return rsock_s_accept_nonblock(rb_cTCPSocket, ex, fptr, &from.addr, &len);
 }
 
 /*
@@ -175,7 +138,8 @@ rsock_init_tcpserver(void)
      */
     rb_cTCPServer = rb_define_class("TCPServer", rb_cTCPSocket);
     rb_define_method(rb_cTCPServer, "accept", tcp_accept, 0);
-    rb_define_method(rb_cTCPServer, "accept_nonblock", tcp_accept_nonblock, -1);
+    rb_define_private_method(rb_cTCPServer,
+			     "__accept_nonblock", tcp_accept_nonblock, 1);
     rb_define_method(rb_cTCPServer, "sysaccept", tcp_sysaccept, 0);
     rb_define_method(rb_cTCPServer, "initialize", tcp_svr_init, -1);
     rb_define_method(rb_cTCPServer, "listen", rsock_sock_listen, 1); /* in socket.c */
diff --git a/ext/socket/unixserver.c b/ext/socket/unixserver.c
index 532d951..799dcff 100644
--- a/ext/socket/unixserver.c
+++ b/ext/socket/unixserver.c
@@ -57,45 +57,9 @@ unix_accept(VALUE sock)
 		          (struct sockaddr*)&from, &fromlen);
 }
 
-/*
- * call-seq:
- *   unixserver.accept_nonblock([options]) => unixsocket
- *
- * Accepts an incoming connection using accept(2) after
- * O_NONBLOCK is set for the underlying file descriptor.
- * It returns an accepted UNIXSocket for the incoming connection.
- *
- * === Example
- * 	require 'socket'
- * 	serv = UNIXServer.new("/tmp/sock")
- * 	begin # emulate blocking accept
- * 	  sock = serv.accept_nonblock
- * 	rescue IO::WaitReadable, Errno::EINTR
- * 	  IO.select([serv])
- * 	  retry
- * 	end
- * 	# sock is an accepted socket.
- *
- * Refer to Socket#accept for the exceptions that may be thrown if the call
- * to UNIXServer#accept_nonblock fails.
- *
- * UNIXServer#accept_nonblock may raise any error corresponding to accept(2) failure,
- * including Errno::EWOULDBLOCK.
- *
- * If the exception is Errno::EWOULDBLOCK, Errno::EAGAIN, Errno::ECONNABORTED or Errno::EPROTO,
- * it is extended by IO::WaitReadable.
- * So IO::WaitReadable can be used to rescue the exceptions for retrying accept_nonblock.
- *
- * By specifying `exception: false`, the options hash allows you to indicate
- * that accept_nonblock should not raise an IO::WaitReadable exception, but
- * return the symbol :wait_readable instead.
- *
- * === See
- * * UNIXServer#accept
- * * Socket#accept
- */
+/* :nodoc: */
 static VALUE
-unix_accept_nonblock(int argc, VALUE *argv, VALUE sock)
+unix_accept_nonblock(VALUE sock, VALUE ex)
 {
     rb_io_t *fptr;
     struct sockaddr_un from;
@@ -103,7 +67,7 @@ unix_accept_nonblock(int argc, VALUE *argv, VALUE sock)
 
     GetOpenFile(sock, fptr);
     fromlen = (socklen_t)sizeof(from);
-    return rsock_s_accept_nonblock(argc, argv, rb_cUNIXSocket, fptr,
+    return rsock_s_accept_nonblock(rb_cUNIXSocket, ex, fptr,
 			           (struct sockaddr *)&from, &fromlen);
 }
 
@@ -152,7 +116,10 @@ rsock_init_unixserver(void)
     rb_cUNIXServer = rb_define_class("UNIXServer", rb_cUNIXSocket);
     rb_define_method(rb_cUNIXServer, "initialize", unix_svr_init, 1);
     rb_define_method(rb_cUNIXServer, "accept", unix_accept, 0);
-    rb_define_method(rb_cUNIXServer, "accept_nonblock", unix_accept_nonblock, -1);
+
+    rb_define_private_method(rb_cUNIXServer,
+			     "__accept_nonblock", unix_accept_nonblock, 1);
+
     rb_define_method(rb_cUNIXServer, "sysaccept", unix_sysaccept, 0);
     rb_define_method(rb_cUNIXServer, "listen", rsock_sock_listen, 1); /* in socket.c */
 #endif
-- 
EW


  parent reply	other threads:[~2015-11-13  4:10 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2015-11-13  4:10 [PATCH 0/5] avoid kwarg parsing in socket ext Eric Wong
2015-11-13  4:10 ` [PATCH 1/5] socket: avoid arg parsing in rsock_s_recvfrom_nonblock Eric Wong
2015-11-13  4:10 ` [PATCH 2/5] socket: Socket#connect_nonblock avoids arg parsing with C API Eric Wong
2015-11-13  4:10 ` Eric Wong [this message]
2015-11-13  4:10 ` [PATCH 4/5] socket (bsock_recvmsg_internal): avoid arg parsing Eric Wong
2015-11-13  4:10 ` [PATCH 5/5] socket: avoid arg parsing in bsock_sendmsg_internal Eric Wong

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20151113041012.27235-4-e@80x24.org \
    --to=e@80x24.org \
    --cc=spew@80x24.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).