Lighthouse has a new layout. Prefer the old one? Return to the old layout, and switch back any time from the link at the top of each page.

This project is archived and is in readonly mode.

Rails 3.0.0-pre - stylesheet_link_tag, javascript_include_tag cache fails when using File.atomic_write: Invalid cross-device link

#3927

In Rails 3.0.0-pre, when caching JavaScript and CSS assets, a mysterious error is generated on systems with a separate partition for temporary files.

ActionView::Helpers.write_asset_file_contents makes use of File.atomic_write with no tmp_dir parameter specified. This defaults to Dir.tmpdir accordingly, however, it appears that if this directory is on a different volume than the Rails application then an error will result when apparently trying to create a hard-link to move the cached asset file:

ActionView::Template::Error (Invalid cross-device link - /tmp//_main.css20100210-24481-ilznwv-0 or public/stylesheets/_main.css)

This was generated from a simple asset-caching request:

stylesheet_link_tag('reset', 'master', 'forms', :cache => '_main')

This would only seem to be an issue on systems where Dir.tmpname returns a path on a different volume than where Rails.root is situated. On some Linux installations /tmp is given its own partition.

A simple patch is to use the default Rails tmp/ directory by modifying the call in write_asset_file_contents to be:

module ActionView
  module Helpers
    def write_asset_file_contents(joined_asset_path, asset_paths)

      FileUtils.mkdir_p(File.dirname(joined_asset_path))
      File.atomic_write(joined_asset_path, File.join(Rails.root, 'tmp')) { |cache| cache.write(join_asset_file_contents(asset_paths)) }

      # Set mtime to the latest of the combined files to allow for
      # consistent ETag without a shared filesystem.
      mt = asset_paths.map { |p| File.mtime(asset_file_path(p)) }.max
      File.utime(mt, mt, joined_asset_path)
    end
  end
end

It stands to reason that the environment could be tweaked so that TMPDIR is assigned accordingly somewhere during the initialization, but that may alter behavior on a much larger scale.

The previous implementation in Rails 2.3.5 does not use atomic_write and does not suffer from this problem:

File.open(joined_asset_path, "w+") { |cache| cache.write(join_asset_file_contents(asset_paths)) }

This has the unfortunate effect of presumably failing if the Rails application does not have its own tmp/ directory.

Reported by tadman · February 11th, 2010 @ 05:37 AM

State: open
Milestone: 3.0.6
Assigned to: Yehuda Katz (wycats) Yehuda Katz (wycats)
Importance: High

Activity

  1. renchap
    renchap

    Same problem here, /tmp is on a different partition and I get the error.

    March 8th, 2010 @ 01:03 PM

  2. Scott Marshall
    Scott Marshall

    I can confirm that atomic_write is indeed the culprit.

    write_asset_file_contents calls it without a tmp path argument so it defaults to the system tmp dir which normally is /tmp.

    Being able to configure an alternate tmp dir for atomic writes would solve the problem.

    March 22nd, 2010 @ 06:26 PM

  3. Yehuda Katz (wycats)
    Yehuda Katz (wycats)
    • State changed from new to open
    • Tag changed from actionview to actionview, rails3
    • Assigned user set to Yehuda Katz (wycats)
    • Milestone cleared.

    March 27th, 2010 @ 07:54 AM

  4. Jeremy Kemper
    Jeremy Kemper
    • Milestone cleared.
    • Importance changed from to High

    August 30th, 2010 @ 04:10 AM

  5. Jeremy Kemper
  6. Ryan Bigg
    Ryan Bigg
    • Tag cleared.

    Automatic cleanup of spam.

    October 19th, 2010 @ 08:34 AM

  7. uggleader
  8. Santiago Pastorino
  9. Santiago Pastorino
  10. Santiago Pastorino
    Santiago Pastorino
    • Milestone changed from 3.0.5 to 3.0.6

    February 27th, 2011 @ 03:15 AM