Multiple applications each bundle their own version of Rack

314 views
Skip to first unread message

Mislav

unread,
Jan 2, 2010, 3:39:26 PM1/2/10
to Phusion Passenger Discussions
I've read that Passenger 2.2.8 doesn't bundle Rack anymore. However,
it loads the latest stable Rack from rubygems.

Rails 3 suggest deploying with Bundler. Web applications using Bundler
have all their dependencies bundled, including Rack. Thus, they don't
even have to load Rubygems. Still, they might have different Rack
versions. Rails 2.3.5 application will bundle different Rack than
Rails 3.0.

So what I'm asking is can multiple applications be deployed on the
same server, each bundling their own version of Rack?

If not—what's the best approach to deploying apps that require edge
Rack (like Rails 3.0). Do we constantly need to build edge Rack gem
and install it on the system we're deploying to, so that Passenger can
use it? Wouldn't that mean edge Rack will be used for all web apps on
the same server, even those which don't require it?

I'm not familiar with how Passenger works and why does it have Rack as
dependency. I know I can edit the PassengerRuby directive to point to
a wrapper script that executes Ruby while loading the app-specific
vendor/gems/environment.rb file generated by Bundler, which will
probably make Passenger use the Rack version bundled by this app, but
this locks Passenger to only running this single app. It would be
ideal if PassengerRuby wouldn't have to be edited, but that still each
webapp starts as a clean ruby process and then it is determined if it
bundles its own Rack or, if not, Rubygems—and from them Rack—have to
be loaded.

Hongli Lai

unread,
Jan 2, 2010, 6:04:26 PM1/2/10
to phusion-passenger

It works like this: Phusion Passenger has two different loaders, a
Rails one and a Rack one. If config.ru exists then the app is detected
as a Rack app and the Rack loader will kick in. Otherwise, if
config/environment.rb exists then the app is detected as a Rails app
and the Rails loader will kick in. config.ru has precedence over
config/environment.rb.

The Rack loader will run 'require rack' before evaluating config.ru,
since config.ru assumes a Rack::Builder DSL environment, and
Rack::Builder is a part of the Rack library. Because of this Phusion
Passenger *has* to have a dependency on Rack. The code in question is
as follows:

rackup_code = ::File.read("config.ru")
eval("Rack::Builder.new {( #{rackup_code}\n )}.to_app",
TOPLEVEL_BINDING, "config.ru")

As you can see, there's no way to get rid of the 'require rack' part
because we depend on Rack::Builder before evaluating config.ru. Thin
suffers from the same problem:

[hongli@Minato lib (master)]$ rak "'rack'"
thin.rb
39|require 'rack'

Thin's Rackup file loading code looks very similar to Phusion
Passenger's (controller.rb #load_rackup_config):

rackup_code = File.read(@options[:rackup])
eval("Rack::Builder.new {( #{rackup_code}\n )}.to_app",
TOPLEVEL_BINDING, @options[:rackup])

I suspect that Unicorn and probably every Rack web server out there
suffers from the problem: Rack must be loaded in order to evaluate
config.ru, but further down the executation timeline config.ru
actually depends on a different Rack version.

The Rails loader does not suffer from this problem because it does not
run 'require rack', but I haven't tested the Rails loader against
Rails 3 yet.

Are you running Rails 3 with or without config.ru?

There are three solutions that I can think of:
- Making the Rails loader work with Rails 3. The implication of this
is that if you want to use a custom config.ru for your Rails 3 app
then you must install the required Rack version as a gem.
- Introduce a "RackVersion" or "RackPath" configuration option, so
that Phusion Passenger knows where to load Rack from. I really detest
this solution and would prefer not to go through this route.
- Seperate Rack::Builder into a separate library. However, this would
imply that config.ru will have to run 'require rack' if it wants to
use any other Rack code.

What do you think?

--
Phusion | The Computer Science Company

Web: http://www.phusion.nl/
E-mail: in...@phusion.nl
Chamber of commerce no: 08173483 (The Netherlands)

Hongli Lai

unread,
Jan 7, 2010, 9:07:27 AM1/7/10
to phusion-passenger
On Sun, Jan 3, 2010 at 12:04 AM, Hongli Lai <hon...@phusion.nl> wrote:
> There are three solutions that I can think of:
> - Making the Rails loader work with Rails 3. The implication of this
> is that if you want to use a custom config.ru for your Rails 3 app
> then you must install the required Rack version as a gem.
> - Introduce a "RackVersion" or "RackPath" configuration option, so
> that Phusion Passenger knows where to load Rack from. I really detest
> this solution and would prefer not to go through this route.
> - Seperate Rack::Builder into a separate library. However, this would
> imply that config.ru will have to run 'require rack' if it wants to
> use any other Rack code.
>
> What do you think?

It looks like Phusion Passenger's Rails loader is compatible with Rails 3.

Mislav

unread,
Jan 7, 2010, 10:43:13 AM1/7/10
to Phusion Passenger Discussions
On Jan 3, 12:04 am, Hongli Lai <hon...@phusion.nl> wrote:
> Are you running Rails 3 with or without config.ru?

With. The Rails 3 app generator makes one; I didn't think of removing
it.

> There are three solutions that I can think of:
> - Making the Rails loader work with Rails 3. The implication of this
> is that if you want to use a custom config.ru for your Rails 3 app
> then you must install the required Rack version as a gem.

Does Passenger do some optimizations when detecting Rails apps as
opposed to Rack apps? If not, I'd rather keep using Rack loader for
Rails 3 apps so that eventually all Ruby webapps that I might run are
spawned the same way. It feels right. And as for the issue we're
discussing here, a solution should be something else that doesn't
special-case Rails apps.

> - Introduce a "RackVersion" or "RackPath" configuration option, so
> that Phusion Passenger knows where to load Rack from. I really detest
> this solution and would prefer not to go through this route.

I wanted to propose a new directive, but much more generic than
RackPath. When I run scripts in an app that uses Bundler, I'm doing
this:

$ ruby -r vendor/gems/environment path/to/script.rb

Here, the "environment.rb" is a Bundler-generated file that bootstraps
the environment (LOAD_PATH, to be exact). So, I'm telling the Ruby
interpreter "run this script, but load another script first". As far
as I know, there's no way of telling Passenger to do this without
setting PassengerRuby to a wrapper script, which is not a good-enough
solution because it's per-server, not per-app. If there was a per-app
directive that makes Passenger load a script *before* it requires Rack
and loads config.ru, we could use it for Bundler.

<VirtualHost *:80>
ServerName example.com
DocumentRoot "/my/app/public"
RackEnv development
RubyOpt "-r /my/app/vendor/gems/environment"
</VirtualHost>

I tried using RUBYOPT, just for kicks:

SetEnv RUBYOPT "-r /my/app/vendor/gems/environment"

Didn't seem to work. Rack is still loaded from Rubygems.

> - Seperate Rack::Builder into a separate library. However, this would
> imply that config.ru will have to run 'require rack' if it wants to
> use any other Rack code.

Not sure. Then we're back to vendoring Rack (at least one part of it).

Hongli Lai

unread,
Jan 7, 2010, 11:18:54 AM1/7/10
to phusion-passenger
On Thu, Jan 7, 2010 at 4:43 PM, Mislav <mislav....@gmail.com> wrote:
> Does Passenger do some optimizations when detecting Rails apps as
> opposed to Rack apps? If not, I'd rather keep using Rack loader for
> Rails 3 apps so that eventually all Ruby webapps that I might run are
> spawned the same way. It feels right. And as for the issue we're
> discussing here, a solution should be something else that doesn't
> special-case Rails apps.

Yes it does. All the Ruby Enterprise Edition memory saving and reduced
spawn time optimizations are only possible with the Rails loader. The
exact mechanisms are described in various documents:
http://www.modrails.com/documentation/Users%20guide%20Apache.html#spawning_methods_explained
http://www.modrails.com/documentation/Architectural%20overview.html

By using the Rack loader you will lose all of those advantages. This
is one of the main reasons why the Rails loader still exist; it's not
just for compatibility with Rails <= 2.1.


> I wanted to propose a new directive, but much more generic than
> RackPath. When I run scripts in an app that uses Bundler, I'm doing
> this:
>
>    $ ruby -r vendor/gems/environment path/to/script.rb
>
> Here, the "environment.rb" is a Bundler-generated file that bootstraps
> the environment (LOAD_PATH, to be exact). So, I'm telling the Ruby
> interpreter "run this script, but load another script first".  As far
> as I know, there's no way of telling Passenger to do this without
> setting PassengerRuby to a wrapper script, which is not a good-enough
> solution because it's per-server, not per-app. If there was a per-app
> directive that makes Passenger load a script *before* it requires Rack
> and loads config.ru, we could use it for Bundler.

Sounds like a good idea. Phusion Passenger can just load
vendor/gems/environments if it exists, without any config options.

Hongli Lai

unread,
Jan 7, 2010, 11:27:22 AM1/7/10
to phusion-passenger
On Thu, Jan 7, 2010 at 4:43 PM, Mislav <mislav....@gmail.com> wrote:
> With. The Rails 3 app generator makes one; I didn't think of removing
> it.

Oops, looks like I made a mistake; the Rails loader doesn't work with
Rails 3 after all. We'll release a fix soon.

Mislav

unread,
Jan 7, 2010, 1:40:10 PM1/7/10
to Phusion Passenger Discussions
On Jan 7, 5:18 pm, Hongli Lai <hon...@phusion.nl> wrote:
>
> Sounds like a good idea. Phusion Passenger can just load
> vendor/gems/environments if it exists, without any config options.

It might be a good idea, but you have to keep in mind that Bundler
users can change that location to something else. Popular location for
Rails 2 apps is "vendor/bundler_gems/environment", for instance. Then
again, it might be something completely else, so Passenger has to
expose some directive for configuring it -- a directive which says
"run this script before anything else". Then we can use Bundler or any
other library/tool for managing LOAD_PATH and we don't need Rubygems +
system gems anymore.

Hongli Lai

unread,
Jan 7, 2010, 2:17:30 PM1/7/10
to phusion-passenger
On Thu, Jan 7, 2010 at 7:40 PM, Mislav <mislav....@gmail.com> wrote:
> It might be a good idea, but you have to keep in mind that Bundler
> users can change that location to something else. Popular location for
> Rails 2 apps is "vendor/bundler_gems/environment", for instance. Then
> again, it might be something completely else, so Passenger has to
> expose some directive for configuring it -- a directive which says
> "run this script before anything else". Then we can use Bundler or any
> other library/tool for managing LOAD_PATH and we don't need Rubygems +
> system gems anymore.

It looks like the bundler will become the de facto standard bundling
tool. I'm thinking about full integration: parse the Gemfile and
figure out where the bundle location is, and load the gems from there
before booting the app. Let's keep things DRY.

vendor/bundler_gems was the old location. The latest bundler README
does not mention changing the location for Rails.

Mislav

unread,
Jan 7, 2010, 3:13:34 PM1/7/10
to Phusion Passenger Discussions
On Jan 7, 8:17 pm, Hongli Lai <hon...@phusion.nl> wrote:
>
> It looks like the bundler will become the de facto standard bundling
> tool. I'm thinking about full integration: parse the Gemfile and
> figure out where the bundle location is, and load the gems from there
> before booting the app. Let's keep things DRY.

Then you need to vendor Bundler? Or you will eval Gemfile in a scope
where only `bundle_path` is defined (to capture the path)?

> vendor/bundler_gems was the old location. The latest bundler README
> does not mention changing the location for Rails.

People are using "vendor/bundler_gems" for Rails 2 because "vendor/
gems" is a special path where Rails 2 initializer expected to find
unpacked gems in. If Bundler writes to "vendor/gems", Rails 2 apps
complain on boot (but otherwise work).

Mislav

unread,
Jan 7, 2010, 3:15:50 PM1/7/10
to Phusion Passenger Discussions
Another question: any idea why my "SetEnv RUBYOPT" hack didn't work?

It's great that you intend to support Bundler out-of-the-box, but I'd
still like the feature with which I can set a script to load
immediately after the ruby interpreter has started. This is to support
other LOAD_PATH tools alternative to Bundler.

Hongli Lai

unread,
Jan 7, 2010, 4:34:31 PM1/7/10
to phusion-passenger
On Thu, Jan 7, 2010 at 9:15 PM, Mislav <mislav....@gmail.com> wrote:
> Another question: any idea why my "SetEnv RUBYOPT" hack didn't work?

SetEnv environment data is passed to Phusion Passenger during a
request. But the Ruby spawn server is started during Apache startup,
before the first request comes in, therefore there's no good way to
pass SetEnv data like that.

> It's great that you intend to support Bundler out-of-the-box, but I'd
> still like the feature with which I can set a script to load
> immediately after the ruby interpreter has started. This is to support
> other LOAD_PATH tools alternative to Bundler.

At this time it's already possible by writing a Ruby wrapper script.

Hongli Lai

unread,
Jan 7, 2010, 4:35:48 PM1/7/10
to phusion-passenger
On Thu, Jan 7, 2010 at 9:13 PM, Mislav <mislav....@gmail.com> wrote:
> Then you need to vendor Bundler? Or you will eval Gemfile in a scope
> where only `bundle_path` is defined (to capture the path)?

I'm thinking about depending on bundler, or maybe taking over its DSL
implementation. Don't need more than that.

Mislav Marohnić

unread,
Jan 7, 2010, 4:41:16 PM1/7/10
to phusion-...@googlegroups.com
On Thu, Jan 7, 2010 at 22:34, Hongli Lai <hon...@phusion.nl> wrote:

At this time it's already possible by writing a Ruby wrapper script.

But as I said, this can be only be accomplished per-server, not per-app, making it a severly limited solution.

Hongli Lai

unread,
Jan 7, 2010, 7:16:58 PM1/7/10
to phusion-passenger
On Thu, Jan 7, 2010 at 10:41 PM, Mislav Marohnić
<mislav....@gmail.com> wrote:
> But as I said, this can be only be accomplished per-server, not per-app,
> making it a severly limited solution.

I will take note of this. Seems like something we will want to tackle
some time in the future.

Anuj Dutta

unread,
Jan 11, 2010, 5:56:56 AM1/11/10
to phusion-...@googlegroups.com

2010/1/8 Hongli Lai <hon...@phusion.nl>
On Thu, Jan 7, 2010 at 10:41 PM, Mislav Marohnić
> But as I said, this can be only be accomplished per-server, not per-app,
> making it a severly limited solution.

I will take note of this. Seems like something we will want to tackle
some time in the future.



Hello there,

I have been following this particular thread with interest and going back to Mislav's original question:


"So what I'm asking is can multiple applications be deployed on the same server, each bundling their own version of Rack?"

To which Hongli replied:  that since a rack loader basically 'requires rack' before evaluating config.ru so it is not possible ? Is that correct ?

and it can be fixed with Passenger + Bundler as the particular rack version can be bundled up and used by Passenger when needed to spawn a rack app?

Does that make sense or have I got the wrong end of the stick?


Also, In Passenger 2.2.8 Rack has been unvendored? So, is it loaded from gems now?

Sorry, too many questions in one post.

Thanks.

Anuj

 
--
Phusion | The Computer Science Company

Web: http://www.phusion.nl/
E-mail
: in...@phusion.nl
Chamber of commerce no: 08173483 (The Netherlands)

--
You received this message because you are subscribed to the Google Groups "Phusion Passenger Discussions" group.
To post to this group, send email to phusion-...@googlegroups.com.
To unsubscribe from this group, send email to phusion-passen...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/phusion-passenger?hl=en.







 



--
Anuj DUTTA

Hongli Lai

unread,
Jan 11, 2010, 6:36:57 AM1/11/10
to phusion-passenger
On Mon, Jan 11, 2010 at 11:56 AM, Anuj Dutta <dutta...@googlemail.com> wrote:
> Hello there,
>
> I have been following this particular thread with interest and going back to
> Mislav's original question:
>
> "So what I'm asking is can multiple applications be deployed on the same
> server, each bundling their own version of Rack?"
>
> To which Hongli replied:  that since a rack loader basically 'requires rack'
> before evaluating config.ru so it is not possible ? Is that correct ?

For Phusion Passenger 2.2, this is correct. We'll look into a solution
for Phusion Passenger 3.

But for Rails 3 specifically, it can use whatever bundled Rack version
it wants; in Phusion Passenger 2.2.9 we fixed the Rails loader to be
Rails 3 compatible, and the Rails loader does not 'require rack'.


> and it can be fixed with Passenger + Bundler as the particular rack version
> can be bundled up and used by Passenger when needed to spawn a rack app?

That is one of the possible solutions.


> Also, In Passenger 2.2.8 Rack has been unvendored? So, is it loaded from
> gems now?

This is correct.

With kind regards,
Hongli Lai

Mislav Marohnić

unread,
Jan 11, 2010, 6:38:52 AM1/11/10
to phusion-...@googlegroups.com
On Mon, Jan 11, 2010 at 11:56, Anuj Dutta <dutta...@googlemail.com> wrote:

and it can be fixed with Passenger + Bundler as the particular rack version can be bundled up and used by Passenger when needed to spawn a rack app?

When Passenger supports Bundler, boot process might look like this:

Scenario A: no bundled gems
  1. Gemfile doesn't exist
  2. require "rubygems"
  3. require "rack"   # Rack is loaded from RubyGems
  4. parse "config.ru"
Scenario B: there are bundled gems
  1. Gemfile is found
  2. require "vendor/gems/environment"
  3. require "rack"   # bundled Rack is loaded
  4. parse "config.ru"
Hongli gave his thumbs up for Bundler support, but I was proposing an even more general solution: to be able to configure that a ruby script gets loaded before everything else in a single application.

Scenario C: ruby script configured
  1. config directive detected: PassengerBootstrapScript "path/to/script.rb"
  2. require "path/to/script.rb"  # the script sets up load paths
  3. require "rack" 
  4. parse "config.ru"

Also, In Passenger 2.2.8 Rack has been unvendored? So, is it loaded from gems now?

Yes. 

Anuj Dutta

unread,
Jan 11, 2010, 6:50:42 AM1/11/10
to phusion-...@googlegroups.com


2010/1/11 Mislav Marohnić <mislav....@gmail.com>

I can see why you would like to go for Scenario C. Correct me if I am wrong:

  1. config directive not detected.
  2. Load from rubygems as a default.
  3. require "rack" 
  4. parse "config.ru"
or would it be mandatory to define a directive and a script. Right?

Anuj


 
Also, In Passenger 2.2.8 Rack has been unvendored? So, is it loaded from gems now?

Yes. 

--
You received this message because you are subscribed to the Google Groups "Phusion Passenger Discussions" group.
To post to this group, send email to phusion-...@googlegroups.com.
To unsubscribe from this group, send email to phusion-passen...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/phusion-passenger?hl=en.




--
Anuj DUTTA

Mislav Marohnić

unread,
Jan 11, 2010, 7:27:12 AM1/11/10
to phusion-...@googlegroups.com
On Mon, Jan 11, 2010 at 12:50, Anuj Dutta <dutta...@googlemail.com> wrote:

I can see why you would like to go for Scenario C. Correct me if I am wrong:

  1. config directive not detected.
  2. Load from rubygems as a default.
  3. require "rack" 
  4. parse "config.ru"
This is present functionality. New features will not remove or alter it in any way. If there's going to be a new Apache/Nginx directive, it should be entirely optional.

Reply all
Reply to author
Forward
0 new messages