bootsnap 1.4.5 → 1.7.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +74 -0
  3. data/README.md +46 -15
  4. data/exe/bootsnap +5 -0
  5. data/ext/bootsnap/bootsnap.c +255 -87
  6. data/ext/bootsnap/extconf.rb +20 -14
  7. data/lib/bootsnap.rb +89 -15
  8. data/lib/bootsnap/bundler.rb +1 -0
  9. data/lib/bootsnap/cli.rb +246 -0
  10. data/lib/bootsnap/cli/worker_pool.rb +131 -0
  11. data/lib/bootsnap/compile_cache.rb +3 -2
  12. data/lib/bootsnap/compile_cache/iseq.rb +22 -7
  13. data/lib/bootsnap/compile_cache/yaml.rb +110 -40
  14. data/lib/bootsnap/explicit_require.rb +1 -0
  15. data/lib/bootsnap/load_path_cache.rb +3 -16
  16. data/lib/bootsnap/load_path_cache/cache.rb +25 -8
  17. data/lib/bootsnap/load_path_cache/change_observer.rb +2 -1
  18. data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +18 -5
  19. data/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb +1 -0
  20. data/lib/bootsnap/load_path_cache/loaded_features_index.rb +34 -11
  21. data/lib/bootsnap/load_path_cache/path.rb +3 -2
  22. data/lib/bootsnap/load_path_cache/path_scanner.rb +50 -26
  23. data/lib/bootsnap/load_path_cache/realpath_cache.rb +5 -5
  24. data/lib/bootsnap/load_path_cache/store.rb +15 -7
  25. data/lib/bootsnap/setup.rb +2 -36
  26. data/lib/bootsnap/version.rb +2 -1
  27. metadata +15 -29
  28. data/.github/CODEOWNERS +0 -2
  29. data/.github/probots.yml +0 -2
  30. data/.gitignore +0 -17
  31. data/.rubocop.yml +0 -20
  32. data/.travis.yml +0 -21
  33. data/CODE_OF_CONDUCT.md +0 -74
  34. data/CONTRIBUTING.md +0 -21
  35. data/Gemfile +0 -8
  36. data/README.jp.md +0 -231
  37. data/Rakefile +0 -12
  38. data/bin/ci +0 -10
  39. data/bin/console +0 -14
  40. data/bin/setup +0 -8
  41. data/bin/test-minimal-support +0 -7
  42. data/bin/testunit +0 -8
  43. data/bootsnap.gemspec +0 -45
  44. data/dev.yml +0 -10
  45. data/lib/bootsnap/load_path_cache/core_ext/active_support.rb +0 -106
  46. data/shipit.rubygems.yml +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: be54a60d54f32f824d5c2fdccf189cdcb5409d1848c55f6e654fe44ace1d3f21
4
- data.tar.gz: c6239c30984a936b76e218c2b1292a7e72d817d3e4847a6c58f0121c1e3068dc
3
+ metadata.gz: d5f10210480d5f0c9574fe970766d9ef4799e9a8a212f6ccafab6a61c2189481
4
+ data.tar.gz: 3502a4a6f2ab2614f470d1aaa4d648ec86ab0aebe9390074a71fc1b7544a9d1c
5
5
  SHA512:
6
- metadata.gz: c251990457406cd51726eec8d62e0e1028bbeade6e24f266cb743d6f00f53650841beed9ee8e1795c78febd18ea234691125920c16aa5a95031718a28619bb31
7
- data.tar.gz: 554a885bf2264f088618c41864fb84069d0664b35af0ae4b619c8fbb1cf285c80fad564d36845161f8044c35c6b53a33e66f27eb75c6715f25869af2795c97f6
6
+ metadata.gz: 53975108838cdfe27d2b8c9e7b0ef1a25100e85ad691063d2dfd35e6db600bf5c76591aeaf51117651fab7c96779c6877a86984bfa97eb7152afecbd42d77828
7
+ data.tar.gz: 294d0cf74421f374dfde4bc551cdcb3a4f6dd4a971c0d50f3f03eb59c73ae75554689077af0cdc85aa7765aa8944e8e3edd919380fcfea9742bf7391d54756a8
data/CHANGELOG.md CHANGED
@@ -1,3 +1,77 @@
1
+ # Unreleased
2
+
3
+ # 1.7.4
4
+
5
+ * Stop raising errors when encoutering various file system errors. The cache is now best effort,
6
+ if somehow it can't be saved, bootsnapp will gracefully fallback to the original operation (e.g. `Kernel.require`).
7
+ (#353, #177, #262)
8
+
9
+ # 1.7.3
10
+
11
+ * Disable YAML precompilation when encountering YAML tags. (#351)
12
+
13
+ # 1.7.2
14
+
15
+ * Fix compatibility with msgpack < 1. (#349)
16
+
17
+ # 1.7.1
18
+
19
+ * Warn Ruby 2.5 users if they turn ISeq caching on. (#327, #244)
20
+ * Disable ISeq caching for the whole 2.5.x series again.
21
+ * Better handle hashing of Ruby strings. (#318)
22
+
23
+ # 1.7.0
24
+
25
+ * Fix detection of YAML files in gems.
26
+ * Adds an instrumentation API to monitor cache misses.
27
+ * Allow to control the behavior of `require 'bootsnap/setup'` using environment variables.
28
+ * Deprecate the `disable_trace` option.
29
+ * Deprecate the `ActiveSupport::Dependencies` (AKA Classic autoloader) integration. (#344)
30
+
31
+ # 1.6.0
32
+
33
+ * Fix a Ruby 2.7/3.0 issue with `YAML.load_file` keyword arguments. (#342)
34
+ * `bootsnap precompile` CLI use multiple processes to complete faster. (#341)
35
+ * `bootsnap precompile` CLI also precompile YAML files. (#340)
36
+ * Changed the load path cache directory from `$BOOTSNAP_CACHE_DIR/bootsnap-load-path-cache` to `$BOOTSNAP_CACHE_DIR/bootsnap/load-path-cache` for ease of use. (#334)
37
+ * Changed the compile cache directory from `$BOOTSNAP_CACHE_DIR/bootsnap-compile-cache` to `$BOOTSNAP_CACHE_DIR/bootsnap/compile-cache` for ease of use. (#334)
38
+
39
+ # 1.5.1
40
+
41
+ * Workaround a Ruby bug in InstructionSequence.compile_file. (#332)
42
+
43
+ # 1.5.0
44
+
45
+ * Add a command line to statically precompile the ISeq cache. (#326)
46
+
47
+ # 1.4.9
48
+
49
+ * [Windows support](https://github.com/Shopify/bootsnap/pull/319)
50
+ * [Fix potential crash](https://github.com/Shopify/bootsnap/pull/322)
51
+
52
+ # 1.4.8
53
+
54
+ * [Prevent FallbackScan from polluting exception cause](https://github.com/Shopify/bootsnap/pull/314)
55
+
56
+ # 1.4.7
57
+
58
+ * Various performance enhancements
59
+ * Fix race condition in heavy concurrent load scenarios that would cause bootsnap to raise
60
+
61
+ # 1.4.6
62
+
63
+ * Fix bug that was erroneously considering that files containing `.` in the names were being
64
+ required if a different file with the same name was already being required
65
+
66
+ Example:
67
+
68
+ require 'foo'
69
+ require 'foo.en'
70
+
71
+ Before bootsnap was considering `foo.en` to be the same file as `foo`
72
+
73
+ * Use glibc as part of the ruby_platform cache key
74
+
1
75
  # 1.4.5
2
76
 
3
77
  * MRI 2.7 support
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # Bootsnap [![Build Status](https://travis-ci.org/Shopify/bootsnap.svg?branch=master)](https://travis-ci.org/Shopify/bootsnap)
1
+ # Bootsnap [![Actions Status](https://github.com/Shopify/bootsnap/workflows/ci/badge.svg)](https://github.com/Shopify/bootsnap/actions)
2
2
 
3
- Bootsnap is a library that plugs into Ruby, with optional support for `ActiveSupport` and `YAML`,
3
+ Bootsnap is a library that plugs into Ruby, with optional support for `YAML`,
4
4
  to optimize and cache expensive computations. See [How Does This Work](#how-does-this-work).
5
5
 
6
6
  #### Performance
@@ -11,7 +11,7 @@ to optimize and cache expensive computations. See [How Does This Work](#how-does
11
11
  - The core Shopify platform -- a rather large monolithic application -- boots about 75% faster,
12
12
  dropping from around 25s to 6.5s.
13
13
  * In Shopify core (a large app), about 25% of this gain can be attributed to `compile_cache_*`
14
- features; 75% to path caching, and ~1% to `disable_trace`. This is fairly representative.
14
+ features; 75% to path caching. This is fairly representative.
15
15
 
16
16
  ## Usage
17
17
 
@@ -29,7 +29,8 @@ If you are using Rails, add this to `config/boot.rb` immediately after `require
29
29
  require 'bootsnap/setup'
30
30
  ```
31
31
 
32
- Note that bootsnap writes to `tmp/cache`, and that directory *must* be writable. Rails will fail to
32
+ Note that bootsnap writes to `tmp/cache` (or the path specified by `ENV['BOOTSNAP_CACHE_DIR']`),
33
+ and that directory *must* be writable. Rails will fail to
33
34
  boot if it is not. If this is unacceptable (e.g. you are running in a read-only container and
34
35
  unwilling to mount in a writable tmpdir), you should remove this line or wrap it in a conditional.
35
36
 
@@ -53,15 +54,11 @@ Bootsnap.setup(
53
54
  cache_dir: 'tmp/cache', # Path to your cache
54
55
  development_mode: env == 'development', # Current working environment, e.g. RACK_ENV, RAILS_ENV, etc
55
56
  load_path_cache: true, # Optimize the LOAD_PATH with a cache
56
- autoload_paths_cache: true, # Optimize ActiveSupport autoloads with cache
57
- disable_trace: true, # Set `RubyVM::InstructionSequence.compile_option = { trace_instruction: false }`
58
57
  compile_cache_iseq: true, # Compile Ruby code into ISeq cache, breaks coverage reporting.
59
58
  compile_cache_yaml: true # Compile YAML into a cache
60
59
  )
61
60
  ```
62
61
 
63
- **Note that `disable_trace` will break debuggers and tracing.**
64
-
65
62
  **Protip:** You can replace `require 'bootsnap'` with `BootLib::Require.from_gem('bootsnap',
66
63
  'bootsnap')` using [this trick](https://github.com/Shopify/bootsnap/wiki/Bootlib::Require). This
67
64
  will help optimize boot time further if you have an extremely large `$LOAD_PATH`.
@@ -71,12 +68,39 @@ speeds up the loading of individual source files, Spring keeps a copy of a pre-b
71
68
  on hand to completely skip parts of the boot process the next time it's needed. The two tools work
72
69
  well together, and are both included in a newly-generated Rails applications by default.
73
70
 
71
+ ### Environment variables
72
+
73
+ `require 'bootsnap/setup'` behavior can be changed using environment variables:
74
+
75
+ - `BOOTSNAP_CACHE_DIR` allows to define the cache location.
76
+ - `DISABLE_BOOTSNAP` allows to entirely disable bootsnap.
77
+ - `DISABLE_BOOTSNAP_LOAD_PATH_CACHE` allows to disable load path caching.
78
+ - `DISABLE_BOOTSNAP_COMPILE_CACHE` allows to disable ISeq and YAML caches.
79
+ - `BOOTSNAP_LOG` configure bootsnap to log all caches misses to STDERR.
80
+
74
81
  ### Environments
75
82
 
76
83
  All Bootsnap features are enabled in development, test, production, and all other environments according to the configuration in the setup. At Shopify, we use this gem safely in all environments without issue.
77
84
 
78
85
  If you would like to disable any feature for a certain environment, we suggest changing the configuration to take into account the appropriate ENV var or configuration according to your needs.
79
86
 
87
+ ### Instrumentation
88
+
89
+ Bootsnap cache misses can be monitored though a callback:
90
+
91
+ ```ruby
92
+ Bootsnap.instrumentation = ->(event, path) { puts "#{event} #{path}" }
93
+ ```
94
+
95
+ `event` is either `:miss` or `:stale`. You can also call `Bootsnap.log!` as a shortcut to
96
+ log all events to STDERR.
97
+
98
+ To turn instrumentation back off you can set it to nil:
99
+
100
+ ```ruby
101
+ Bootsnap.instrumentation = nil
102
+ ```
103
+
80
104
  ## How does this work?
81
105
 
82
106
  Bootsnap optimizes methods to cache results of expensive computations, and can be grouped
@@ -84,8 +108,6 @@ into two broad categories:
84
108
 
85
109
  * [Path Pre-Scanning](#path-pre-scanning)
86
110
  * `Kernel#require` and `Kernel#load` are modified to eliminate `$LOAD_PATH` scans.
87
- * `ActiveSupport::Dependencies.{autoloadable_module?,load_missing_constant,depend_on}` are
88
- overridden to eliminate scans of `ActiveSupport::Dependencies.autoload_paths`.
89
111
  * [Compilation caching](#compilation-caching)
90
112
  * `RubyVM::InstructionSequence.load_iseq` is implemented to cache the result of ruby bytecode
91
113
  compilation.
@@ -124,10 +146,6 @@ open y/foo.rb
124
146
  ...
125
147
  ```
126
148
 
127
- Exactly the same strategy is employed for methods that traverse
128
- `ActiveSupport::Dependencies.autoload_paths` if the `autoload_paths_cache` option is given to
129
- `Bootsnap.setup`.
130
-
131
149
  The following diagram flowcharts the overrides that make the `*_path_cache` features work.
132
150
 
133
151
  ![Flowchart explaining
@@ -214,7 +232,7 @@ Bootsnap writes a cache file containing a 64 byte header followed by the cache c
214
232
  is a cache key including several fields:
215
233
 
216
234
  * `version`, hardcoded in bootsnap. Essentially a schema version;
217
- * `os_version`, A hash of the current kernel version (on macOS, BSD) or glibc version (on Linux);
235
+ * `ruby_platform`, A hash of `RUBY_PLATFORM` (e.g. x86_64-linux-gnu) variable and glibc version (on Linux) or OS version (`uname -v` on BSD, macOS)
218
236
  * `compile_option`, which changes with `RubyVM::InstructionSequence.compile_option` does;
219
237
  * `ruby_revision`, the version of Ruby this was compiled with;
220
238
  * `size`, the size of the source file;
@@ -294,6 +312,19 @@ open /c/nope.bundle -> -1
294
312
  # (nothing!)
295
313
  ```
296
314
 
315
+ ## Precompilation
316
+
317
+ In development environments the bootsnap compilation cache is generated on the fly when source files are loaded.
318
+ But in production environments, such as docker images, you might need to precompile the cache.
319
+
320
+ To do so you can use the `bootsnap precompile` command.
321
+
322
+ Example:
323
+
324
+ ```bash
325
+ $ bundle exec bootsnap precompile --gemfile app/ lib/
326
+ ```
327
+
297
328
  ## When not to use Bootsnap
298
329
 
299
330
  *Alternative engines*: Bootsnap is pretty reliant on MRI features, and parts are disabled entirely on alternative ruby
data/exe/bootsnap ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bootsnap/cli'
5
+ exit Bootsnap::CLI.new(ARGV).run
@@ -14,6 +14,7 @@
14
14
  #include "bootsnap.h"
15
15
  #include "ruby.h"
16
16
  #include <stdint.h>
17
+ #include <stdbool.h>
17
18
  #include <sys/types.h>
18
19
  #include <errno.h>
19
20
  #include <fcntl.h>
@@ -21,6 +22,9 @@
21
22
  #ifndef _WIN32
22
23
  #include <sys/utsname.h>
23
24
  #endif
25
+ #ifdef __GLIBC__
26
+ #include <gnu/libc-version.h>
27
+ #endif
24
28
 
25
29
  /* 1000 is an arbitrary limit; FNV64 plus some slashes brings the cap down to
26
30
  * 981 for the cache dir */
@@ -29,6 +33,12 @@
29
33
 
30
34
  #define KEY_SIZE 64
31
35
 
36
+ #define MAX_CREATE_TEMPFILE_ATTEMPT 3
37
+
38
+ #ifndef RB_UNLIKELY
39
+ #define RB_UNLIKELY(x) (x)
40
+ #endif
41
+
32
42
  /*
33
43
  * An instance of this key is written as the first 64 bytes of each cache file.
34
44
  * The mtime and size members track whether the file contents have changed, and
@@ -65,7 +75,7 @@ struct bs_cache_key {
65
75
  STATIC_ASSERT(sizeof(struct bs_cache_key) == KEY_SIZE);
66
76
 
67
77
  /* Effectively a schema version. Bumping invalidates all previous caches */
68
- static const uint32_t current_version = 2;
78
+ static const uint32_t current_version = 3;
69
79
 
70
80
  /* hash of e.g. "x86_64-darwin17", invalidating when ruby is recompiled on a
71
81
  * new OS ABI, etc. */
@@ -83,19 +93,25 @@ static VALUE rb_mBootsnap_CompileCache;
83
93
  static VALUE rb_mBootsnap_CompileCache_Native;
84
94
  static VALUE rb_eBootsnap_CompileCache_Uncompilable;
85
95
  static ID uncompilable;
96
+ static ID instrumentation_method;
97
+ static VALUE sym_miss;
98
+ static VALUE sym_stale;
99
+ static bool instrumentation_enabled = false;
86
100
 
87
101
  /* Functions exposed as module functions on Bootsnap::CompileCache::Native */
102
+ static VALUE bs_instrumentation_enabled_set(VALUE self, VALUE enabled);
88
103
  static VALUE bs_compile_option_crc32_set(VALUE self, VALUE crc32_v);
89
- static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler);
104
+ static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args);
105
+ static VALUE bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler);
90
106
 
91
107
  /* Helpers */
92
- static uint64_t fnv1a_64(const char *str);
93
- static void bs_cache_path(const char * cachedir, const char * path, char ** cache_path);
108
+ static void bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_CACHEPATH_SIZE]);
94
109
  static int bs_read_key(int fd, struct bs_cache_key * key);
95
110
  static int cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2);
96
- static VALUE bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler);
97
- static int open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenance);
98
- static int fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag, char ** errno_provenance);
111
+ static VALUE bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args);
112
+ static VALUE bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler);
113
+ static int open_current_file(char * path, struct bs_cache_key * key, const char ** errno_provenance);
114
+ static int fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE * output_data, int * exception_tag, const char ** errno_provenance);
99
115
  static uint32_t get_ruby_revision(void);
100
116
  static uint32_t get_ruby_platform(void);
101
117
 
@@ -103,12 +119,12 @@ static uint32_t get_ruby_platform(void);
103
119
  * Helper functions to call ruby methods on handler object without crashing on
104
120
  * exception.
105
121
  */
106
- static int bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data);
122
+ static int bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * output_data);
107
123
  static VALUE prot_storage_to_output(VALUE arg);
108
124
  static VALUE prot_input_to_output(VALUE arg);
109
- static void bs_input_to_output(VALUE handler, VALUE input_data, VALUE * output_data, int * exception_tag);
125
+ static void bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag);
110
126
  static VALUE prot_input_to_storage(VALUE arg);
111
- static int bs_input_to_storage(VALUE handler, VALUE input_data, VALUE pathval, VALUE * storage_data);
127
+ static int bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data);
112
128
  struct s2o_data;
113
129
  struct i2o_data;
114
130
  struct i2s_data;
@@ -141,15 +157,31 @@ Init_bootsnap(void)
141
157
  current_ruby_platform = get_ruby_platform();
142
158
 
143
159
  uncompilable = rb_intern("__bootsnap_uncompilable__");
160
+ instrumentation_method = rb_intern("_instrument");
144
161
 
162
+ sym_miss = ID2SYM(rb_intern("miss"));
163
+ rb_global_variable(&sym_miss);
164
+
165
+ sym_stale = ID2SYM(rb_intern("stale"));
166
+ rb_global_variable(&sym_stale);
167
+
168
+ rb_define_module_function(rb_mBootsnap, "instrumentation_enabled=", bs_instrumentation_enabled_set, 1);
145
169
  rb_define_module_function(rb_mBootsnap_CompileCache_Native, "coverage_running?", bs_rb_coverage_running, 0);
146
- rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch, 3);
170
+ rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch, 4);
171
+ rb_define_module_function(rb_mBootsnap_CompileCache_Native, "precompile", bs_rb_precompile, 3);
147
172
  rb_define_module_function(rb_mBootsnap_CompileCache_Native, "compile_option_crc32=", bs_compile_option_crc32_set, 1);
148
173
 
149
174
  current_umask = umask(0777);
150
175
  umask(current_umask);
151
176
  }
152
177
 
178
+ static VALUE
179
+ bs_instrumentation_enabled_set(VALUE self, VALUE enabled)
180
+ {
181
+ instrumentation_enabled = RTEST(enabled);
182
+ return enabled;
183
+ }
184
+
153
185
  /*
154
186
  * Bootsnap's ruby code registers a hook that notifies us via this function
155
187
  * when compile_option changes. These changes invalidate all existing caches.
@@ -178,7 +210,7 @@ bs_compile_option_crc32_set(VALUE self, VALUE crc32_v)
178
210
  * - 32 bits doesn't feel collision-resistant enough; 64 is nice.
179
211
  */
180
212
  static uint64_t
181
- fnv1a_64_iter(uint64_t h, const char *str)
213
+ fnv1a_64_iter_cstr(uint64_t h, const char *str)
182
214
  {
183
215
  unsigned char *s = (unsigned char *)str;
184
216
 
@@ -191,7 +223,21 @@ fnv1a_64_iter(uint64_t h, const char *str)
191
223
  }
192
224
 
193
225
  static uint64_t
194
- fnv1a_64(const char *str)
226
+ fnv1a_64_iter(uint64_t h, const VALUE str)
227
+ {
228
+ unsigned char *s = (unsigned char *)RSTRING_PTR(str);
229
+ unsigned char *str_end = (unsigned char *)RSTRING_PTR(str) + RSTRING_LEN(str);
230
+
231
+ while (s < str_end) {
232
+ h ^= (uint64_t)*s++;
233
+ h += (h << 1) + (h << 4) + (h << 5) + (h << 7) + (h << 8) + (h << 40);
234
+ }
235
+
236
+ return h;
237
+ }
238
+
239
+ static uint64_t
240
+ fnv1a_64(const VALUE str)
195
241
  {
196
242
  uint64_t h = (uint64_t)0xcbf29ce484222325ULL;
197
243
  return fnv1a_64_iter(h, str);
@@ -212,7 +258,7 @@ get_ruby_revision(void)
212
258
  } else {
213
259
  uint64_t hash;
214
260
 
215
- hash = fnv1a_64(StringValueCStr(ruby_revision));
261
+ hash = fnv1a_64(ruby_revision);
216
262
  return (uint32_t)(hash >> 32);
217
263
  }
218
264
  }
@@ -232,16 +278,19 @@ get_ruby_platform(void)
232
278
  VALUE ruby_platform;
233
279
 
234
280
  ruby_platform = rb_const_get(rb_cObject, rb_intern("RUBY_PLATFORM"));
235
- hash = fnv1a_64(RSTRING_PTR(ruby_platform));
281
+ hash = fnv1a_64(ruby_platform);
236
282
 
237
283
  #ifdef _WIN32
238
284
  return (uint32_t)(hash >> 32) ^ (uint32_t)GetVersion();
285
+ #elif defined(__GLIBC__)
286
+ hash = fnv1a_64_iter_cstr(hash, gnu_get_libc_version());
287
+ return (uint32_t)(hash >> 32);
239
288
  #else
240
289
  struct utsname utsname;
241
290
 
242
291
  /* Not worth crashing if this fails; lose extra cache invalidation potential */
243
292
  if (uname(&utsname) >= 0) {
244
- hash = fnv1a_64_iter(hash, utsname.version);
293
+ hash = fnv1a_64_iter_cstr(hash, utsname.version);
245
294
  }
246
295
 
247
296
  return (uint32_t)(hash >> 32);
@@ -256,14 +305,13 @@ get_ruby_platform(void)
256
305
  * The path will look something like: <cachedir>/12/34567890abcdef
257
306
  */
258
307
  static void
259
- bs_cache_path(const char * cachedir, const char * path, char ** cache_path)
308
+ bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_CACHEPATH_SIZE])
260
309
  {
261
310
  uint64_t hash = fnv1a_64(path);
262
-
263
311
  uint8_t first_byte = (hash >> (64 - 8));
264
312
  uint64_t remainder = hash & 0x00ffffffffffffff;
265
313
 
266
- sprintf(*cache_path, "%s/%02x/%014llx", cachedir, first_byte, remainder);
314
+ sprintf(*cache_path, "%s/%02"PRIx8"/%014"PRIx64, cachedir, first_byte, remainder);
267
315
  }
268
316
 
269
317
  /*
@@ -293,7 +341,7 @@ cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2)
293
341
  * conversions on the ruby VALUE arguments before passing them along.
294
342
  */
295
343
  static VALUE
296
- bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
344
+ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args)
297
345
  {
298
346
  FilePathValue(path_v);
299
347
 
@@ -308,27 +356,51 @@ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
308
356
  char * path = RSTRING_PTR(path_v);
309
357
  char cache_path[MAX_CACHEPATH_SIZE];
310
358
 
311
- { /* generate cache path to cache_path */
312
- char * tmp = (char *)&cache_path;
313
- bs_cache_path(cachedir, path, &tmp);
314
- }
359
+ /* generate cache path to cache_path */
360
+ bs_cache_path(cachedir, path_v, &cache_path);
315
361
 
316
- return bs_fetch(path, path_v, cache_path, handler);
362
+ return bs_fetch(path, path_v, cache_path, handler, args);
317
363
  }
318
364
 
365
+ /*
366
+ * Entrypoint for Bootsnap::CompileCache::Native.precompile.
367
+ * Similar to fetch, but it only generate the cache if missing
368
+ * and doesn't return the content.
369
+ */
370
+ static VALUE
371
+ bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
372
+ {
373
+ FilePathValue(path_v);
374
+
375
+ Check_Type(cachedir_v, T_STRING);
376
+ Check_Type(path_v, T_STRING);
377
+
378
+ if (RSTRING_LEN(cachedir_v) > MAX_CACHEDIR_SIZE) {
379
+ rb_raise(rb_eArgError, "cachedir too long");
380
+ }
381
+
382
+ char * cachedir = RSTRING_PTR(cachedir_v);
383
+ char * path = RSTRING_PTR(path_v);
384
+ char cache_path[MAX_CACHEPATH_SIZE];
385
+
386
+ /* generate cache path to cache_path */
387
+ bs_cache_path(cachedir, path_v, &cache_path);
388
+
389
+ return bs_precompile(path, path_v, cache_path, handler);
390
+ }
319
391
  /*
320
392
  * Open the file we want to load/cache and generate a cache key for it if it
321
393
  * was loaded.
322
394
  */
323
395
  static int
324
- open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenance)
396
+ open_current_file(char * path, struct bs_cache_key * key, const char ** errno_provenance)
325
397
  {
326
398
  struct stat statbuf;
327
399
  int fd;
328
400
 
329
401
  fd = open(path, O_RDONLY);
330
402
  if (fd < 0) {
331
- *errno_provenance = (char *)"bs_fetch:open_current_file:open";
403
+ *errno_provenance = "bs_fetch:open_current_file:open";
332
404
  return fd;
333
405
  }
334
406
  #ifdef _WIN32
@@ -336,7 +408,7 @@ open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenan
336
408
  #endif
337
409
 
338
410
  if (fstat(fd, &statbuf) < 0) {
339
- *errno_provenance = (char *)"bs_fetch:open_current_file:fstat";
411
+ *errno_provenance = "bs_fetch:open_current_file:fstat";
340
412
  close(fd);
341
413
  return -1;
342
414
  }
@@ -352,7 +424,8 @@ open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenan
352
424
  }
353
425
 
354
426
  #define ERROR_WITH_ERRNO -1
355
- #define CACHE_MISSING_OR_INVALID -2
427
+ #define CACHE_MISS -2
428
+ #define CACHE_STALE -3
356
429
 
357
430
  /*
358
431
  * Read the cache key from the given fd, which must have position 0 (e.g.
@@ -360,15 +433,16 @@ open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenan
360
433
  *
361
434
  * Possible return values:
362
435
  * - 0 (OK, key was loaded)
363
- * - CACHE_MISSING_OR_INVALID (-2)
364
436
  * - ERROR_WITH_ERRNO (-1, errno is set)
437
+ * - CACHE_MISS (-2)
438
+ * - CACHE_STALE (-3)
365
439
  */
366
440
  static int
367
441
  bs_read_key(int fd, struct bs_cache_key * key)
368
442
  {
369
443
  ssize_t nread = read(fd, key, KEY_SIZE);
370
444
  if (nread < 0) return ERROR_WITH_ERRNO;
371
- if (nread < KEY_SIZE) return CACHE_MISSING_OR_INVALID;
445
+ if (nread < KEY_SIZE) return CACHE_STALE;
372
446
  return 0;
373
447
  }
374
448
 
@@ -378,19 +452,19 @@ bs_read_key(int fd, struct bs_cache_key * key)
378
452
  *
379
453
  * Possible return values:
380
454
  * - 0 (OK, key was loaded)
381
- * - CACHE_MISSING_OR_INVALID (-2)
455
+ * - CACHE_MISS (-2)
456
+ * - CACHE_STALE (-3)
382
457
  * - ERROR_WITH_ERRNO (-1, errno is set)
383
458
  */
384
459
  static int
385
- open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_provenance)
460
+ open_cache_file(const char * path, struct bs_cache_key * key, const char ** errno_provenance)
386
461
  {
387
462
  int fd, res;
388
463
 
389
464
  fd = open(path, O_RDONLY);
390
465
  if (fd < 0) {
391
- *errno_provenance = (char *)"bs_fetch:open_cache_file:open";
392
- if (errno == ENOENT) return CACHE_MISSING_OR_INVALID;
393
- return ERROR_WITH_ERRNO;
466
+ *errno_provenance = "bs_fetch:open_cache_file:open";
467
+ return CACHE_MISS;
394
468
  }
395
469
  #ifdef _WIN32
396
470
  setmode(fd, O_BINARY);
@@ -398,7 +472,7 @@ open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_prov
398
472
 
399
473
  res = bs_read_key(fd, key);
400
474
  if (res < 0) {
401
- *errno_provenance = (char *)"bs_fetch:open_cache_file:read";
475
+ *errno_provenance = "bs_fetch:open_cache_file:read";
402
476
  close(fd);
403
477
  return res;
404
478
  }
@@ -422,7 +496,7 @@ open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_prov
422
496
  * or exception, will be the final data returnable to the user.
423
497
  */
424
498
  static int
425
- fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag, char ** errno_provenance)
499
+ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE * output_data, int * exception_tag, const char ** errno_provenance)
426
500
  {
427
501
  char * data = NULL;
428
502
  ssize_t nread;
@@ -431,7 +505,7 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data,
431
505
  VALUE storage_data;
432
506
 
433
507
  if (data_size > 100000000000) {
434
- *errno_provenance = (char *)"bs_fetch:fetch_cached_data:datasize";
508
+ *errno_provenance = "bs_fetch:fetch_cached_data:datasize";
435
509
  errno = EINVAL; /* because wtf? */
436
510
  ret = -1;
437
511
  goto done;
@@ -439,18 +513,18 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data,
439
513
  data = ALLOC_N(char, data_size);
440
514
  nread = read(fd, data, data_size);
441
515
  if (nread < 0) {
442
- *errno_provenance = (char *)"bs_fetch:fetch_cached_data:read";
516
+ *errno_provenance = "bs_fetch:fetch_cached_data:read";
443
517
  ret = -1;
444
518
  goto done;
445
519
  }
446
520
  if (nread != data_size) {
447
- ret = CACHE_MISSING_OR_INVALID;
521
+ ret = CACHE_STALE;
448
522
  goto done;
449
523
  }
450
524
 
451
- storage_data = rb_str_new_static(data, data_size);
525
+ storage_data = rb_str_new(data, data_size);
452
526
 
453
- *exception_tag = bs_storage_to_output(handler, storage_data, output_data);
527
+ *exception_tag = bs_storage_to_output(handler, args, storage_data, output_data);
454
528
  ret = 0;
455
529
  done:
456
530
  if (data != NULL) xfree(data);
@@ -491,29 +565,36 @@ mkpath(char * file_path, mode_t mode)
491
565
  * path.
492
566
  */
493
567
  static int
494
- atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char ** errno_provenance)
568
+ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, const char ** errno_provenance)
495
569
  {
496
570
  char template[MAX_CACHEPATH_SIZE + 20];
497
571
  char * tmp_path;
498
- int fd, ret;
572
+ int fd, ret, attempt;
499
573
  ssize_t nwrite;
500
574
 
501
- tmp_path = strncpy(template, path, MAX_CACHEPATH_SIZE);
502
- strcat(tmp_path, ".tmp.XXXXXX");
575
+ for (attempt = 0; attempt < MAX_CREATE_TEMPFILE_ATTEMPT; ++attempt) {
576
+ tmp_path = strncpy(template, path, MAX_CACHEPATH_SIZE);
577
+ strcat(tmp_path, ".tmp.XXXXXX");
503
578
 
504
- // mkstemp modifies the template to be the actual created path
505
- fd = mkstemp(tmp_path);
506
- if (fd < 0) {
507
- if (mkpath(tmp_path, 0775) < 0) {
508
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:mkpath";
509
- return -1;
510
- }
511
- fd = open(tmp_path, O_WRONLY | O_CREAT, 0664);
512
- if (fd < 0) {
513
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:open";
579
+ // mkstemp modifies the template to be the actual created path
580
+ fd = mkstemp(tmp_path);
581
+ if (fd > 0) break;
582
+
583
+ if (attempt == 0 && mkpath(tmp_path, 0775) < 0) {
584
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:mkpath";
514
585
  return -1;
515
586
  }
516
587
  }
588
+ if (fd < 0) {
589
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:mkstemp";
590
+ return -1;
591
+ }
592
+
593
+ if (chmod(tmp_path, 0644) < 0) {
594
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:chmod";
595
+ return -1;
596
+ }
597
+
517
598
  #ifdef _WIN32
518
599
  setmode(fd, O_BINARY);
519
600
  #endif
@@ -521,11 +602,11 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
521
602
  key->data_size = RSTRING_LEN(data);
522
603
  nwrite = write(fd, key, KEY_SIZE);
523
604
  if (nwrite < 0) {
524
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:write";
605
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:write";
525
606
  return -1;
526
607
  }
527
608
  if (nwrite != KEY_SIZE) {
528
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:keysize";
609
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:keysize";
529
610
  errno = EIO; /* Lies but whatever */
530
611
  return -1;
531
612
  }
@@ -533,7 +614,7 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
533
614
  nwrite = write(fd, RSTRING_PTR(data), RSTRING_LEN(data));
534
615
  if (nwrite < 0) return -1;
535
616
  if (nwrite != RSTRING_LEN(data)) {
536
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:writelength";
617
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:writelength";
537
618
  errno = EIO; /* Lies but whatever */
538
619
  return -1;
539
620
  }
@@ -541,12 +622,12 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
541
622
  close(fd);
542
623
  ret = rename(tmp_path, path);
543
624
  if (ret < 0) {
544
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:rename";
625
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:rename";
545
626
  return -1;
546
627
  }
547
628
  ret = chmod(path, 0664 & ~current_umask);
548
629
  if (ret < 0) {
549
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:chmod";
630
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:chmod";
550
631
  }
551
632
  return ret;
552
633
  }
@@ -555,13 +636,13 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
555
636
  /* Read contents from an fd, whose contents are asserted to be +size+ bytes
556
637
  * long, into a buffer */
557
638
  static ssize_t
558
- bs_read_contents(int fd, size_t size, char ** contents, char ** errno_provenance)
639
+ bs_read_contents(int fd, size_t size, char ** contents, const char ** errno_provenance)
559
640
  {
560
641
  ssize_t nread;
561
642
  *contents = ALLOC_N(char, size);
562
643
  nread = read(fd, *contents, size);
563
644
  if (nread < 0) {
564
- *errno_provenance = (char *)"bs_fetch:bs_read_contents:read";
645
+ *errno_provenance = "bs_fetch:bs_read_contents:read";
565
646
  }
566
647
  return nread;
567
648
  }
@@ -611,13 +692,13 @@ bs_read_contents(int fd, size_t size, char ** contents, char ** errno_provenance
611
692
  * - Return storage_to_output(storage_data)
612
693
  */
613
694
  static VALUE
614
- bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
695
+ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args)
615
696
  {
616
697
  struct bs_cache_key cached_key, current_key;
617
698
  char * contents = NULL;
618
699
  int cache_fd = -1, current_fd = -1;
619
700
  int res, valid_cache = 0, exception_tag = 0;
620
- char * errno_provenance = NULL;
701
+ const char * errno_provenance = NULL;
621
702
 
622
703
  VALUE input_data; /* data read from source file, e.g. YAML or ruby source */
623
704
  VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
@@ -631,26 +712,34 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
631
712
 
632
713
  /* Open the cache key if it exists, and read its cache key in */
633
714
  cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance);
634
- if (cache_fd == CACHE_MISSING_OR_INVALID) {
715
+ if (cache_fd == CACHE_MISS || cache_fd == CACHE_STALE) {
635
716
  /* This is ok: valid_cache remains false, we re-populate it. */
717
+ if (RB_UNLIKELY(instrumentation_enabled)) {
718
+ rb_funcall(rb_mBootsnap, instrumentation_method, 2, cache_fd == CACHE_MISS ? sym_miss : sym_stale, path_v);
719
+ }
636
720
  } else if (cache_fd < 0) {
637
721
  goto fail_errno;
638
722
  } else {
639
723
  /* True if the cache existed and no invalidating changes have occurred since
640
724
  * it was generated. */
641
725
  valid_cache = cache_key_equal(&current_key, &cached_key);
726
+ if (RB_UNLIKELY(instrumentation_enabled)) {
727
+ if (!valid_cache) {
728
+ rb_funcall(rb_mBootsnap, instrumentation_method, 2, sym_stale, path_v);
729
+ }
730
+ }
642
731
  }
643
732
 
644
733
  if (valid_cache) {
645
734
  /* Fetch the cache data and return it if we're able to load it successfully */
646
735
  res = fetch_cached_data(
647
- cache_fd, (ssize_t)cached_key.data_size, handler,
736
+ cache_fd, (ssize_t)cached_key.data_size, handler, args,
648
737
  &output_data, &exception_tag, &errno_provenance
649
738
  );
650
- if (exception_tag != 0) goto raise;
651
- else if (res == CACHE_MISSING_OR_INVALID) valid_cache = 0;
652
- else if (res == ERROR_WITH_ERRNO) goto fail_errno;
653
- else if (!NIL_P(output_data)) goto succeed; /* fast-path, goal */
739
+ if (exception_tag != 0) goto raise;
740
+ else if (res == CACHE_MISS || res == CACHE_STALE) valid_cache = 0;
741
+ else if (res == ERROR_WITH_ERRNO) goto fail_errno;
742
+ else if (!NIL_P(output_data)) goto succeed; /* fast-path, goal */
654
743
  }
655
744
  close(cache_fd);
656
745
  cache_fd = -1;
@@ -658,37 +747,39 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
658
747
 
659
748
  /* Read the contents of the source file into a buffer */
660
749
  if (bs_read_contents(current_fd, current_key.size, &contents, &errno_provenance) < 0) goto fail_errno;
661
- input_data = rb_str_new_static(contents, current_key.size);
750
+ input_data = rb_str_new(contents, current_key.size);
662
751
 
663
752
  /* Try to compile the input_data using input_to_storage(input_data) */
664
- exception_tag = bs_input_to_storage(handler, input_data, path_v, &storage_data);
753
+ exception_tag = bs_input_to_storage(handler, args, input_data, path_v, &storage_data);
665
754
  if (exception_tag != 0) goto raise;
666
755
  /* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
667
756
  * to cache anything; just return input_to_output(input_data) */
668
757
  if (storage_data == uncompilable) {
669
- bs_input_to_output(handler, input_data, &output_data, &exception_tag);
758
+ bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
670
759
  if (exception_tag != 0) goto raise;
671
760
  goto succeed;
672
761
  }
673
762
  /* If storage_data isn't a string, we can't cache it */
674
763
  if (!RB_TYPE_P(storage_data, T_STRING)) goto invalid_type_storage_data;
675
764
 
676
- /* Write the cache key and storage_data to the cache directory */
677
- res = atomic_write_cache_file(cache_path, &current_key, storage_data, &errno_provenance);
678
- if (res < 0) goto fail_errno;
765
+ /* Attempt to write the cache key and storage_data to the cache directory.
766
+ * We do however ignore any failures to persist the cache, as it's better
767
+ * to move along, than to interrupt the process.
768
+ */
769
+ atomic_write_cache_file(cache_path, &current_key, storage_data, &errno_provenance);
679
770
 
680
771
  /* Having written the cache, now convert storage_data to output_data */
681
- exception_tag = bs_storage_to_output(handler, storage_data, &output_data);
772
+ exception_tag = bs_storage_to_output(handler, args, storage_data, &output_data);
682
773
  if (exception_tag != 0) goto raise;
683
774
 
684
775
  /* If output_data is nil, delete the cache entry and generate the output
685
776
  * using input_to_output */
686
777
  if (NIL_P(output_data)) {
687
778
  if (unlink(cache_path) < 0) {
688
- errno_provenance = (char *)"bs_fetch:unlink";
779
+ errno_provenance = "bs_fetch:unlink";
689
780
  goto fail_errno;
690
781
  }
691
- bs_input_to_output(handler, input_data, &output_data, &exception_tag);
782
+ bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
692
783
  if (exception_tag != 0) goto raise;
693
784
  }
694
785
 
@@ -719,6 +810,79 @@ invalid_type_storage_data:
719
810
  #undef CLEANUP
720
811
  }
721
812
 
813
+ static VALUE
814
+ bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
815
+ {
816
+ struct bs_cache_key cached_key, current_key;
817
+ char * contents = NULL;
818
+ int cache_fd = -1, current_fd = -1;
819
+ int res, valid_cache = 0, exception_tag = 0;
820
+ const char * errno_provenance = NULL;
821
+
822
+ VALUE input_data; /* data read from source file, e.g. YAML or ruby source */
823
+ VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
824
+
825
+ /* Open the source file and generate a cache key for it */
826
+ current_fd = open_current_file(path, &current_key, &errno_provenance);
827
+ if (current_fd < 0) goto fail;
828
+
829
+ /* Open the cache key if it exists, and read its cache key in */
830
+ cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance);
831
+ if (cache_fd == CACHE_MISS || cache_fd == CACHE_STALE) {
832
+ /* This is ok: valid_cache remains false, we re-populate it. */
833
+ } else if (cache_fd < 0) {
834
+ goto fail;
835
+ } else {
836
+ /* True if the cache existed and no invalidating changes have occurred since
837
+ * it was generated. */
838
+ valid_cache = cache_key_equal(&current_key, &cached_key);
839
+ }
840
+
841
+ if (valid_cache) {
842
+ goto succeed;
843
+ }
844
+
845
+ close(cache_fd);
846
+ cache_fd = -1;
847
+ /* Cache is stale, invalid, or missing. Regenerate and write it out. */
848
+
849
+ /* Read the contents of the source file into a buffer */
850
+ if (bs_read_contents(current_fd, current_key.size, &contents, &errno_provenance) < 0) goto fail;
851
+ input_data = rb_str_new(contents, current_key.size);
852
+
853
+ /* Try to compile the input_data using input_to_storage(input_data) */
854
+ exception_tag = bs_input_to_storage(handler, Qnil, input_data, path_v, &storage_data);
855
+ if (exception_tag != 0) goto fail;
856
+
857
+ /* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
858
+ * to cache anything; just return false */
859
+ if (storage_data == uncompilable) {
860
+ goto fail;
861
+ }
862
+ /* If storage_data isn't a string, we can't cache it */
863
+ if (!RB_TYPE_P(storage_data, T_STRING)) goto fail;
864
+
865
+ /* Write the cache key and storage_data to the cache directory */
866
+ res = atomic_write_cache_file(cache_path, &current_key, storage_data, &errno_provenance);
867
+ if (res < 0) goto fail;
868
+
869
+ goto succeed;
870
+
871
+ #define CLEANUP \
872
+ if (contents != NULL) xfree(contents); \
873
+ if (current_fd >= 0) close(current_fd); \
874
+ if (cache_fd >= 0) close(cache_fd);
875
+
876
+ succeed:
877
+ CLEANUP;
878
+ return Qtrue;
879
+ fail:
880
+ CLEANUP;
881
+ return Qfalse;
882
+ #undef CLEANUP
883
+ }
884
+
885
+
722
886
  /*****************************************************************************/
723
887
  /********************* Handler Wrappers **************************************/
724
888
  /*****************************************************************************
@@ -738,11 +902,13 @@ invalid_type_storage_data:
738
902
 
739
903
  struct s2o_data {
740
904
  VALUE handler;
905
+ VALUE args;
741
906
  VALUE storage_data;
742
907
  };
743
908
 
744
909
  struct i2o_data {
745
910
  VALUE handler;
911
+ VALUE args;
746
912
  VALUE input_data;
747
913
  };
748
914
 
@@ -756,15 +922,16 @@ static VALUE
756
922
  prot_storage_to_output(VALUE arg)
757
923
  {
758
924
  struct s2o_data * data = (struct s2o_data *)arg;
759
- return rb_funcall(data->handler, rb_intern("storage_to_output"), 1, data->storage_data);
925
+ return rb_funcall(data->handler, rb_intern("storage_to_output"), 2, data->storage_data, data->args);
760
926
  }
761
927
 
762
928
  static int
763
- bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data)
929
+ bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * output_data)
764
930
  {
765
931
  int state;
766
932
  struct s2o_data s2o_data = {
767
933
  .handler = handler,
934
+ .args = args,
768
935
  .storage_data = storage_data,
769
936
  };
770
937
  *output_data = rb_protect(prot_storage_to_output, (VALUE)&s2o_data, &state);
@@ -772,10 +939,11 @@ bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data)
772
939
  }
773
940
 
774
941
  static void
775
- bs_input_to_output(VALUE handler, VALUE input_data, VALUE * output_data, int * exception_tag)
942
+ bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag)
776
943
  {
777
944
  struct i2o_data i2o_data = {
778
945
  .handler = handler,
946
+ .args = args,
779
947
  .input_data = input_data,
780
948
  };
781
949
  *output_data = rb_protect(prot_input_to_output, (VALUE)&i2o_data, exception_tag);
@@ -785,7 +953,7 @@ static VALUE
785
953
  prot_input_to_output(VALUE arg)
786
954
  {
787
955
  struct i2o_data * data = (struct i2o_data *)arg;
788
- return rb_funcall(data->handler, rb_intern("input_to_output"), 1, data->input_data);
956
+ return rb_funcall(data->handler, rb_intern("input_to_output"), 2, data->input_data, data->args);
789
957
  }
790
958
 
791
959
  static VALUE
@@ -796,7 +964,7 @@ try_input_to_storage(VALUE arg)
796
964
  }
797
965
 
798
966
  static VALUE
799
- rescue_input_to_storage(VALUE arg)
967
+ rescue_input_to_storage(VALUE arg, VALUE e)
800
968
  {
801
969
  return uncompilable;
802
970
  }
@@ -812,7 +980,7 @@ prot_input_to_storage(VALUE arg)
812
980
  }
813
981
 
814
982
  static int
815
- bs_input_to_storage(VALUE handler, VALUE input_data, VALUE pathval, VALUE * storage_data)
983
+ bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data)
816
984
  {
817
985
  int state;
818
986
  struct i2s_data i2s_data = {