Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sprockets::FileOutsidePaths error seems to be caused by fix to #59 #96

Closed
bughit opened this issue Aug 13, 2015 · 71 comments
Closed

Sprockets::FileOutsidePaths error seems to be caused by fix to #59 #96

bughit opened this issue Aug 13, 2015 · 71 comments

Comments

@bughit
Copy link

bughit commented Aug 13, 2015

Not a 100% sure, but what seems to be happening is even though cache key generation now uses relative paths, some or all depedency references are absolute.

so if you are sharing the asset cache folder among multiple release, sprockets ends up finding the right cache entry but then tries to load dependencies from an older release absolute path (where the cache was originally constructed) and fails with Sprockets::FileOutsidePaths

@schneems
Copy link
Member

some or all dependency references are absolute.

If you're using an asset that isn't inside of the root of where you're running sprockets, an absolute path will be used to generate the cache key. Can you give me an example project that shows the problem?

@bughit
Copy link
Author

bughit commented Aug 13, 2015

I haven't yet tried in a new project but can give you repro directions:

  1. assets:clobber
  2. make a copy of your project_folder (project_copy)
  3. precompile in project_copy to generate the cache
  4. symlink project_folder assets cache to project_copy
  5. if you examine/grep the cache files you'll see absolute references to project_copy (file-digest: urls)
  6. in project_folder there is a javascript_include_tag 'jquery.sparkline'
  7. jquery.sparkline is a folder with an index that //= require ./jquery.sparkline-2.1.2
  8. javascript_include_tag 'jquery.sparkline' raises saying that the absolute path to jquery.sparkline-2.1.2.js via project_copy is no longer under a load path (all the load paths being under project_folder)

@bughit
Copy link
Author

bughit commented Aug 13, 2015

so this is actually very easy to repro with a new project

  1. create
  2. add one action that will load application.js via the layout
  3. add one js asset to app/assets/javascripts (application.js already requires tree)
  4. rails server and load the action (it should work)
  5. assets:clobber
  6. make a copy of the project
  7. assets:precompile in the copy
  8. symlink to the copy's cache/assets
  9. rails server and load the action
  10. Sprockets::FileOutsidePaths

@bughit
Copy link
Author

bughit commented Aug 13, 2015

with v3.2.0 there's no error, but only because the cache is not used due to absolute paths

with v2.12.4 there's no error and the cache is used

@schneems
Copy link
Member

There's a lot of grey area between 1 and 10. I'll attempt trying this tomorrow,

@bughit
Copy link
Author

bughit commented Aug 14, 2015

Symlink the tmp/cache in the copy to the original project? Is that what you're doing in a project?

the original project does not have the asset cache (assets:clobber in step 5)
the copy has the cache (assets:precompile in step 7)
so you need to make the copy's cache available to the original project by creating a symlink to it

@schneems
Copy link
Member

I tried that and couldn't reproduce

I'm pointing at my copy's assets and cache:

$ ls -la
total 64
drwxr-xr-x  19 richardschneeman  staff   646 Aug 14 11:56 .
drwxr-xr-x  63 richardschneeman  staff  2142 Aug 14 11:52 ..
drwxr-xr-x  12 richardschneeman  staff   408 Aug 14 11:52 .git
-rw-r--r--   1 richardschneeman  staff   474 Aug 14 11:47 .gitignore
-rw-r--r--   1 richardschneeman  staff  1476 Aug 14 11:47 Gemfile
-rw-r--r--   1 richardschneeman  staff  3851 Aug 14 11:47 Gemfile.lock
-rw-r--r--   1 richardschneeman  staff   478 Aug 14 11:47 README.rdoc
-rw-r--r--   1 richardschneeman  staff   249 Aug 14 11:47 Rakefile
drwxr-xr-x   8 richardschneeman  staff   272 Aug 14 11:47 app
drwxr-xr-x   7 richardschneeman  staff   238 Aug 14 11:47 bin
drwxr-xr-x  11 richardschneeman  staff   374 Aug 14 11:47 config
-rw-r--r--   1 richardschneeman  staff   153 Aug 14 11:47 config.ru
drwxr-xr-x   7 richardschneeman  staff   238 Aug 14 11:50 db
drwxr-xr-x   4 richardschneeman  staff   136 Aug 14 11:47 lib
drwxr-xr-x   5 richardschneeman  staff   170 Aug 14 11:49 log
lrwxr-xr-x   1 richardschneeman  staff    28 Aug 14 11:56 public -> ../sprockets-96-copy/public/
drwxr-xr-x   9 richardschneeman  staff   306 Aug 14 11:47 test
lrwxr-xr-x   1 richardschneeman  staff    25 Aug 14 11:56 tmp -> ../sprockets-96-copy/tmp/

Using sprockets 3.3.0

$ bundle list | grep sprockets
  * sprockets (3.3.0)
  * sprockets-rails (2.3.2)

It works:

$ curl localhost:3000/users/new
<!DOCTYPE html>
<html>
<head>
  <title>Sprockets96</title>
  <link rel="stylesheet" media="all" href="/assets/application-0723cb9a2dd5a514d954f70e0fe0b89f6f9f1ae3a375c182f43b5f2b57e9c869.css" data-turbolinks-track="true" />
  <script src="/assets/application-3698babcae573d7cf113fedbe38437ec3bc009e54bc20ccfe4a69d94b7a17732.js" data-turbolinks-track="true"></script>
  <meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="SYrsgjE9ckapdN6cxl5h+VZKs6JJtk7dgd/8aCkBAyi8rHRuBA2+hHFBHpU5xTOB7whZ/H6zF87l2UUWpgZ+6Q==" />
</head>
<body>

<h1>New User</h1>

<form class="new_user" id="new_user" action="/users" accept-charset="UTF-8" method="post"><input name="utf8" type="hidden" value="&#x2713;" /><input type="hidden" name="authenticity_token" value="mULGsJ5EH1pa6xTh6LTP9hgvZgP8uiHHj701/onQbpdsZF5cq3TTmILe1OgXL52OoW2MXcu/eNTru4yABtcTVg==" />

  <div class="actions">
    <input type="submit" name="commit" value="Create User" />
  </div>
</form>

<a href="/users">Back</a>


</body>
</html>

If you can repro the problem, can you wrap up the two repos you're using in another git repo and throw em' on github for me?

What are you doing IRL to cause this issue? Do you have multiple rails app deploys that are simlinked to the same asset directory is this only to show a repro case?

@bughit
Copy link
Author

bughit commented Aug 14, 2015

Don't link public/assets. The presence of precompiled assets bypasses the cache entirely I believe (right?). Also this is dev mode (on demand compilation is on)

The whole point is to have the original project use the cache generated in the copy. Also, I am linking just the tmp/cache/assets not the whole tmp (this probably does not matter)

In capistrano deploys the assets cache is shared between releases. I am dealing with a project in which on demand compilation is on in prod (that will change but not immediately ). On demand compilation tries to use shared cache entries generated by and pointing to previous releases and produces the error (this is exactly what this test demonstrates).

So please try without public/assets linking, if that does not work, I'll upload.

@schneems
Copy link
Member

Still working for me:

$ rm -rf public
$ ls -la
total 56
drwxr-xr-x  19 richardschneeman  staff   646 Aug 14 15:24 .
drwxr-xr-x  63 richardschneeman  staff  2142 Aug 14 11:52 ..
drwxr-xr-x  12 richardschneeman  staff   408 Aug 14 11:52 .git
-rw-r--r--   1 richardschneeman  staff   474 Aug 14 11:47 .gitignore
-rw-r--r--   1 richardschneeman  staff  1476 Aug 14 11:47 Gemfile
-rw-r--r--   1 richardschneeman  staff  3851 Aug 14 11:47 Gemfile.lock
-rw-r--r--   1 richardschneeman  staff   478 Aug 14 11:47 README.rdoc
-rw-r--r--   1 richardschneeman  staff   249 Aug 14 11:47 Rakefile
drwxr-xr-x   8 richardschneeman  staff   272 Aug 14 11:47 app
drwxr-xr-x   7 richardschneeman  staff   238 Aug 14 11:47 bin
drwxr-xr-x  11 richardschneeman  staff   374 Aug 14 11:47 config
-rw-r--r--   1 richardschneeman  staff   153 Aug 14 11:47 config.ru
drwxr-xr-x   7 richardschneeman  staff   238 Aug 14 11:50 db
drwxr-xr-x   3 richardschneeman  staff   102 Aug 14 15:26 lib
drwxr-xr-x   5 richardschneeman  staff   170 Aug 14 11:49 log
drwxr-xr-x   2 richardschneeman  staff    68 Aug 14 15:24 public
drwxr-xr-x   9 richardschneeman  staff   306 Aug 14 11:47 test
lrwxr-xr-x   1 richardschneeman  staff    25 Aug 14 11:56 tmp -> ../sprockets-96-copy/tmp/
drwxr-xr-x   3 richardschneeman  staff   102 Aug 14 11:47 vendor
# environments/production.rb
Rails.application.configure do
  # Settings specified here will take precedence over those in config/application.rb.

  # Code is not reloaded between requests.
  config.cache_classes = true

  # Eager load code on boot. This eager loads most of Rails and
  # your application in memory, allowing both threaded web servers
  # and those relying on copy on write to perform better.
  # Rake tasks automatically ignore this option for performance.
  config.eager_load = true

  # Full error reports are disabled and caching is turned on.
  config.consider_all_requests_local       = false
  config.action_controller.perform_caching = true

  # Enable Rack::Cache to put a simple HTTP cache in front of your application
  # Add `rack-cache` to your Gemfile before enabling this.
  # For large-scale production use, consider using a caching reverse proxy like
  # NGINX, varnish or squid.
  # config.action_dispatch.rack_cache = true

  # Disable serving static files from the `/public` folder by default since
  # Apache or NGINX already handles this.
  config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present?

  # Compress JavaScripts and CSS.
  config.assets.js_compressor = :uglifier
  # config.assets.css_compressor = :sass

  # Do not fallback to assets pipeline if a precompiled asset is missed.
  config.assets.compile = true

  # Asset digests allow you to set far-future HTTP expiration dates on all assets,
  # yet still be able to expire them through the digest params.
  config.assets.digest = true

  # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb

  # Specifies the header that your server uses for sending files.
  # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
  # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX

  # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
  # config.force_ssl = true

  # Use the lowest log level to ensure availability of diagnostic information
  # when problems arise.
  config.log_level = :debug

  # Prepend all log lines with the following tags.
  # config.log_tags = [ :subdomain, :uuid ]

  # Use a different logger for distributed setups.
  # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)

  # Use a different cache store in production.
  # config.cache_store = :mem_cache_store

  # Enable serving of images, stylesheets, and JavaScripts from an asset server.
  # config.action_controller.asset_host = 'http://assets.example.com'

  # Ignore bad email addresses and do not raise email delivery errors.
  # Set this to true and configure the email server for immediate delivery to raise delivery errors.
  # config.action_mailer.raise_delivery_errors = false

  # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
  # the I18n.default_locale when a translation cannot be found).
  config.i18n.fallbacks = true

  # Send deprecation notices to registered listeners.
  config.active_support.deprecation = :notify

  # Use default logging formatter so that PID and timestamp are not suppressed.
  config.log_formatter = ::Logger::Formatter.new

  # Do not dump schema after migrations.
  config.active_record.dump_schema_after_migration = false
end

Started with

$ env SECRET_KEY_BASE=foo RAILS_ENV=production RAILS_SERVE_STATIC_FILES=1 rails s

Result

$ curl http://localhost:3000/assets/application-3698babcae573d7cf113fedbe38437ec3bc009e54bc20ccfe4a69d94b7a17732.js -I
HTTP/1.1 405 Method Not Allowed
Content-Type: text/plain
Content-Length: 18
Cache-Control: no-cache
X-Request-Id: 8b5bdc1e-d662-408f-a752-bb2c1395f327
X-Runtime: 0.000544
Server: WEBrick/1.3.1 (Ruby/2.2.2/2015-04-13)
Date: Fri, 14 Aug 2015 20:29:33 GMT
Connection: Keep-Alive

@schneems
Copy link
Member

The presence of precompiled assets bypasses the cache entirely I believe (right?)

Not quite. First sprockets tries to see if there is a valid asset by first using mtime with the cache to make sure the source file in app/assets hasn't changed. If it hasn't and the cache returns a value then another cache lookup will be performed that stores all the "dependencies" of that asset. Each dependency is pulled out and "resolved" (i.e. they check the mtime) to make sure none of them has changed. If they're all still the same then it uses a digest of the dependencies to create another cache key, this one is finally used to pull the asset from the cache. Only then will sprockets check the disk to see if the asset with digest is already in public/assets. I'm not 100% sure that's everything, but it's my understanding of how sprockets work, though it wouldn't be the first time i've been surprised by sprockets.

on demand compilation is on

This is a huge performance penalty to your site, I hope you've got some really good CDN in front of your site so each asset only has to be compiled once, otherwise you're in for a world of pain. I would highly recommend against leaving this option on, I would be curious hearing why it's needed but perhaps in a different conversation to keep this thread focused.

problem

For your use case, can you add the physical location of the assets to sprockets load paths? Sprockets can use multiple paths, though i'm not sure how to set this through rails, that might alleviate the issue. Another option could be symlinking the public/assets directory so that you're pointing the cache and the assets directory to the same project/path-structure.

@bughit
Copy link
Author

bughit commented Aug 14, 2015

you are doing a HEAD on application.js and it's responding with method not allowed. You are not hitting the asset cache with that.

I tested doing just a GET on application.js and it works. So, as I directed, you need to invoke an action that does a javascript_include_tag or css.

I'll upload, unless you can retry quickly.

@schneems
Copy link
Member

That was the wrong paste. It renders correctly when I visit the page and when I curl without -I

@bughit
Copy link
Author

bughit commented Aug 14, 2015

created a repo

Please follow these directions exactly:

  1. use ruby 2.2.2
  2. git clone git@github.com:bughit/asset_cache_repo.git
  3. cd asset_cache_repo/asset_cache_test_copy
  4. bin/rake assets:precompile
  5. cd ../asset_cache_test
  6. bin/rails server -p 3002
  7. load or curl http://localhost:3002/

@bughit
Copy link
Author

bughit commented Aug 14, 2015

can you add the physical location of the assets to sprockets load paths?

As I mentioned, sprockets 2 works fine, so that's the workaround we are using. sprockets 2 cache does not use absolute paths for it's keys or anything else (I think there is nothing else in 2.x), which facilitates cache sharing between releases. Sprockets 3 should not be using absolute paths at all either. 3.3 started along this path, but didn't finish.

@elia
Copy link
Contributor

elia commented Aug 14, 2015

Got the same error in production this morning, the way we do the deploy is by compiling locally and checking the manifest into the repo and uploading to cloudfront (via asset_sync). Here's the backtrace in case it's useful:

Error message   Sprockets::FileOutsidePaths: /home/ubuntu/apps/histreet/releases/20150814095620/app/assets/javascripts/spree/turbolinks.js.coffee is no longer under a load path: 
/home/ubuntu/apps/histreet/shared/bundle/ruby/2.2.0/gems/opal-0.8.0/opal, 
/home/ubuntu/apps/histreet/shared/bundle/ruby/2.2.0/gems/opal-0.8.0/stdlib, 
/home/ubuntu/apps/histreet/shared/bundle/ruby/2.2.0/gems/opal-0.8.0/lib, 
/home/ubuntu/apps/histreet/shared/bundle/ruby/2.2.0/bundler/gems/opal-jquery-5f6662498a81/lib, 
/home/ubuntu/apps/histreet/shared/bundle/ruby/2.2.0/gems/opal-activesupport-0.1.0/opal, 
/home/ubuntu/apps/histreet/shared/bundle/ruby/2.2.0/gems/opal-rspec-0.4.3/opal, 
/home/ubuntu/apps/histreet/shared/bundle/ruby/2.2.0/gems/opal-rspec-0.4.3/vendor_lib, 
/home/ubuntu/apps/histreet/shared/bundle/ruby/2.2.0/bundler/gems/opal-haml-3996e34c67bb/opal, 
/home/ubuntu/apps/histreet/releases/20150814102012/app/assets/images, 
/home/ubuntu/apps/histreet/releases/20150814102012/app/assets/javascripts, 
/home/ubuntu/apps/histreet/releases/20150814102012/app/assets/stylesheets, 
/home/ubuntu/apps/histreet/releases/20150814102012/vendor/assets/javascripts, 
/home/ubuntu/apps/histreet/releases/20150814102012/vendor/assets/stylesheets, 
[cut]

Looks like relative paths are being confused by the changed root (from 20150814095620 to 20150814102012) which is stored in the cache for some reason.

Also I tried the steps above and I can reproduce.

@clemensg
Copy link

I hit the same error when putting some images in vendor/assets/images, deploying with capistrano and compiling the assets on the server. Then, the following error occurs:

Sprockets::FileOutsidePaths:
/var/rails/releases/20150814213520/vendor/assets/images/fancybox_sprite.png is no longer under a load path:
/var/rails/releases/20150814225400/app/assets/images,
/var/rails/releases/20150814225400/app/assets/javascripts,
/var/rails/releases/20150814225400/app/assets/stylesheets,
/var/rails/releases/20150814225400/lib/assets/javascripts,
/var/rails/releases/20150814225400/lib/assets/stylesheets,
/var/rails/releases/20150814225400/vendor/assets/images
...

That fancybox_sprite.png file however, does exist on the server, so it looks like the same sprockets caching bug.

@schneems
Copy link
Member

Thanks for the report, I got the example @bughit sent over to reproduce the problem.

Sprockets 3 should not be using absolute paths at all either. 3.3 started along this path, but didn't finish.

Right now if an asset is not inside of your root, then we cache the absolute path.

def get_relative_path_from_uri
path = uri.sub(/\Afile:\/\//, "".freeze)
if relative_path = PathUtils.split_subpath(root, path)
relative_path
else
path
end
end

My original assumption was all your assets would either be relative to your project root or at a static path such as /Users/richardschneeman/.gem/ruby/2.2.2/gems/twitter-bootstrap-rails-3.2.0/ but that doesn't look to be valid.

I'm not sure what other alternatives we have. If we cache the relative path to the asset ../../../../../../.gem/ruby/2.2.2/gems/twitter-bootstrap-rails-3.2.0/ then this would change when you moved or down a level. I don't know how sprockets 2 caching worked, and I don't know why the caching scheme was changed to use absolute paths. I can dig into this deeper on monday.

@bughit
Copy link
Author

bughit commented Aug 15, 2015

Right now if an asset is not inside of your root, then we cache the absolute path.

I don't understand what you are saying there. There are only two assets, application.js and application.css and both are inside the project (app/assets). I am assuming that's what you mean by "inside the root".

The cache is generated in the asset_cache_test_copy by precompile. So you appear to be saying there should not be any absolute paths in the cache files, but clearly there are.

@schneems
Copy link
Member

Here's the issue. When we try to load the asset via

if uri_from_cache = cache.get(unloaded.digest_key(digest), true)
cache.get(UnloadedAsset.new(uri_from_cache, self).asset_key, true)
we find a cache entry exists, and bingo:

{:uri=>
  "file:///Users/richardschneeman/Documents/projects/tmp/asset_cache_repo/asset_cache_test_copy/app/assets/stylesheets/application.css?type=text/css&id=362558b6fff2447f73fc9fc4e37a7315119a920a68da221e0c7a8a1b0246e723",
 :load_path=>
  "/Users/richardschneeman/Documents/projects/tmp/asset_cache_repo/asset_cache_test_copy/app/assets/stylesheets",
 :filename=>
  "/Users/richardschneeman/Documents/projects/tmp/asset_cache_repo/asset_cache_test_copy/app/assets/stylesheets/application.css",
 :name=>"application",
 :logical_path=>"application.css",
 :content_type=>"text/css",
 :source=>
  "/*\n * This is a manifest file that'll be compiled into application.css, which will include all the files\n * listed below.\n *\n * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,\n * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.\n *\n * You're free to add application-wide styles to this file and they'll appear at the bottom of the\n * compiled file so the styles you add here take precedence over styles defined in any styles\n * defined in the other CSS/SCSS files in this directory. It is generally better to create a new\n * file per style scope.\n *\n\n\n */\n\n",
 :metadata=>
  {:dependencies=>
    #<Set: {"environment-version",
     "environment-paths",
     "processors:type=text/css&file_type=text/css",
     "file-digest:///Users/richardschneeman/Documents/projects/tmp/asset_cache_repo/asset_cache_test_copy/app/assets/stylesheets/application.css",
     "processors:type=text/css&file_type=text/css&pipeline=self",
     "file-digest:///Users/richardschneeman/Documents/projects/tmp/asset_cache_repo/asset_cache_test_copy/app/assets/stylesheets"}>,
   :sass_dependencies=>#<Set: {}>,
   :links=>#<Set: {}>,
   :included=>
    ["file:///Users/richardschneeman/Documents/projects/tmp/asset_cache_repo/asset_cache_test_copy/app/assets/stylesheets/application.css?type=text/css&pipeline=self&id=74e025a9f030d490798b9eb416d5a4451a77bec3e06bdbd0b30a45fd1c455629"],
   :charset=>"utf-8",
   :digest=>
    "\xE8\x0E\x8F#\x18\x04>\x8A\xF9M\xDD\xC2\xAD\xADZO\ts\x9A\x8E\xBB2;:\xB3\x1C\xD7\x1DE\xFD\x91\x13",
   :length=>653},
 :dependencies_digest=>
  "\x81X\xC8\xC7$\xAA|\xF7\x11'\xEB\xC4p\xEBI\xF7\x1F^l\x1Ab\xDC!\xAD?\x8ADN\"\x9B\n\xC7",
 :id=>"362558b6fff2447f73fc9fc4e37a7315119a920a68da221e0c7a8a1b0246e723",
 :mtime=>1439675961}

The uri and load_path both have absolute file paths stored in them. The metadata[:dependencies] does as well, but I'm already handling that in the resolve_dependencies method in the same file. It's probably not caught in the test I wrote since the asset already exists on disk in our copy, though i'm not 100% sure why it doesn't try to load the absolute uri to the wrong path

test "moving root of project after generation is still freaky fast" do
env1 = new_environment
env1.cache = Cache.new
env1["mobile.js"]
assert_no_redundant_processor_calls
assert_no_redundant_bundle_processor_calls
assert_no_redundant_cache_set_calls
Dir.mktmpdir do |dir|
Dir.chdir(dir) do
`cp -R #{File.join(fixture_path("default"), "*")} .`
env2 = new_environment("./default")
env2.cache = env1.cache
reset_stats!
env2["mobile.js"]
assert_no_redundant_stat_calls
assert_no_processor_calls
assert_no_bundle_processor_calls
assert_no_redundant_cache_get_calls
assert_no_cache_set_calls
end
end
end
.

@bughit
Copy link
Author

bughit commented Aug 16, 2015

The uri and load_path both have absolute file paths stored in them. The metadata[:dependencies] does as well

Right, and they shouldn't. There is no good reason to store absolute paths in the cache entry (unless, as you pointed out, the assets is outside the project)

@schneems
Copy link
Member

yes

schneems added a commit that referenced this issue Aug 16, 2015
The patch in #92 was incomplete, it converted all cache keys to use relative paths, but didn't fully remove all absolute paths from cache contents. The test case was accidentally passing due to the copying of files instead of moving files

- [x] dependency paths
- [x] asset uris
- [x] "included" paths (no idea what these are)
- [ ] load paths

This is a WIP
@schneems
Copy link
Member

Here's my WIP #101 needs some major cleanup. Not sure about using presence of a host to share the absolute/relative information but it seems to work fine. This solution works for your test case but is pretty gnarly

@dhampik
Copy link

dhampik commented Aug 18, 2015

For anyone having that error with vendor/assets (which worked before), I was able to resolve production deploy by adding to config/initializers/assets.rb the following:

# Add additional assets to the asset load path
Rails.application.config.assets.paths << "#{Rails.root}/vendor/assets"

Not sure if it's a correct fix, but it helped me to deploy my app.

schneems added a commit to schneems/sprockets that referenced this issue Aug 18, 2015
The patch in rails#92 was incomplete, it converted all cache keys to use relative paths, but didn't fully remove all absolute paths from cache contents. The test case was accidentally passing due to the copying of files instead of moving files

- [x] dependency paths
- [x] filename
- [x] asset uris
- [x] "included" paths (no idea what these are)
- [x] load paths

- [x] update docs
- [ ] test broken behavior

This is a WIP
@schneems
Copy link
Member

#101 is now (what I believe) to be a working fix. I'm trying to write a failing test case in 3.x so we don't have to worry about a future regression.

schneems added a commit that referenced this issue Aug 18, 2015
The patch in #92 was incomplete, it converted all cache keys to use relative paths, but didn't fully remove all absolute paths from cache contents. The test case was accidentally passing since we didn't check to make sure any of the paths from cache were different from the original ones stored. This patch eliminates these absolute paths stored in the cache:

- [x] dependency paths
- [x] filename
- [x] asset uris
- [x] "included" paths (no idea what these are)
- [x] load paths

### URITar

This patch works by introducing a utility class URITar which can "compress" or "expand" a given uri. A "compressed" uri can either be one that is relative to the root, or an absolute path if it is outside of the root. An expanded uri will always be an absolute path.

A uri that is relative to the root will be compressed with no beginning slash

    file://relative/to/root/file.js

A uri that is outside of the root will be compressed with a beginning slash

    file:///absolute/path/to/file.js

Even though I'm using "uri" here the URITar class can also operate on file paths without a valid URI scheme name. Like:

    relative/to/root/file.js

and

    /absolute/path/to/file.js

The UnloadedAsset class was moved to it's own file and refactored to use the new URITar class.

## Fix

Before putting anything in the cache, we will "compress" all uris  and paths so that no absolute paths are in the cache (unless they're not relative to the root which would indicate they're somewhere global e.g. from a gem or shared directory). 

Upon loading an asset in memory, we "expand" all uris since sprockets relies on absolute paths for just about everything. 

Almost all the business logic is limited to the loader, so the rest of sprockets has no clue if relative or absolute paths were used to build the asset.

We are also compressing the "environment-paths" so that dependencies in different paths will differ. I think this is needed, but the tests don't fail when it's taken out.

ATP
@bughit
Copy link
Author

bughit commented Aug 21, 2015

grep /tmp/asset_cache_repo/asset_cache_test_copy/ *

Binary file BPk82Q0VktG2GPovwSHPysyr27IyWE5kuwo6FWsTccA.cache matches
Binary file lpNenmFa5RbxzW1angUNN_l6blUaixmPjHrpFoafsgE.cache matches
Binary file PmQLT63FTKauRkinZSc5stYiJXhMun10-pUVsy2bhI0.cache matches
Binary file xfzSrEFVSrlGcqb97NgufRAtPlLchvbikTlsBfVZgaA.cache matches

grep -a /tmp/asset_cache_repo/asset_cache_test_copy/ *

BPk82Q0VktG2GPovwSHPysyr27IyWE5kuwo6FWsTccA.cache:{�uriI"��file://app/assets/stylesheets/application.css?type=text/css&id=13290f4508text/css�;�T:p/assets/stylesheets/application.css�;�T:�:nameI"�application�;�T:�logical_pathI"�application.css�;�T:�content_typeI"
             sourceI"�#�/* line 1, /tmp/asset_cache_repo/asset_cache_test_copy/app/assets/stylesheets/scss2.scss */
lpNenmFa5RbxzW1angUNN_l6blUaixmPjHrpFoafsgE.cache:@file0:@importer0;�i�;�0;�i�;�i�;�0:�@selector_source_rangeo;�    ;�o;�;�i�;�i�;�o;�;�i�;�i;�I"R/tmp/asset_cache_repo/asset_cache_test_copy/app/assets/stylesheets/scss2.scss�;   T;�o:�Sass::Rails::SassImporte:
lpNenmFa5RbxzW1angUNN_l6blUaixmPjHrpFoafsgE.cache:@rootI"R/tmp/asset_cache_repo/asset_cache_test_copy/app/assets/stylesheets/scss2.scss�;   F:@real_rootI"R/tmp/asset_cache_repo/asset_cache_test_copy/app/assets/stylesheets/scss2.scss�;  F:�@same_name_warningso;��;�{:
PmQLT63FTKauRkinZSc5stYiJXhMun10-pUVsy2bhI0.cache:@fileI"R/tmp/asset_cache_repo/asset_cache_test_copy/app/assets/stylesheets/scss1.scss�T:@importero:�Sass::Rails::SassImporte:
PmQLT63FTKauRkinZSc5stYiJXhMun10-pUVsy2bhI0.cache:@rootI"R/tmp/asset_cache_repo/asset_cache_test_copy/app/assets/stylesheets/scss1.scss�F:@real_rootI"R/tmp/asset_cache_repo/asset_cache_test_copy/app/assets/stylesheets/scss1.scss�F:�@same_name_warningsoSet�:
text/css�;�T:qb97NgufRAtPlLchvbikTlsBfVZgaA.cache:scss2�;�T:�logical_pathI"�scss2.self.css�;�T:�content_typeI"
             sourceI"w/* line 1, /tmp/asset_cache_repo/asset_cache_test_copy/app/assets/stylesheets/scss2.scss */

@schneems
Copy link
Member

The comment is being inserted by https://github.com/sass/sass/blob/1421c1fb38f56d947d1e0271ab5ed12dee510d48/lib/sass/tree/visitors/to_css.rb#L307 and that other absolute paths come from sass cache.

Sass uses marshal dump: https://github.com/sass/sass/blob/stable/lib/sass/cache_stores/base.rb#L51

The nodes it dumps contain a "source_range"

[3] pry(#<Sass::Rails::CacheStore>)> root.inspect
=> "Sass::Tree::RootNode"
# ...
[6] pry(#<Sass::Rails::CacheStore>)> root.source_range
=> (1:1 to 1:1 in /Users/richardschneeman/Documents/projects/tmp/asset_cache_repo/asset_cache_test_copy/app/assets/stylesheets/scss1.scss)

All the absolute paths are coming from sass.

If there's no absolute paths coming from sprockets (that we can find for now) I vote we merge and cut a release. I'll then work with sass to remove the absolute paths there.

Unfortunately sprockets doesn't have a dependency on sass since you can use sprockets without sass, so we can't rev the sass dependency to guarantee no absolute paths. We could check the version and emit a warning perhaps?

@joker-777
Copy link

I'm testing #96 (comment) right now and couldn't experience the error again. Looks like as if it is fixed for me.

@schneems
Copy link
Member

@joker-777 thanks for testing! I opened an issue with sass sass/sass#1802 hopefully I can get some help there while I work on a patch.

@joker-777
Copy link

@schneems You are welcome. Unfortunately we just discovered that it somehow doesn't recompile js files which were changed. Investigating further.

@schneems
Copy link
Member

@joker-777 let me know. I just tested with @bughit's app and it seems to work

2.2.3  ~/documents/projects/tmp/asset_cache_repo/asset_cache_test_copy (master)
$ bin/rake assets:precompile

2.2.3  ~/documents/projects/tmp/asset_cache_repo/asset_cache_test_copy (master)
$ echo 'var foo = "hello";' >> app/assets/javascripts/asset1.js

2.2.3  ~/documents/projects/tmp/asset_cache_repo/asset_cache_test_copy (master)
$ bin/rake assets:precompile
I, [2015-08-21T10:12:08.152279 #66549]  INFO -- : Writing /Users/richardschneeman/Documents/projects/tmp/asset_cache_repo/asset_cache_test_copy/public/assets/application-55ae4c5318c07b2196f18e52480c0563fe6631eb7fd24d38a2e15a5d50a55b59.js

@joker-777
Copy link

Ok, we definitely have this problem that changes js files don't get precompiled. I will clobber the current assets and try it again.

@schneems
Copy link
Member

Can you give me an example app that reproduces the problem? I'll be happy to take a look.

@joker-777
Copy link

Ok, now it works. We keep and eye on it.

@schneems
Copy link
Member

The clobber might have been needed for the cache changes to take affect if the old cache had absolute paths pointing at assets that were still on disk. Thanks again for testing.

@joker-777
Copy link

Unfortunately I have tell you that we still experience that js files don't get precompiled even though they should contain changes. In the beginning it worked but now happened again.

@schneems
Copy link
Member

Can you give me an example app that reproduces the problem? I'll be happy to take a look.

@JamesChevalier
Copy link

I'm chiming in, just in case I can be of any help.

I tried a deploy this morning after updating sprockets, and it failed with a FileOutsidePaths error (details below).

I did get a successful deploy by forcing the version to 3.3.0. Using versions 3.3.2, or 3.3.3 results in the deploy attempting to use the old release path during precompile (below, notice that the css file it's referring to is in 20150821195208 while the load path it's referring to is in 20150824133631).

Sprockets::FileOutsidePaths: /var/www/staging/releases/20150821195208/app/assets/stylesheets/application.css is no longer under a load path: ... /var/www/staging/releases/20150824133631/app/assets/stylesheets ...

The order I went in testing versions of sprockets was:

  1. This morning's first deploy after updating to 3.3.3 from 3.3.2 (fail)
  2. I rolled back to 3.3.2 (fail)
  3. I jumped back to 3.3.0 (success)
  4. Now I'm curious which version introduced the issue, so I try 3.3.1 (success)
  5. I'm still curious, so I try this again ... 3.3.2 (success)

So for me this doesn't seem to be caused solely by a particular version of sprockets. Whether or not a previous deploy was successful also seems to play a role. Whatever that means. :-)

It seems that version 3.3.0 is the newest version that is capable of completing a deploy, so I'm currently pinned there. Let me know if you'd like more information or have any tests I could run for you.

@schneems
Copy link
Member

@JamesChevalier Version 3.3.0 introduced changes to the cache, so you won't see any issue at all unless you are using the cache. Sprockets invalidates the cache on every version change, so every time you upgrade or downgrade sprockets you theoretically should be getting a fresh cache. Since that's the case, you should be getting a problem the second time you deploy, not the first. So deploying with 3.3.0 or 3.3.1 or 3.3.2 might work the first time but probably shouldn't work the second time as there's known bugs in them. If you can give me a 3.3.3 example app that reproduces this error, I would be grateful.

@themilkman
Copy link

We had the same issue here and solved it by changing into the old directory used (20150821195208 in your case) and deleting the whole tmp/cache/assets/production dir. Afterwards the deployment worked again - reproducible.

@JamesChevalier
Copy link

When using sprockets v 3.3.0, I am able to deploy more than once. I tested simply running cap staging deploy a number of times in a row, with no changes. I also tested by running cap staging deploy, making some minor view code changes, and running cap staging deploy again.

What are the specific files/details you'd like to receive, for the reproduced error? I want to be sure I don't waste your time.

@schneems
Copy link
Member

3.3.0 works because works because it has bugs in it, 3.3.3 fixed all known bugs so if you are getting errors with 3.3.3 then we need to reproduce them and fix them. If you can give me a 3.3.3 example app on github that reproduces this error, and instructions on how to run it to reproduce the error then I can probably fix whatever issue you're seeing.

@JamesChevalier
Copy link

OK, sounds good.

I've got this app back up to 3.3.3, and it's working fine (something about that 3.3.0 deploy I mentioned above as step 3 in my testing has fixed it for now).
If there's any connection between this issue and the fact that there were a few days between deploys, then I don't expect to see any issue with this until Tuesday.
Otherwise, it seems like I ran across A Weird Thing in the upgrade & I'm not sure I'll be able to reproduce it at this point.

Thanks for your help & hard work!

@karlingen
Copy link

@themilkman @schneems
Wonderful! I had the same issue here. Removing tmp/cache/assets/production along with upgrading to 3.3.3 fixed it. Thanks a bunch guys!

@Sija
Copy link

Sija commented Aug 29, 2015

@karlingen same here, worked like charm!

@krisdigital
Copy link

Upgrading to 3.3 and running

bundle exec rake tmp:clear

on the server before deploying worked.

@schneems
Copy link
Member

schneems commented Sep 1, 2015

Awesome, going to close this issue. If you see any other weirdness please open up a new one with information on how to reproduce. Thanks for everyone's help ❤️

@schneems schneems closed this as completed Sep 1, 2015
@itay-grudev
Copy link

@schneems I have no idea what has changed, but I am getting the same issue running 4.0.0.beta6 .

@itay-grudev
Copy link

I vote for reopening this issue.

@mat2maa
Copy link

mat2maa commented Feb 10, 2018

@schneems @itay-grudev yes, me also - downgrading to 3.7.1 resolves the issue, but I can't do that for the lack of ES6 support. For now, I have a Capistrano before hook which does

bundle exec rake tmp:clear

before starting deployment.

@jjwdowik
Copy link

Seeing the same issue as well on 4.0.0.beta6

@marcamillion
Copy link

marcamillion commented Jul 3, 2020

I am seeing this same issue within the test suite on 4.0.2.

I stumbled on another bug which I think I have fixed, but now when I run the test suite, I get this error:

Finished in 140.653251s, 6.3987 runs/s, 28.0050 assertions/s.

  1) Error:
TestLoader#test_"load uri with index alias":
Sprockets::FileOutsidePaths: /Users/marcamillion/sprockets/%2FUsers%2Fmarcgayle%2Fsprockets%2Ftest%2Ffixtures%2Fdefault%2Fcoffee.js (index alias of /Users/marcamillion/sprockets/test/fixtures/default/coffee/index.js) is no longer under a load path: /Users/marcamillion/sprockets/test/fixtures/default
    /Users/marcamillion/sprockets/lib/sprockets/loader.rb:156:in `load_from_unloaded'
    /Users/marcamillion/sprockets/lib/sprockets/loader.rb:59:in `block in load'
    /Users/marcamillion/sprockets/lib/sprockets/loader.rb:337:in `fetch_asset_from_dependency_cache'
    /Users/marcamillion/sprockets/lib/sprockets/loader.rb:43:in `load'
    /Users/marcamillion/sprockets/lib/sprockets/cached_environment.rb:44:in `load'
    /Users/marcamillion/sprockets/lib/sprockets/environment.rb:43:in `load'
    /Users/marcamillion/sprockets/test/test_loader.rb:55:in `block in <class:TestLoader>'

900 runs, 3939 assertions, 0 failures, 1 errors, 4 skips

I am still trying to figure out how to tackle this properly, but I don't fully understand the issue yet.

Edit 1

Hey @schneems could you help me understanding the flow of some of this code and how this works.

I think I have isolated the problem to these lines.

        path_to_split =
          if index_alias = unloaded.params[:index_alias]
            expand_from_root index_alias
          else
            unloaded.filename
          end

        load_path, logical_path = paths_split(config[:paths], path_to_split)

        unless load_path
          target = path_to_split
          target += " (index alias of #{unloaded.filename})" if unloaded.params[:index_alias]
          raise FileOutsidePaths, "#{target} is no longer under a load path: #{self.paths.join(', ')}"
        end

Here are the first 3 variables in action - index_alias, unloaded.params[:index_alias] & path_to_split:

[15] pry(#<Sprockets::CachedEnvironment>)> index_alias
=> "%2FUsers%2Fmarcamillion%2Fsprockets%2Ftest%2Ffixtures%2Fdefault%2Fcoffee.js"
[16] pry(#<Sprockets::CachedEnvironment>)> unloaded.params[:index_alias]
=> "%2FUsers%2Fmarcamillion%2Fsprockets%2Ftest%2Ffixtures%2Fdefault%2Fcoffee.js"
[17] pry(#<Sprockets::CachedEnvironment>)> path_to_split
=> "/Users/marcamillion/sprockets/%2FUsers%2Fmarcamillion%2Fsprockets%2Ftest%2Ffixtures%2Fdefault%2Fcoffee.js"

Then if we look at the 2nd 3 variables:

[18] pry(#<Sprockets::CachedEnvironment>)> config[:paths]
=> ["/Users/marcamillion/sprockets/test/fixtures/default"]
[19] pry(#<Sprockets::CachedEnvironment>)> path_to_split
=> "/Users/marcamillion/sprockets/%2FUsers%2Fmarcamillion%2Fsprockets%2Ftest%2Ffixtures%2Fdefault%2Fcoffee.js"
[20] pry(#<Sprockets::CachedEnvironment>)> paths_split(config[:paths], path_to_split)
=> nil

That nil from paths_split(config[:paths], path_to_split) is what causes this error in my instance I believe.

But I can't quite grok what's happening here and why it creates that funky URI in the path_to_split.

Thoughts?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests