From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on dcvr.yhbt.net X-Spam-Level: X-Spam-ASN: X-Spam-Status: No, score=-3.4 required=3.0 tests=AWL,BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,RCVD_IN_DNSWL_NONE,SPF_PASS shortcircuit=no autolearn=ham autolearn_force=no version=3.4.0 Received: from mail-wr0-x22f.google.com (mail-wr0-x22f.google.com [IPv6:2a00:1450:400c:c0c::22f]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by dcvr.yhbt.net (Postfix) with ESMTPS id 64C63201B0 for ; Sat, 25 Feb 2017 14:03:11 +0000 (UTC) Received: by mail-wr0-x22f.google.com with SMTP id 89so28756112wrr.3 for ; Sat, 25 Feb 2017 06:03:11 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=shopify.com; s=google; h=mime-version:from:date:message-id:subject:to; bh=BMx6rFU0RThRX9Iq+JCGj2yqlA1NQiw4lB/yVio7Elg=; b=RAyJCTxLz4uuT0/QiIaitpswjqrYSjM8Bi08NG8QecpCeXLZFTo7Px2yb/ivMR7Opv smQrson870fvskrw8aQXLujDFUTzoCB8FJEgrLDi3VPqqygIpiqZPIFguidsnUWhCM4N 5Cl6mORYna7bynnKRYYPZvEtdnA+gT9RjhD+E= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:mime-version:from:date:message-id:subject:to; bh=BMx6rFU0RThRX9Iq+JCGj2yqlA1NQiw4lB/yVio7Elg=; b=D67aRfrY5hg0xBxyl0arijhCZ/cttRAJOY2OWZigb6xXYeDFx2VqvLo5dRZY4m8ET2 e0OpXozWazbTN4rbXtxi2dBy9qAMz9/9iePrakk6ize2d8omABfFSzvXUA9YCr6cMTtE GcghocpFzbNeLzcUEYTGuiBNwRqEVQrJQnWwut8sNpgvo8BYj+Tox94oUVbOZrYYHECW 4iXuupVLqByBZhHTh2o+08PWZ8tqdKzbvC3N8g4bNVQB7jKtatQapA/1vDAAL7zruj/k rNTWAXozs6XvLLzcL8HqXiJ9/LdQuNbZh/FuO098KuPtFmKcPB4oOtu5kCqqRuy50uvQ 2wSg== X-Gm-Message-State: AMke39kwerlrFzMLKkcvkdVeykY97DdXS7sAty3qFSpdo9NwuC1AgwTjbYVAR2hwMkTInrSwMgY1odrk8LkdJVgA X-Received: by 10.223.153.168 with SMTP id y37mr2832829wrb.193.1488031389792; Sat, 25 Feb 2017 06:03:09 -0800 (PST) MIME-Version: 1.0 Received: by 10.28.197.66 with HTTP; Sat, 25 Feb 2017 06:03:09 -0800 (PST) From: Simon Eskildsen Date: Sat, 25 Feb 2017 09:03:09 -0500 Message-ID: Subject: [PATCH] check_client_connection: use tcp state on linux To: unicorn-public@bogomips.org Content-Type: text/plain; charset=UTF-8 List-Id: The implementation of the check_client_connection causing an early write is ineffective when not performed on loopback. In my testing, when on non-loopback, such as another host, we only see a 10-20% rejection rate with TCP_NODELAY of clients that are closed. This means 90-80% of responses in this case are rendered in vain. This patch uses getosockopt(2) with TCP_INFO on Linux to read the socket state. If the socket is in a state where it doesn't take a response, we'll abort the request with a similar error as to what write(2) would give us on a closed socket in case of an error. In my testing, this has a 100% rejection rate. This testing was conducted with the following script: 100.times do |i| client = TCPSocket.new("some-unicorn", 20_000) client.write("GET /collections/#{rand(10000)} HTTP/1.1\r\nHost:walrusser.myshopify.com\r\n\r\n") client.close end --- lib/unicorn/http_request.rb | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/lib/unicorn/http_request.rb b/lib/unicorn/http_request.rb index 0c1f9bb..b4c95fc 100644 --- a/lib/unicorn/http_request.rb +++ b/lib/unicorn/http_request.rb @@ -31,6 +31,9 @@ class Unicorn::HttpParser @@input_class = Unicorn::TeeInput @@check_client_connection = false + # TCP_TIME_WAIT: 6, TCP_CLOSE: 7, TCP_CLOSE_WAIT: 8, TCP_LAST_ACK: 9 + IGNORED_CHECK_CLIENT_SOCKET_STATES = (6..9) + def self.input_class @@input_class end @@ -83,10 +86,18 @@ def read(socket) false until add_parse(socket.kgio_read!(16384)) end - # detect if the socket is valid by writing a partial response: - if @@check_client_connection && headers? - self.response_start_sent = true - HTTP_RESPONSE_START.each { |c| socket.write(c) } + # detect if the socket is valid by checking client connection. + if @@check_client_connection + if defined?(Raindrops::TCP_Info) + tcp_info = Raindrops::TCP_Info.new(socket) + if IGNORED_CHECK_CLIENT_SOCKET_STATES.cover?(tcp_info.state) + socket.close + raise Errno::EPIPE + end + elsif headers? + self.response_start_sent = true + HTTP_RESPONSE_START.each { |c| socket.write(c) } + end end e['rack.input'] = 0 == content_length ? -- 2.11.0