I''m using eventmachine-0.8.1. I have a couple of questions/requests: (1) When an exception occurs inside a Connection handler, it seems to be silently handled and the connection''s ''unbind'' method is called. Is there a general hook where I can log these exceptions? At the moment the best I can think of is def receive_data data ... rescue Exception => e $stderr.puts "#{e}\n#{e.backtrace.join("\n")}" raise end but I have to remember to repeat this in every Connection subclass I write. (2) If I attempt to bind to a port which is already in use, I get a rather strange error: /usr/lib/ruby/gems/1.8/gems/eventmachine-0.8.1/lib/eventmachine.rb:476:in `start_tcp_server'': no acceptor (RuntimeError) It took me a while to work out what it was on about. Would it be possible to change this to a more standard exception like Errno::EADDRINUSE ? Thanks... Brian.
On 10/2/07, Brian Candler <B.Candler at pobox.com> wrote:> > I''m using eventmachine-0.8.1. I have a couple of questions/requests: > > (1) When an exception occurs inside a Connection handler, it seems to be > silently handled and the connection''s ''unbind'' method is called. Is there > a > general hook where I can log these exceptions? At the moment the best I > can > think of is > > def receive_data data > ... > rescue Exception => e > $stderr.puts "#{e}\n#{e.backtrace.join("\n")}" > raise > end > > but I have to remember to repeat this in every Connection subclass I > write.Are you thinking of errors generated by the reactor itself (like a connection failure) or errors generated by your Ruby code? It''s true that a failure to connect to a remote TCP host is defined as a non-error. The defined way to detect this condition is to observe that Connection#unbind is called without #connection_completed being called. I have to admit that''s no more graceful than putting a begin/rescue around the connect call! (2) If I attempt to bind to a port which is already in use, I get a rather> strange error: > > /usr/lib/ruby/gems/1.8/gems/eventmachine-0.8.1/lib/eventmachine.rb:476:in > `start_tcp_server'': no acceptor (RuntimeError) > > It took me a while to work out what it was on about. Would it be possible > to > change this to a more standard exception like Errno::EADDRINUSE ?That''s a good suggestion and worth doing. I can''t remember now if there was some rationale for treating acceptor and connection errors differently. They probably ought to be the same. There is a small number of reactor-level errors that will be emitted as C++ exceptions. These will never go away, because the EM core library can be used by languages other than Ruby. But as a rule, they''re really low-level internal errors that you should rarely see anyway. A failure to open a server socket should NOT be one of these errors. -------------- next part -------------- An HTML attachment was scrubbed... URL: http://rubyforge.org/pipermail/eventmachine-talk/attachments/20071002/e77be95d/attachment.html
> Are you thinking of errors generated by the reactor itself (like a > connection failure) or errors generated by your Ruby code?I mean errors generated by my Ruby code, programming errors in particular. I was finding them difficult to track down because the only feedback I got was that the connection was dropped. For comparison, using GServer there''s a top-level exception catcher which by default writes the exception to a log, and you can override the ''error'' method to do something else with it. I just wondered if there was an equivalent hook for Eventmachine. Cheers, Brian.
On 10/2/07, Brian Candler <B.Candler at pobox.com> wrote:(2) If I attempt to bind to a port which is already in use, I get a rather> strange error: > > /usr/lib/ruby/gems/1.8/gems/eventmachine-0.8.1/lib/eventmachine.rb:476:in > `start_tcp_server'': no acceptor (RuntimeError) > > It took me a while to work out what it was on about. Would it be possible > to > change this to a more standard exception like Errno::EADDRINUSE ?Ok, so I hadn''t remembered that the Ruby-glue code in the EM extension converts this error to a Ruby RuntimeError. So you can catch it with an ordinary rescue statement. I agree that the various "normal" errors (like not being able to start a TCP server) deserve more informative error treatment. What I don''t like about your specific suggestion is that EADDRINUSE isn''t the only way for start_server to fail. What if we raised subclasses of EventMachine::Error? -------------- next part -------------- An HTML attachment was scrubbed... URL: http://rubyforge.org/pipermail/eventmachine-talk/attachments/20071003/a1d43886/attachment-0001.html
On 10/3/07, Brian Candler <B.Candler at pobox.com> wrote:> > > Are you thinking of errors generated by the reactor itself (like a > > connection failure) or errors generated by your Ruby code? > > I mean errors generated by my Ruby code, programming errors in particular. > I > was finding them difficult to track down because the only feedback I got > was > that the connection was dropped. > > For comparison, using GServer there''s a top-level exception catcher which > by > default writes the exception to a log, and you can override the ''error'' > method to do something else with it. I just wondered if there was an > equivalent hook for Eventmachine. > > Cheers, > > Brian.How would you feel about a syntax like this: require ''eventmachine'' EM.run { EM.set_exception_hook {|exception| # do something useful } # do your other stuff } -------------- next part -------------- An HTML attachment was scrubbed... URL: http://rubyforge.org/pipermail/eventmachine-talk/attachments/20071003/9bef0a0e/attachment.html
On 10/3/07, Brian Candler <B.Candler at pobox.com> wrote:> > > Are you thinking of errors generated by the reactor itself (like a > > connection failure) or errors generated by your Ruby code? > > I mean errors generated by my Ruby code, programming errors in particular. > I > was finding them difficult to track down because the only feedback I got > was > that the connection was dropped. > > For comparison, using GServer there''s a top-level exception catcher which > by > default writes the exception to a log, and you can override the ''error'' > method to do something else with it. I just wondered if there was an > equivalent hook for Eventmachine.Sync to the current head revision. I added some support for this idea. There now is a default hook for RuntimeErrors emitted from user code, that simply rethrows the error. (Thus existing code will not change behavior.) To set your own handler, call: EM::set_runtime_error_hook { # this block will be called whenever user code throws a RuntimeError. } You can call this hook whether or not the reactor is running. You can call it repeatedly (which replaces the handler), and you can call it without a block (which restores the default behavior). You can also provide your own implementation of EventMachine::handle_runtime_error, but I think the hook function will be more convenient. Was that what you had in mind? -------------- next part -------------- An HTML attachment was scrubbed... URL: http://rubyforge.org/pipermail/eventmachine-talk/attachments/20071003/361c22cb/attachment.html
On Wed, Oct 03, 2007 at 01:44:20AM -0400, Francis Cianfrocca wrote:> (2) If I attempt to bind to a port which is already in use, I get a > rather > > strange error: > /usr/lib/ruby/gems/1.8/gems/eventmachine-0.8.1/lib/eventmachine.rb: > 476:in `start_tcp_server'': no acceptor (RuntimeError) > It took me a while to work out what it was on about. Would it be > possible to > change this to a more standard exception like Errno::EADDRINUSE ? > > Ok, so I hadn''t remembered that the Ruby-glue code in the EM extension > converts this error to a Ruby RuntimeError. So you can catch it with > an ordinary rescue statement.Absolutely. But in this case I didn''t want to catch it - I just wanted to understand what the error meant. What happened was I kept running my daemon, getting this strange error, poking around my own code to find what I''d broken, scratching my head... only to find eventually that I had simply left another copy of the daemon running in a different window :-) Sorry if I was being confusing by putting two unrelated comments into one posting.> I agree that the various "normal" errors (like not being able to start > a TCP server) deserve more informative error treatment. What I don''t > like about your specific suggestion is that EADDRINUSE isn''t the only > way for start_server to fail.Well that''s true, but start_server makes system calls to open a socket and bind it to a port, and all the failures are ultimately going to be Errno derivatives. If there are other failure modes of start_server, then I expect these are of the form "internal failure in the eventmachine library" - and I''m happy for these to be raised as a Runtime error with a descriptive message as they are now. Changing these to EventMachine::Error would be nice, but isn''t essential. I''d still want system errors to be the original Errno::XXX if possible though. As for failures to make outbound connections, and also my other point about catching application errors: maybe an exception object could be passed as a parameter to the unbind() call? Or stored in an instance variable, to avoid an API change? Regards, Brian.
On Wed, Oct 03, 2007 at 06:42:48AM +0100, Brian Candler wrote:> > Are you thinking of errors generated by the reactor itself (like a > > connection failure) or errors generated by your Ruby code? > > I mean errors generated by my Ruby code, programming errors in particular. I > was finding them difficult to track down because the only feedback I got was > that the connection was dropped. > > For comparison, using GServer there''s a top-level exception catcher which by > default writes the exception to a log, and you can override the ''error'' > method to do something else with it. I just wondered if there was an > equivalent hook for Eventmachine.I''ve been investigating my problem further under eventmachine-0.8.1, and I think I have fed you a partial red-herring. I only get the problem when using the HeaderAndContentProtocol component. Here''s a program which demonstrates the issue: ----- 8< ---------------------------------------------- require ''rubygems'' require ''eventmachine'' require ''protocols/header_and_content'' class MyConnection < EventMachine::Protocols::HeaderAndContentProtocol def receive_request(headers, content) xxx # this should raise an exception end end EventMachine::run do EventMachine::start_server(''127.0.0.1'', 8086, MyConnection) do |c| c.send_data "Hello!\r\n" end end ------------------------------------------------------- When you connect and send a header followed by a blank line, it simply drops the connection and keeps on running. No exception is displayed anywhere. This is different to the behaviour when using a raw Connection class: ----- 8< ---------------------------------------------- require ''rubygems'' require ''eventmachine'' class MyConnection < EventMachine::Connection def receive_data(str) xxx end end EventMachine::run do EventMachine::start_server(''127.0.0.1'', 8086, MyConnection) do |c| c.send_data "Hello!\r\n" end end ------------------------------------------------------- In this case, the program aborts when the exception is raised in receive_data, which is what I expected. (The new hook for catching these exceptions is a good idea; I have not tested it yet though) Now, I think the problem is due to rescue Exception in protocols/line_and_text.rb line 45. The following fix appears to work for me: --- lib/protocols/buftok.rb.orig 2007-10-05 20:22:59.000000000 +0100 +++ lib/protocols/buftok.rb 2007-10-05 20:23:47.000000000 +0100 @@ -20,6 +20,7 @@ # by which entities are delimited. class BufferedTokenizer + class InputBufferFull < ::RuntimeError; end # New BufferedTokenizers will operate on lines delimited by "\n" by default # or allow you to specify any delimiter token you so choose, which will then # be used by String#split to tokenize the input data @@ -57,7 +58,7 @@ # Check to see if the buffer has exceeded capacity, if we''re imposing a limit if @size_limit - raise ''input buffer full'' if @input_size + entities.first.size > @size_limit + raise InputBufferFull if @input_size + entities.first.size > @size_limit @input_size += entities.first.size end --- lib/protocols/line_and_text.rb.orig 2007-10-05 20:23:03.000000000 +0100 +++ lib/protocols/line_and_text.rb 2007-10-05 20:24:10.000000000 +0100 @@ -42,7 +42,7 @@ @lpb_buffer.extract(data).each do |line| receive_line(line.chomp) if respond_to?(:receive_line) end - rescue Exception + rescue BufferedTokenizer::InputBufferFull receive_error(''overlength line'') if respond_to?(:receive_error) close_connection return Now if an exception occurs in receive_request, it aborts the same as a normal Connection. Regards, Brian.
On 10/5/07, Brian Candler <B.Candler at pobox.com> wrote:> > > I''ve been investigating my problem further under eventmachine-0.8.1, and I > think I have fed you a partial red-herring. I only get the problem when > using the HeaderAndContentProtocol component. > > Here''s a program which demonstrates the issue: > > ----- 8< ---------------------------------------------- > require ''rubygems'' > require ''eventmachine'' > require ''protocols/header_and_content'' > > class MyConnection < EventMachine::Protocols::HeaderAndContentProtocol > def receive_request(headers, content) > xxx # this should raise an exception > end > end > > EventMachine::run do > EventMachine::start_server(''127.0.0.1'', 8086, MyConnection) do |c| > c.send_data "Hello!\r\n" > end > end > ------------------------------------------------------- > > When you connect and send a header followed by a blank line, it simply > drops > the connection and keeps on running. No exception is displayed anywhere.Brian, I haven''t had a chance to run your test program, but I assume you mean receive_line rather than receive_request. More to the point, the protocol handler is written in such a way as to give user code the opportunity to handle errors without aborting the program. In your example, if you had defined a method named receive_error in your class, then it would have been called. You may reasonably disagree with this design choice! Your thoughts? -------------- next part -------------- An HTML attachment was scrubbed... URL: http://rubyforge.org/pipermail/eventmachine-talk/attachments/20071005/ab1f6252/attachment.html
> Brian, I haven''t had a chance to run your test program, but I assume you > mean receive_line rather than receive_requestNo, I definitely mean receive_request. The HeaderAndContentProtocol receives a HTTP-like request of a number of header lines, a blank line, then Content-Length: bytes of data. It then invokes receive_request, passing in the headers and content as two objects. It uses receive_line from LineAndTextProtocol internally.> More to the point, the protocol handler is written in such a way as to give > user code the opportunity to handle errors without aborting the program. In > your example, if you had defined a method named receive_error in your class, > then it would have been called.However it would have been called with "overlength line" as the error: rescue Exception receive_error(''overlength line'') if respond_to?(:receive_error) close_connection return even though in my case the actual exception was an application-generated one. If it said receive_error($!) then that would make more sense.> You may reasonably disagree with this design choice! Your thoughts?Well, I *did* say originally that I wanted a way to catch and log exceptions :-) If this was receive_error($!) then that would give what I need. I would only disagree with it on the basis of inconsistency, because this feature isn''t available for classes which directly subclass EventMachine::Connection. So maybe Connection should be extended to work in this way too? Regards, Brian.
On 10/6/07, Brian Candler <B.Candler at pobox.com> wrote:> > > Brian, I haven''t had a chance to run your test program, but I assume you > > mean receive_line rather than receive_request > > No, I definitely mean receive_request. > > The HeaderAndContentProtocol receives a HTTP-like request of a number of > header lines, a blank line, then Content-Length: bytes of data. It then > invokes receive_request, passing in the headers and content as two > objects. > > It uses receive_line from LineAndTextProtocol internally. > > > More to the point, the protocol handler is written in such a way as to > give > > user code the opportunity to handle errors without aborting the program. > In > > your example, if you had defined a method named receive_error in your > class, > > then it would have been called. > > However it would have been called with "overlength line" as the error: > > rescue Exception > receive_error(''overlength line'') if > respond_to?(:receive_error) > close_connection > return > > even though in my case the actual exception was an application-generated > one. If it said receive_error($!) then that would make more sense. > > > You may reasonably disagree with this design choice! Your thoughts? > > Well, I *did* say originally that I wanted a way to catch and log > exceptions > :-) If this was receive_error($!) then that would give what I need. > > I would only disagree with it on the basis of inconsistency, because this > feature isn''t available for classes which directly subclass > EventMachine::Connection. So maybe Connection should be extended to work > in > this way too?Please do try the new exception hook that I added by way of responding to your original question. If you like it (and others like it), then we can make that the "blessed" way to handle exceptions from user code, and take out the exception handling from classes like HeaderAndContent. (And it was my mistake not to realize that''s what you were using. I''d forgotten about that class. EM''s HTTP client doesn''t use it. Maybe it was intended for SIP.) EM tries (not with complete consistency) to wrap user-code exceptions *by default* rather than letting them kill your program, which is the default most programmers would expect. The reasoning behind this is that EM is used to create server programs that can and will run for months without being actively monitored or managed. So you generally don''t want them to just crash in the middle of a run. -------------- next part -------------- An HTML attachment was scrubbed... URL: http://rubyforge.org/pipermail/eventmachine-talk/attachments/20071006/43c74159/attachment-0001.html
On Sat, Oct 06, 2007 at 08:26:16AM -0400, Francis Cianfrocca wrote:> Please do try the new exception hook that I added by way of responding > to your original question. If you like it (and others like it), then > we can make that the "blessed" way to handle exceptions from user > code, and take out the exception handling from classes like > HeaderAndContent.Well, in your previous mail you wrote:> Sync to the current head revision. I added some support for this idea. > There now is a default hook for RuntimeErrors emitted from user code, > that simply rethrows the error. (Thus existing code will not change > behavior.) > To set your own handler, call: > EM::set_runtime_error_hook { > # this block will be called whenever user code throws a > RuntimeError. > }Just catching RuntimeError isn''t much use to me. If I make a programming mistake then it may raise, for example, a NoMethodError (which is not a subclass of RuntimeError). I would like to catch every Exception, or at least every StandardError.> (And it was my mistake not to realize that''s what > you were using. I''d forgotten about that class. EM''s HTTP client > doesn''t use it. Maybe it was intended for SIP.)Maybe. I''m actually using it as a noddy HTTP server for a proof-of-concept project. I''m sure it would be possible to integrate either Webrick or Mongrel (I know an "evented" version of Mongrel was made as part of the Swiftiply project), but I didn''t fancy integrating it myself.> EM tries (not with complete consistency) to wrap user-code exceptions > *by default* rather than letting them kill your program, which is the > default most programmers would expect. The reasoning behind this is > that EM is used to create server programs that can and will run for > months without being actively monitored or managed. So you generally > don''t want them to just crash in the middle of a run.That sounds like a very reasonable goal. But now I understand a bit more about what''s going on, I don''t think the complexity of set_runtime_error_hook is needed. I believe the simplest solution would be to change the Connection class so that: 1. It catches all exceptions which occur in receive_data 2. It calls receive_error($!); close_connection (as LineAndTextProtocol does) 3. The default implementation of receive_error would just print the exception and its backtrace to $stderr Then if programmers want to silence these errors or redirect them to a log file, all they need to do is override receive_error. This isn''t strictly a backwards compatible change, but only in the sense that programs which used to crash before won''t crash now :-) Just an idea. Regards, Brian.
On Sat, Oct 06, 2007 at 05:34:17PM +0100, Brian Candler wrote:> I believe the simplest solution would be to change the Connection class so > that: > > 1. It catches all exceptions which occur in receive_data > > 2. It calls receive_error($!); close_connection > (as LineAndTextProtocol does) > > 3. The default implementation of receive_error would just print the > exception and its backtrace to $stderrOr more flexibly: 2. It calls receive_error($!) 3. The default implementation of receive_error would just print the exception and its backtrace to $stderr, then call close_connection
On 10/6/07, Brian Candler <B.Candler at pobox.com> wrote: Maybe. I''m actually using it as a noddy HTTP server for a proof-of-concept> project. I''m sure it would be possible to integrate either Webrick or > Mongrel (I know an "evented" version of Mongrel was made as part of the > Swiftiply project), but I didn''t fancy integrating it myself.Which concept are you trying to prove? :-). If you want to illustrate higher performance and scalability, you might look at the already-existing HTTP server in the EM distro. (It''s a separate gem from EM itself.) This code is solid, fast, and scalable, and has been running trouble-free in production for quite some time. If you want to prove writing your own protocol handler for HTTP, you might look at the LineText2 protocol handler. Originally written to implement stomp, this handler is more up-to-date than the one you''re using. It''s also used in the new SMTP client and server in the EM package, which have been working beautifully. believe the simplest solution would be to change the Connection class so> that: > > 1. It catches all exceptions which occur in receive_data > > 2. It calls receive_error($!); close_connection > (as LineAndTextProtocol does) > > 3. The default implementation of receive_error would just print the > exception and its backtrace to $stderr > > Then if programmers want to silence these errors or redirect them to a log > file, all they need to do is override receive_error. > > This isn''t strictly a backwards compatible change, but only in the sense > that programs which used to crash before won''t crash now :-) > > Just an idea.Not a bad idea. I''ll give it a try the next chance I get. -------------- next part -------------- An HTML attachment was scrubbed... URL: http://rubyforge.org/pipermail/eventmachine-talk/attachments/20071006/8112c19a/attachment.html
> you might look at the already-existing HTTP > server in the EM distro. (It''s a separate gem from EM itself.) This code is > solid, fast, and scalable, and has been running trouble-free in production > for quite some time.Thank you - I didn''t even know that existed. There''s little documentation in the gem, but I''ll try to work out how to use it. By the way, the README for EM says: Homepage:: http://rubyeventmachine.com However the navigation links on that page appear to be broken. I thought it might be because I was using Firefox, but looking at the HTML source I see links like: <li><a href="#" title="Downloads"><span>Downloads</span></a></li> Regards, Brian.
On 10/6/07, Brian Candler <B.Candler at pobox.com> wrote:> > > you might look at the already-existing HTTP > > server in the EM distro. (It''s a separate gem from EM itself.) This code > is > > solid, fast, and scalable, and has been running trouble-free in > production > > for quite some time. > > Thank you - I didn''t even know that existed. There''s little documentation > in > the gem, but I''ll try to work out how to use it.If you get stuck, I''ll send you some sample code. There''s a complete RESTful web framework based on the module which I really should publish one of these days. By the way, the README for EM says:> > Homepage:: http://rubyeventmachine.com > > However the navigation links on that page appear to be broken. I thought > it > might be because I was using Firefox, but looking at the HTML source I see > links like:It''s under construction. Soon! -------------- next part -------------- An HTML attachment was scrubbed... URL: http://rubyforge.org/pipermail/eventmachine-talk/attachments/20071006/3c276340/attachment.html
On Sat, Oct 06, 2007 at 08:59:30PM +0100, Brian Candler wrote:> > you might look at the already-existing HTTP > > server in the EM distro. (It''s a separate gem from EM itself.) This code is > > solid, fast, and scalable, and has been running trouble-free in production > > for quite some time. > > Thank you - I didn''t even know that existed. There''s little documentation in > the gem, but I''ll try to work out how to use it.Going through test/app.rb and some of http.cpp, this is what I''ve managed to work out: ------- 8< ---------------------------------------------------- require ''rubygems'' require ''eventmachine'' require ''eventmachine_httpserver'' class MyConnection < EventMachine::Connection include EventMachine::HttpServer def initialize(*args) super end def process_http_request(*args) $stderr.puts "ENV=#{ENV.inspect}" $stderr.puts self.inspect send_data "HTTP/1.0 200 OK\r\nX-Wibble: Foo\r\n\r\n" end end EventMachine::run do EventMachine::start_server(''127.0.0.1'', 8086, MyConnection) end ------- 8< ---------------------------------------------------- As far as I can tell, the received request alters the state of the connection object itself, by setting instance variables within it, and also modifies the process'' global environment (ENV). (*) For a GET and POST request respectively I see: #<MyConnection:0xb78a5c20 @http_if_none_match=nil, @http_protocol="HTTP/1.0", @http_request_uri="/", @http_cookie=nil, @http_headers="\000", @http_path_info="/", @http_request_method="GET", @http_post_content=nil, @http_content_type=nil, @signature="388319286b6e3a6348349e60c0448ce63", @http______conn=136156248, @http_query_string=nil> #<MyConnection:0xb78a5c20 @http_if_none_match=nil, @http_protocol="HTTP/1.0", @http_request_uri="/", @http_cookie=nil, @http_headers="Content-Length: 5\000\000", @http_path_info="/", @http_request_method="POST", @http_post_content="abc\r\n", @http_content_type=nil, @signature="388319286b6e3a634834_____conn=136156248, @http_query_string=nil> Am I driving this library the right way? (Generating responses with HttpResponse is a separate matter, but as that''s written in ruby and reasonably well documented, that shouldn''t be a problem) Thanks, Brian. (*) Arguably it might be cleaner to pass in a request, rather than modify the connection state. This is because RFC2616 explicitly allows pipelining - that is, you can receive multiple overlapping requests and then send the corresponding responses later. To handle this would require copying state out of the HttpConnection object before receiving the next request. Or I guess you could #dup the HttpConnection, as long as that wouldn''t have any unforeseen side effects.