postrank-labs / goliath

Goliath is a non-blocking Ruby web server framework

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Application.run! at_exit issue

Harrison-Uhl opened this issue · comments

I'm new to Goliath, but not to using Ruby for evented programming.

I wanted to setup multiple socket servers all under Goliath, but tripped over the auto run -- after my multi server exited, the auto run still wanted to wake up and play.

I was working to organize my patches for submission, and looked over old Goliath issues. I saw that issue # 55 also raised the question of blocking the auto run.

Issue # 55 was closed with the observation that requiring "goliath/api" instead of "goliath" would avoid loading Goliath::Application, and in turn avoid the auto run.

Unfortunately, Goliath::API now requires "goliath/goliath" which in turn requires "goliath/application". This makes avoiding Goliath::Application much harder.

My use case of running multiple servers is in a Windows application where I also want to run a Windows GUI application, and therefore a Windows GUI event loop. To avoid Windows inter-process communications, I simply wanted Goliath (and with it, EventMachine) to run in a separate thread.

To disable the auto run, in my app file I added a patch to Goliath::Application:

 module Goliath
  class Application  ### don't want app to auto start.  Want to start it in another thread, and possibly multiple servers at once!!!
    def self.app_file; '' ; end # this blocks auto start based on file name & class extending Goliath::API
  end  
  end 

(I'm not sure if this approach has side effects, but I wasn't ready to start changing the actual gem code -- and it did what I needed.)

This blocked the auto run. Then to make running my app easier I also added:

  class GoliathAPI < Goliath::API ### add some helper methods to Goliath::API (by extending it)
    def options_parser(optionParser, options) # options given to API will override any given to Runner
    options.merge!(@opts);   ## yes I know, it mutates a passed in arg, but it is just a first go
    end 
    def mkRunner ### make a runner for this GoliathAPI class (extension)  (code from: Goliath::Application)
      klass= self.class 
          runner = Goliath::Runner.new(ARGV, self)
          runner.app = Goliath::Rack::Builder.build(klass, self)
          runner.load_plugins(klass.plugins)
      runner
  end
    def run! ## helper method to run a single server e.g. MyGoliathAPI.new(optionsHash).run!  
         mkRunner.run ##   add this at end of an app file  -- eliminates need for a lot of what is in Goliath::Application
    end 
    def mkServer(log);  mkRunner.mkServer(log); end  ## Make a server for this API (see: MultiServer)
  end  #########################################

Note the above also redefined options_parser

Then to run my app, to the end of the app file (in which GoliathAPI is extended) I simply added:

 MyGoliathAPI.new({ options hash here }).run! 

For example, to start the app in a new Thread :

Thread.new { MyGoliathAPI.new({ :port => 80}).run! }

With it being this easy to run an Goliath app, is the auto run really necessary?

For those who like it the old way, after removing the at_exit code from Application, loading the following will restore the auto run:

In a file called GoliathAutoStart.rb (potentially to be included in the gem)

  require "goliath"

  module Goliath

  class API # patch for Auto Start

    class << self  ### from start of api.rb
      # Catches the userland class which inherits the Goliath API
      #
      # In case of further subclassing, the very last class encountered is used.
      def inherited(subclass)
       Goliath::Application.app_class = subclass.name if defined?(Goliath::Application)
      end
    end
  end
  ## from end of application.rb 
  at_exit do
  ##            vvvv block rest of  filtering as this demo code is not placed in the gem directory
  if $!.nil?   ## && $0 == Goliath::Application.app_file
        puts "at_exit Application.run!"
      Application.run!
    end
  end

end

So a user who wants auto start simply requires "GoliathAutoStart" instead of "goliath"

If this is of interest to the community, I'll also follow with a separate issue with the multiple socket server example (it works, but when logging is turned on, it crashes Ruby.)

-Harrison

Hmm.. Did you see the custom_server.rb example? Which wouldn't have this issue...

https://github.com/postrank-labs/goliath/blob/master/examples/custom_server.rb

OK, I checked it out further and I was getting goliath and goliath/goliath mixed up in my editor.

Yes custom_server avoids the auto run. But this seems a long way around to avoid something (auto run) that should just be a little extra to add to code that doesn't auto run -- see example above: MyGoliathAPI.new({ :port => 80}).run!

Please consider adding a comment at the top of application.rb explaining how to avoid the auto run by not utilizing Goliath::Application -- for example adding a comment that custom_server provides a demonstration of avoiding the auto run.

Also please consider adding the helper methods given in the code block starting with:
class GoliathAPI < Goliath::API .

@Harrison-Uhl, could you check if #217 solves your problem?

For my application, I've added a class EventMachineManager (EmMgr) which I use to hold the definitions of one or more servers -- whether they are presently running or not. (EmMgr uses some code from Goliath Application & API, allowing them to be left out, thereby avoiding the present auto start. One of my design requirements is the ability to serve on multi IP's and/or ports.)

With EmMgr, a simple Rack application can be run as (a file containing just the one line that follows):

EmMgr.RackServer(serverParmsHash) { |env| [200, {}, ["My response text"]]

The EmMgr looks at the files of: 1) the caller of RackServer, and 2) $0, and if they match, the server is auto started, if they don't match, then the server is just added to the set of defined servers being managed.

By this approach, I can quickly test an individual server (just invoke its source file), or I can have a higher level app (e.g. a router/gateway manager) direct the individual 'server' where it fits in the over all picture. (And since $0 differs, there is no auto start.)

Presently, in my code, RackServer actually does the starting (if auto start.) However, It might be more useful if RackServer just setup the at_exit (conditionally if the names match) as this would allow several servers to be defined in the source file, and then at exit, all would be started (for the immediate start mode.)

This approach is flexible and unobtrusive for my needs. I haven't examined the code for issue 217, but the comments make it look like it requires more explicit management than as per the above approach.

While I'm happy to contribute my code back, it is presently combined with a number of other changes, many still in progress. (I'm trying to reduce object creation churn caused by the comings and goings of the Rack response array, and also reduce the number of Hash lookups required for my application. In the process, my fork mostly removes Rack-ness from the core of Goliath, and then adds it back as a Rack compatible Gateway class. Presently, I'm adapting a previous version of my app server and the code is very much in flux.)

I hope that this helps. As per the above, I've solved my problem and moved on. While I do urge you to consider ways to make the auto start easier to avoid, as far as I'm concerned, you are welcome to close this issue if you would like.

Best regards
Harrison