shotgun icon indicating copy to clipboard operation
shotgun copied to clipboard

Shotgun visits every mapping in config.ru regardless the request

Open Darkside73 opened this issue 10 years ago • 10 comments

ENV

  ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-linux]
  shotgun-0.9.1
  rack-1.6.1
  thin-1.6.3

Simple config.ru

  map '/' do
    p "root"
    run Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["root"]]}
  end

  map "/assets" do
    p "assets"
    run Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["assets"]]}
  end

  map "/admin" do
    p "admin"
    run Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["admin"]]}
  end

Run shotgun server

  $ shotgun

Hit the server:

  $ curl http://localhost:9393/
  root

Server log output

  == Shotgun/Thin on http://127.0.0.1:9393/
  Thin web server (v1.6.3 codename Protein Powder)
  Maximum connections set to 1024
  Listening on 127.0.0.1:9393, CTRL+C to stop
  "root"
  "assets"
  "admin"
  127.0.0.1 - - [15/Jun/2015:18:52:52 +0300] "GET / HTTP/1.1" 200 - 0.0008

This is really nasty when I use shotgun with sinatra and sprockets: every asset request hits whole application and slow down page loading a lot

Is it by design?

Darkside73 avatar Jun 15 '15 16:06 Darkside73

+1 also seeing this

revett avatar Apr 27 '16 11:04 revett

Do you see the same behavior when running the server without Shotgun?

djanowski avatar Apr 27 '16 14:04 djanowski

No, I don't

Darkside73 avatar Apr 27 '16 18:04 Darkside73

@djanowski Nope

revett avatar Apr 28 '16 09:04 revett

Locally using rackup page responses max out at:

~ 300 ms

Using shotgun they all take:

2.5 secs or more

We currently have 16 routes mapped to controllers using the map method.

revett avatar Apr 28 '16 12:04 revett

@djanowski - any ideas on this? I don't mind taking a look.

revett avatar May 03 '16 10:05 revett

Ping

revett avatar May 25 '16 15:05 revett

Same here. I was trying sprockets with sinatra and couldn't figure out until now what was the problem. It was compiling assets for each request. Then I used simple rackup and worked normally.

Shotgun with sprockets is currently impossible. Any update?

AnwarShah avatar Dec 11 '17 14:12 AnwarShah

I don't think this is an issue with Shotgun itself. This must just be how Rack maps work - loading all the Rack apps when config.ru executes (which Shotgun does after forking for each request).

➜ rackup -s webrick
"root"
"assets"
"admin"
[2020-10-03 13:49:41] INFO  WEBrick 1.6.1
[2020-10-03 13:49:41] INFO  ruby 2.7.1 (2020-03-31) [x86_64-linux]
[2020-10-03 13:49:41] INFO  WEBrick::HTTPServer#start: pid=188442 port=9292

I suppose Shotgun could be made to (optionally?) load the config.ru before forking, but that could have unintended consequences to do with the forking - at GreenSync, unrelated to Shotgun, we've found this with Sequel Postgres database connections not carrying over to a fork. Or maybe it could even clone the Rack app before each request. But actually, either approach would defeat the code-reloading purpose of Shotgun!

Perhaps Shotgun could interpret map itself somehow, to not execute branches that don't match the request?

ZimbiX avatar Oct 03 '20 04:10 ZimbiX

I've just had a fiddle with making Rack::Builder#map lazy, and it works! There would probably be some side-effects from laziness, so I wouldn't make it default. And I reckon what I have wouldn't be working for nested map yet.

# config.ru:

puts 'config.ru evaluation started due to request'

module LazyRackMapForShotgun
  def self.call(path:, app:, run:)
    puts "LazyRackMapForShotgun: #{path}"
    shotgun_request_path?(path) ? app : null_app(run)
  end

  private

  def self.shotgun_request_path?(path)
    # TODO: Make this work for nested map
    path == shotgun_env['PATH_INFO']
  end

  def self.shotgun_env
    ObjectSpace
      .each_object(Shotgun::Loader)
      .map { |loader| loader.instance_variable_get(:@env) }
      .compact[0]
  end

  def self.null_app(run)
    @@missing_lazy_map_app ||= run.call(
      # This shouldn't get called, but just in case:
      proc do |env|
        [
          500,
          { "Content-Type" => "text/html" },
          ["Could not find app for Shotgun lazy Rack::Builder#map monkeypatch"],
        ]
      end
    )
  end
end

class ::Rack::Builder
  alias map_orig map
  def map(path, &block)
    map_orig(path, &LazyRackMapForShotgun.call(path: path, app: block, run: method(:run)))
  end
end

map '/' do
  puts "root"
  run Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["root"]]}
end

map "/assets" do
  puts "assets"
  run Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["assets"]]}
end

map "/admin" do
  puts "admin"
  run Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["admin"]]}
end

puts 'config.ru evaluated'

Running with the monkey-patch disabled then enabled:

➜ shotgun -p 3000 --server webrick
== Shotgun/WEBrick on http://127.0.0.1:3000/
[2020-10-03 15:27:37] INFO  WEBrick 1.6.0
[2020-10-03 15:27:37] INFO  ruby 2.7.1 (2020-03-31) [x86_64-linux]
[2020-10-03 15:27:37] INFO  WEBrick::HTTPServer#start: pid=213572 port=3000
config.ru evaluation started due to request
config.ru evaluated
root
assets
admin
127.0.0.1 - - [03/Oct/2020:15:27:38 +1000] "GET /assets HTTP/1.1" 200 - 0.0005
^C

➜ shotgun -p 3000 --server webrick
== Shotgun/WEBrick on http://127.0.0.1:3000/
[2020-10-03 15:28:20] INFO  WEBrick 1.6.0
[2020-10-03 15:28:20] INFO  ruby 2.7.1 (2020-03-31) [x86_64-linux]
[2020-10-03 15:28:20] INFO  WEBrick::HTTPServer#start: pid=213874 port=3000
config.ru evaluation started due to request
LazyRackMapForShotgun: /
LazyRackMapForShotgun: /assets
LazyRackMapForShotgun: /admin
config.ru evaluated
assets
127.0.0.1 - - [03/Oct/2020:15:28:22 +1000] "GET /assets HTTP/1.1" 200 - 0.0004

ZimbiX avatar Oct 03 '20 05:10 ZimbiX