Skip to content

Commit beda3c4

Browse files
committed
Fix race condition during parallel coverage testing using Template compiled_path option/method
This race condition can only happen during parallel testing. Assume you have 2 processes, both trying to compile the same file at the same time. When using the compiled_path option/method, this involves writing a file and loading it. That's done with File.binwrite. However, the way File.binwrite works is it opens the file for writing, truncating it if it exists, then writes the data. So in the following situation, you lose the race, and you get an exception like `undefined method '__tilt_2720' for module 'Tilt::CompiledTemplates'` when trying to load the template: 1. Process A runs File.binwrite 2. Process A truncates file 3. Process A writes file 4. Process B runs File.binwrite 5. Process B truncates file 6. Process A loads (empty) file 7. Process B writes file 8. Process B loads (nonempty) file In this situation, process A loaded an empty file. Loading an empty file isn't an error, so processing continues. You don't have problems until you try to access the method that should have been defined and it isn't there. Fix this by writing to a temporary file name that includes the pid, and then renaming the file to the expected name after the file has been written successfully. Writing to separate files avoids the race condition, and the rename is atomic, so this should avoid the race condition.
1 parent e3fd319 commit beda3c4

2 files changed

Lines changed: 10 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## master
2+
3+
* Fix race condition during parallel coverage testing using Template compiled_path option/method (jeremyevans)
4+
15
## 2.6.0 (2025-01-13)
26

37
* Support :compiled_path option, needed for compiled paths when using :scope_class and fixed locals (jeremyevans)

lib/tilt/template.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,12 @@ def eval_compiled_method(method_source, offset, scope_class)
473473
end
474474

475475
def load_compiled_method(path, method_source)
476-
File.binwrite(path, method_source)
476+
# Write to a temporary path specific to the current process, and
477+
# rename after writing. This prevents issues during parallel
478+
# coverage testing.
479+
tmp_path = "#{path}-#{$$}"
480+
File.binwrite(tmp_path, method_source)
481+
File.rename(tmp_path, path)
477482

478483
# Use load and not require, so unbind_compiled_method does not
479484
# break if the same path is used more than once.

0 commit comments

Comments
 (0)