* 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).