sprockets: Intermittent access violations in Windows

A few versions ago I started to receive intermittent EACCESS errors, e.g. Permission denied @ utime_failed - /a/sprockets/cache/file.cache But whenever I tried to reproduce it, I failed. Until today 😃

EDIT: After further investigation I found that this problem also applies to threaded servers on Windows, and not just external processes interfering with sprockets. See comment below

The problem is triggered by FileStore#get:

  1. On every request this method reads the file and also updates its timestamps with FileUtils.touch(path).
  2. An external process, like the IDE or antivirus, picks up the change and opens it read-only for further inspection. This happens asynchronously, that’s why the error is not always triggered. (EDIT: With threaded servers it is the server itself, which openes the file. The error occurs almost every time when two or more parallel requests are received)
  3. A possible next call of FileStore#get to the same cache entry opens that file again (which is allowed even if the file is still opened by another process/thread) …
  4. … and changes the timestamp, which fails on Windows. Apparently you can’t change the timestamp if the file is open, even if just read-only. (But that might be specific to the Ruby implementation)

The access violation can be suppressed by changing the offending line to FileUtils.touch(path) rescue nil which provides no (statistically relevant) performance penalty. (Benchmark below)

It also should not affect the garbage collection feature, since if the file is locked, then its timestamp has been updated recently.

Benchmark:

Benchmark.measure { 10_000.times { FileUtils.touch('sample.txt') } }
Benchmark.measure { 10_000.times { FileUtils.touch('sample.txt') rescue nil } }

Linux: 0.020000 0.010000 0.030000 ( 0.028624) FileUtils.touch 0.020000 0.010000 0.030000 ( 0.027564) FileUtils.touch rescue nil

Windows: 0.031000 0.437000 0.468000 ( 0.468788) FileUtils.touch 0.031000 0.438000 0.469000 ( 0.478555) FileUtils.touch rescue nil

PS: Note the time difference between the two platforms 😱

About this issue

  • Original URL
  • State: open
  • Created 9 years ago
  • Comments: 15 (8 by maintainers)

Most upvoted comments

The problem is still there, because the semantics of FileUtils.touch is still different on the Windows platform. But I’m no longer confident, that my “fix” is the proper solution.

Today I ran my demo snippet on various Ruby versions again:

On Ruby 2.2 the cache.get and its subsequent touch method reliably raised EACCES exceptions, where Ruby 2.3 and 2.4 both seem to simply deadlock. On the other hand this happens far less than the AVs in Ruby 2.2: My demo snippet usually created an error after less than 5-6 concurrent reads. (= touch calls) In Ruby 2.3+ I can get about 1.000 concurrent reads until the deadlock. I suspect this is caused by changes to the threading system in Ruby, but honestly I have no idea what’s causing this.

In my Rails application I get this situation about once a week which IMHO is totally acceptable in the development environment. In production (which shouldn’t be on Windows 🤞) this isn’t a problem anyway.