* Should fcgi catch EPIPE?
@ 2009-12-23 16:31 Christian Neukirchen
2009-12-23 18:08 ` James Tucker
0 siblings, 1 reply; 4+ messages in thread
From: Christian Neukirchen @ 2009-12-23 16:31 UTC (permalink / raw)
To: Rack Development
15:17:37 < stbuehler> chris2: hi! i've been told to ask you... i'd
like to know how to get rid of the EPIPE backtraces in my
fastcgi.crash.log :)
15:17:52 < stbuehler> fastcgi-spec: "When a Web server is not
multiplexing requests over a transport connection, the Web server can
abort a request by closing the request's transport connection." - so i
think rack should handle EPIPE somehow
15:17:56 < stbuehler> some backtraces from EPIPE:
http://paste.lighttpd.net/727
Any good reason to not catch EPIPE?
--
Christian Neukirchen <chneukirchen@gmail.com> http://chneukirchen.org
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: Should fcgi catch EPIPE?
2009-12-23 16:31 Should fcgi catch EPIPE? Christian Neukirchen
@ 2009-12-23 18:08 ` James Tucker
2009-12-23 19:11 ` Christian Neukirchen
0 siblings, 1 reply; 4+ messages in thread
From: James Tucker @ 2009-12-23 18:08 UTC (permalink / raw)
To: rack-devel
[-- Attachment #1: Type: text/plain, Size: 1896 bytes --]
On 23 Dec 2009, at 16:31, Christian Neukirchen wrote:
>
> 15:17:37 < stbuehler> chris2: hi! i've been told to ask you... i'd
> like to know how to get rid of the EPIPE backtraces in my
> fastcgi.crash.log :)
> 15:17:52 < stbuehler> fastcgi-spec: "When a Web server is not
> multiplexing requests over a transport connection, the Web server can
> abort a request by closing the request's transport connection." - so i
> think rack should handle EPIPE somehow
> 15:17:56 < stbuehler> some backtraces from EPIPE:
> http://paste.lighttpd.net/727
>
> Any good reason to not catch EPIPE?
At the top level - maybe - maybe not.
Consider this (awful pseudo code):
run lambda do |env|
thing = Persistence.fetch(:a_thing)
begin; lock = thing.acquire_lock; end until lock
print thing.some_property
thing.change!
print thing.some_other_property
lock.release
end
The slightly more correct version, would be:
run lambda do |env|
begin
thing = Persistence.fetch(:a_thing)
begin; lock = thing.acquire_lock; end until lock
print thing.some_property
thing.change!
print thing.some_other_property
ensure
lock.release
end
end
I'm sure you get the principle of intent here. This is relevant because, in example 1 (how IME the bulk of rubyists would write their apps), the problem of unresolved locks is almost impossible to debug unless the persistence layer can give you some kind of trace on when / where the lock was acquired. If we've silenced EPIPE completely, then there's no indication of error to the application developer, and so many developers would take a long time to realise that example 2 is how they need to deal with the problem.
The question is, do we want to protect those folks (given that we're framework-like)?
> --
> Christian Neukirchen <chneukirchen@gmail.com> http://chneukirchen.org
[-- Attachment #2: smime.p7s --]
[-- Type: application/pkcs7-signature, Size: 3679 bytes --]
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: Should fcgi catch EPIPE?
2009-12-23 18:08 ` James Tucker
@ 2009-12-23 19:11 ` Christian Neukirchen
2009-12-23 21:12 ` Eric Wong
0 siblings, 1 reply; 4+ messages in thread
From: Christian Neukirchen @ 2009-12-23 19:11 UTC (permalink / raw)
To: rack-devel
James Tucker <jftucker@gmail.com> writes:
> If we've silenced EPIPE completely, then there's no indication of
> error to the application developer
I thought of something like:
def self.send_headers(out, status, headers)
out.print "Status: #{status}\r\n"
headers.each { |k, vs|
vs.split("\n").each { |v|
out.print "#{k}: #{v}\r\n"
}
}
out.print "\r\n"
out.flush
+ rescue Errno::EPIPE
end
def self.send_body(out, body)
body.each { |part|
out.print part
out.flush
}
+ rescue Errno::EPIPE
end
--
Christian Neukirchen <chneukirchen@gmail.com> http://chneukirchen.org
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: Should fcgi catch EPIPE?
2009-12-23 19:11 ` Christian Neukirchen
@ 2009-12-23 21:12 ` Eric Wong
0 siblings, 0 replies; 4+ messages in thread
From: Eric Wong @ 2009-12-23 21:12 UTC (permalink / raw)
To: rack-devel
Christian Neukirchen <chneukirchen@gmail.com> wrote:
> James Tucker <jftucker@gmail.com> writes:
> > If we've silenced EPIPE completely, then there's no indication of
> > error to the application developer
>
> I thought of something like:
>
> def self.send_headers(out, status, headers)
> out.print "Status: #{status}\r\n"
> headers.each { |k, vs|
> vs.split("\n").each { |v|
> out.print "#{k}: #{v}\r\n"
> }
> }
> out.print "\r\n"
> out.flush
> + rescue Errno::EPIPE
> end
>
> def self.send_body(out, body)
> body.each { |part|
> out.print part
> out.flush
> }
> + rescue Errno::EPIPE
> end
To be pedantic, body#each (and headers#each) may also hit EPIPE by
themselves (body could be a pipe/socket itself) and the application
developer may want to know to track down the error with a backtrace.
But avoiding excessive logging/backtraces is a good idea if the
client initiated the disconnect, since it's a potential DoS
(and at the best case, makes a lot of noise).
The following diff is similar to what I did with the Unicorn::TeeInput
class since that can expose EOFError to the application, which (being
client initiated and out of the app's control) would just mean
unnecessary noise for anybody inspecting logs.
Warning: totally untested, possible typos/syntax errors
--- a/lib/rack/handler/fastcgi.rb
+++ b/lib/rack/handler/fastcgi.rb
@@ -53,11 +53,15 @@ module Rack
env.delete "CONTENT_TYPE" if env["CONTENT_TYPE"] == ""
env.delete "CONTENT_LENGTH" if env["CONTENT_LENGTH"] == ""
+ class ClientDisconnect < Errno::EPIPE
+ end
+
begin
status, headers, body = app.call(env)
begin
send_headers request.out, status, headers
send_body request.out, body
+ rescue ClientDisconnect
ensure
body.close if body.respond_to? :close
end
@@ -67,21 +71,26 @@ module Rack
end
end
+ def self.print_safely(out, part, flush = false)
+ out.print part
+ out.flush if flush
+ rescue Errno::EPIPE => e
+ raise ClientDisconnect, "client disconnected", []
+ end
+
def self.send_headers(out, status, headers)
- out.print "Status: #{status}\r\n"
+ print_safely(out, "Status: #{status}\r\n")
headers.each { |k, vs|
vs.split("\n").each { |v|
- out.print "#{k}: #{v}\r\n"
+ print_safely(out, "#{k}: #{v}\r\n")
}
}
- out.print "\r\n"
- out.flush
+ print_safely(out, "\r\n", true)
end
def self.send_body(out, body)
body.each { |part|
- out.print part
- out.flush
+ print_safely(out, part, true)
}
end
end
--
Eric Wong
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2009-12-23 21:12 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2009-12-23 16:31 Should fcgi catch EPIPE? Christian Neukirchen
2009-12-23 18:08 ` James Tucker
2009-12-23 19:11 ` Christian Neukirchen
2009-12-23 21:12 ` Eric Wong
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).