bootsnap 1.1.8-java → 1.5.0-java

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 23ac7be6b543be8182cd7acf406e53c79e3afc9e
4
- data.tar.gz: dd1314fa6f3105181d2ccf741f1a0f6d1cff77a2
2
+ SHA256:
3
+ metadata.gz: a6981435a732015b8043d7ffc3a63c0603b6f1de300db2610becafc93c25b91e
4
+ data.tar.gz: d977b2ee4969224edd10530a4cb4332bcaea0298f8f147e4e3aaf846cbc584ab
5
5
  SHA512:
6
- metadata.gz: 07086a8ae3f6fdc1090efac81638850ffdb155f89e5ef40020efd2ea46744b0e6dacfbb6aa4d5b0feb2e0f50896f4e7b8f90d7683afac1c44cb61ae69c43738e
7
- data.tar.gz: df363388579768bcfa35ca168ebdde47b6305c2c2a13bad5a99be4174a2fd8647ca52aef412bae02064c1faecbfc90b4304fbd43d3871c0b4a58c0adf26e451d
6
+ metadata.gz: d544e765dcc1ccd998ffd0c65ee8aeda19e0a8a37cc37a0fbcb9917384283d27902acb81c079ec7c6b3cb1fe2ffb4fa752b1b668ae34bfdfc0b695cf943159bb
7
+ data.tar.gz: 7036e0b6c86ada90a48fe5d384b029c2c2f383e3231d3bcab43cc6bb765ef49cce4dc785120bac88c293aae2f24f4e23ba6d15ba9eb5ffa953eb0f8843e5e588
data/CHANGELOG.md CHANGED
@@ -1,3 +1,92 @@
1
+ # 1.5.0
2
+
3
+ * Add a command line to statically precompile the ISeq cache. (#326)
4
+
5
+ # 1.4.9
6
+
7
+ * [Windows support](https://github.com/Shopify/bootsnap/pull/319)
8
+ * [Fix potential crash](https://github.com/Shopify/bootsnap/pull/322)
9
+
10
+ # 1.4.8
11
+
12
+ * [Prevent FallbackScan from polluting exception cause](https://github.com/Shopify/bootsnap/pull/314)
13
+
14
+ # 1.4.7
15
+
16
+ * Various performance enhancements
17
+ * Fix race condition in heavy concurrent load scenarios that would cause bootsnap to raise
18
+
19
+ # 1.4.6
20
+
21
+ * Fix bug that was erroneously considering that files containing `.` in the names were being
22
+ required if a different file with the same name was already being required
23
+
24
+ Example:
25
+
26
+ require 'foo'
27
+ require 'foo.en'
28
+
29
+ Before bootsnap was considering `foo.en` to be the same file as `foo`
30
+
31
+ * Use glibc as part of the ruby_platform cache key
32
+
33
+ # 1.4.5
34
+
35
+ * MRI 2.7 support
36
+ * Fixed concurrency bugs
37
+
38
+ # 1.4.4
39
+
40
+ * Disable ISeq cache in `bootsnap/setup` by default in Ruby 2.5
41
+
42
+ # 1.4.3
43
+
44
+ * Fix some cache permissions and umask issues after switch to mkstemp
45
+
46
+ # 1.4.2
47
+
48
+ * Fix bug when removing features loaded by relative path from `$LOADED_FEATURES`
49
+ * Fix bug with propagation of `NameError` up from nested calls to `require`
50
+
51
+ # 1.4.1
52
+
53
+ * Don't register change observers to frozen objects.
54
+
55
+ # 1.4.0
56
+
57
+ * When running in development mode, always fall back to a full path scan on LoadError, making
58
+ bootsnap more able to detect newly-created files. (#230)
59
+ * Respect `$LOADED_FEATURES.delete` in order to support code reloading, for integration with
60
+ Zeitwerk. (#230)
61
+ * Minor performance improvement: flow-control exceptions no longer generate backtraces.
62
+ * Better support for requiring from environments where some features are not supported (especially
63
+ JRuby). (#226)k
64
+ * More robust handling of OS errors when creating files. (#225)
65
+
66
+ # 1.3.2
67
+
68
+ * Fix Spring + Bootsnap incompatibility when there are files with similar names.
69
+ * Fix `YAML.load_file` monkey patch to keep accepting File objects as arguments.
70
+ * Fix the API for `ActiveSupport::Dependencies#autoloadable_module?`.
71
+ * Some performance improvements.
72
+
73
+ # 1.3.1
74
+
75
+ * Change load path scanning to more correctly follow symlinks.
76
+
77
+ # 1.3.0
78
+
79
+ * Handle cases where load path entries are symlinked (https://github.com/Shopify/bootsnap/pull/136)
80
+
81
+ # 1.2.1
82
+
83
+ * Fix method visibility of `Kernel#require`.
84
+
85
+ # 1.2.0
86
+
87
+ * Add `LoadedFeaturesIndex` to preserve fix a common bug related to `LOAD_PATH` modifications after
88
+ loading bootsnap.
89
+
1
90
  # 1.1.8
2
91
 
3
92
  * Don't cache YAML documents with `!ruby/object`
data/README.md CHANGED
@@ -1,16 +1,21 @@
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
3
  Bootsnap is a library that plugs into Ruby, with optional support for `ActiveSupport` and `YAML`,
4
4
  to optimize and cache expensive computations. See [How Does This Work](#how-does-this-work).
5
5
 
6
6
  #### Performance
7
- - [Discourse](https://github.com/discourse/discourse) reports a boot time reduction of approximately 50%, from roughly 6 to 3 seconds on one machine;
7
+
8
+ - [Discourse](https://github.com/discourse/discourse) reports a boot time reduction of approximately
9
+ 50%, from roughly 6 to 3 seconds on one machine;
8
10
  - One of our smaller internal apps also sees a reduction of 50%, from 3.6 to 1.8 seconds;
9
- - The core Shopify platform -- a rather large monolithic application -- boots about 75% faster, dropping from around 25s to 6.5s.
11
+ - The core Shopify platform -- a rather large monolithic application -- boots about 75% faster,
12
+ dropping from around 25s to 6.5s.
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.
10
15
 
11
16
  ## Usage
12
17
 
13
- This gem works on MacOS and Linux.
18
+ This gem works on macOS and Linux.
14
19
 
15
20
  Add `bootsnap` to your `Gemfile`:
16
21
 
@@ -24,6 +29,17 @@ If you are using Rails, add this to `config/boot.rb` immediately after `require
24
29
  require 'bootsnap/setup'
25
30
  ```
26
31
 
32
+ Note that bootsnap writes to `tmp/cache`, and that directory *must* be writable. Rails will fail to
33
+ boot if it is not. If this is unacceptable (e.g. you are running in a read-only container and
34
+ unwilling to mount in a writable tmpdir), you should remove this line or wrap it in a conditional.
35
+
36
+ **Note also that bootsnap will never clean up its own cache: this is left up to you. Depending on your
37
+ deployment strategy, you may need to periodically purge `tmp/cache/bootsnap*`. If you notice deploys
38
+ getting progressively slower, this is almost certainly the cause.**
39
+
40
+ It's technically possible to simply specify `gem 'bootsnap', require: 'bootsnap/setup'`, but it's
41
+ important to load Bootsnap as early as possible to get maximum performance improvement.
42
+
27
43
  You can see how this require works [here](https://github.com/Shopify/bootsnap/blob/master/lib/bootsnap/setup.rb).
28
44
 
29
45
  If you are not using Rails, or if you are but want more control over things, add this to your
@@ -38,12 +54,14 @@ Bootsnap.setup(
38
54
  development_mode: env == 'development', # Current working environment, e.g. RACK_ENV, RAILS_ENV, etc
39
55
  load_path_cache: true, # Optimize the LOAD_PATH with a cache
40
56
  autoload_paths_cache: true, # Optimize ActiveSupport autoloads with cache
41
- disable_trace: true, # (Alpha) Set `RubyVM::InstructionSequence.compile_option = { trace_instruction: false }`
57
+ disable_trace: true, # Set `RubyVM::InstructionSequence.compile_option = { trace_instruction: false }`
42
58
  compile_cache_iseq: true, # Compile Ruby code into ISeq cache, breaks coverage reporting.
43
59
  compile_cache_yaml: true # Compile YAML into a cache
44
60
  )
45
61
  ```
46
62
 
63
+ **Note that `disable_trace` will break debuggers and tracing.**
64
+
47
65
  **Protip:** You can replace `require 'bootsnap'` with `BootLib::Require.from_gem('bootsnap',
48
66
  'bootsnap')` using [this trick](https://github.com/Shopify/bootsnap/wiki/Bootlib::Require). This
49
67
  will help optimize boot time further if you have an extremely large `$LOAD_PATH`.
@@ -196,7 +214,7 @@ Bootsnap writes a cache file containing a 64 byte header followed by the cache c
196
214
  is a cache key including several fields:
197
215
 
198
216
  * `version`, hardcoded in bootsnap. Essentially a schema version;
199
- * `os_version`, A hash of the current kernel version (on macOS, BSD) or glibc version (on Linux);
217
+ * `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)
200
218
  * `compile_option`, which changes with `RubyVM::InstructionSequence.compile_option` does;
201
219
  * `ruby_revision`, the version of Ruby this was compiled with;
202
220
  * `size`, the size of the source file;
@@ -275,3 +293,25 @@ open /c/nope.bundle -> -1
275
293
  ```
276
294
  # (nothing!)
277
295
  ```
296
+
297
+ ## Precompilation
298
+
299
+ In development environments the bootsnap compilation cache is generated on the fly when source files are loaded.
300
+ But in production environments, such as docker images, you might need to precompile the cache.
301
+
302
+ To do so you can use the `bootsnap precompile` command.
303
+
304
+ Example:
305
+
306
+ ```bash
307
+ $ bundle exec bootsnap precompile --gemfile app/ lib/
308
+ ```
309
+
310
+ ## When not to use Bootsnap
311
+
312
+ *Alternative engines*: Bootsnap is pretty reliant on MRI features, and parts are disabled entirely on alternative ruby
313
+ engines.
314
+
315
+ *Non-local filesystems*: Bootsnap depends on `tmp/cache` (or whatever you set its cache directory
316
+ to) being on a relatively fast filesystem. If you put it on a network mount, bootsnap is very likely
317
+ to slow your application down quite a lot.
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
@@ -21,6 +21,9 @@
21
21
  #ifndef _WIN32
22
22
  #include <sys/utsname.h>
23
23
  #endif
24
+ #ifdef __GLIBC__
25
+ #include <gnu/libc-version.h>
26
+ #endif
24
27
 
25
28
  /* 1000 is an arbitrary limit; FNV64 plus some slashes brings the cap down to
26
29
  * 981 for the cache dir */
@@ -29,6 +32,8 @@
29
32
 
30
33
  #define KEY_SIZE 64
31
34
 
35
+ #define MAX_CREATE_TEMPFILE_ATTEMPT 3
36
+
32
37
  /*
33
38
  * An instance of this key is written as the first 64 bytes of each cache file.
34
39
  * The mtime and size members track whether the file contents have changed, and
@@ -74,6 +79,8 @@ static uint32_t current_ruby_platform;
74
79
  static uint32_t current_ruby_revision;
75
80
  /* Invalidates cache when RubyVM::InstructionSequence.compile_option changes */
76
81
  static uint32_t current_compile_option_crc32 = 0;
82
+ /* Current umask */
83
+ static mode_t current_umask;
77
84
 
78
85
  /* Bootsnap::CompileCache::{Native, Uncompilable} */
79
86
  static VALUE rb_mBootsnap;
@@ -84,29 +91,29 @@ static ID uncompilable;
84
91
 
85
92
  /* Functions exposed as module functions on Bootsnap::CompileCache::Native */
86
93
  static VALUE bs_compile_option_crc32_set(VALUE self, VALUE crc32_v);
87
- static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler);
94
+ static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args);
88
95
 
89
96
  /* Helpers */
90
97
  static uint64_t fnv1a_64(const char *str);
91
- static void bs_cache_path(const char * cachedir, const char * path, char ** cache_path);
98
+ static void bs_cache_path(const char * cachedir, const char * path, const char * extra, char (* cache_path)[MAX_CACHEPATH_SIZE]);
92
99
  static int bs_read_key(int fd, struct bs_cache_key * key);
93
100
  static int cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2);
94
- static VALUE bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler);
95
- static int open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenance);
96
- static int fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag, char ** errno_provenance);
97
- static VALUE prot_exception_for_errno(VALUE err);
101
+ static VALUE bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args);
102
+ static int open_current_file(char * path, struct bs_cache_key * key, const char ** errno_provenance);
103
+ 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);
104
+ static uint32_t get_ruby_revision(void);
98
105
  static uint32_t get_ruby_platform(void);
99
106
 
100
107
  /*
101
108
  * Helper functions to call ruby methods on handler object without crashing on
102
109
  * exception.
103
110
  */
104
- static int bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data);
111
+ static int bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * output_data);
105
112
  static VALUE prot_storage_to_output(VALUE arg);
106
113
  static VALUE prot_input_to_output(VALUE arg);
107
- static void bs_input_to_output(VALUE handler, VALUE input_data, VALUE * output_data, int * exception_tag);
114
+ static void bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag);
108
115
  static VALUE prot_input_to_storage(VALUE arg);
109
- static int bs_input_to_storage(VALUE handler, VALUE input_data, VALUE pathval, VALUE * storage_data);
116
+ static int bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data);
110
117
  struct s2o_data;
111
118
  struct i2o_data;
112
119
  struct i2s_data;
@@ -135,14 +142,17 @@ Init_bootsnap(void)
135
142
  rb_mBootsnap_CompileCache_Native = rb_define_module_under(rb_mBootsnap_CompileCache, "Native");
136
143
  rb_eBootsnap_CompileCache_Uncompilable = rb_define_class_under(rb_mBootsnap_CompileCache, "Uncompilable", rb_eStandardError);
137
144
 
138
- current_ruby_revision = FIX2INT(rb_const_get(rb_cObject, rb_intern("RUBY_REVISION")));
145
+ current_ruby_revision = get_ruby_revision();
139
146
  current_ruby_platform = get_ruby_platform();
140
147
 
141
148
  uncompilable = rb_intern("__bootsnap_uncompilable__");
142
149
 
143
150
  rb_define_module_function(rb_mBootsnap_CompileCache_Native, "coverage_running?", bs_rb_coverage_running, 0);
144
- rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch, 3);
151
+ rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch, 4);
145
152
  rb_define_module_function(rb_mBootsnap_CompileCache_Native, "compile_option_crc32=", bs_compile_option_crc32_set, 1);
153
+
154
+ current_umask = umask(0777);
155
+ umask(current_umask);
146
156
  }
147
157
 
148
158
  /*
@@ -192,6 +202,26 @@ fnv1a_64(const char *str)
192
202
  return fnv1a_64_iter(h, str);
193
203
  }
194
204
 
205
+ /*
206
+ * Ruby's revision may be Integer or String. CRuby 2.7 or later uses
207
+ * Git commit ID as revision. It's String.
208
+ */
209
+ static uint32_t
210
+ get_ruby_revision(void)
211
+ {
212
+ VALUE ruby_revision;
213
+
214
+ ruby_revision = rb_const_get(rb_cObject, rb_intern("RUBY_REVISION"));
215
+ if (RB_TYPE_P(ruby_revision, RUBY_T_FIXNUM)) {
216
+ return FIX2INT(ruby_revision);
217
+ } else {
218
+ uint64_t hash;
219
+
220
+ hash = fnv1a_64(StringValueCStr(ruby_revision));
221
+ return (uint32_t)(hash >> 32);
222
+ }
223
+ }
224
+
195
225
  /*
196
226
  * When ruby's version doesn't change, but it's recompiled on a different OS
197
227
  * (or OS version), we need to invalidate the cache.
@@ -211,6 +241,9 @@ get_ruby_platform(void)
211
241
 
212
242
  #ifdef _WIN32
213
243
  return (uint32_t)(hash >> 32) ^ (uint32_t)GetVersion();
244
+ #elif defined(__GLIBC__)
245
+ hash = fnv1a_64_iter(hash, gnu_get_libc_version());
246
+ return (uint32_t)(hash >> 32);
214
247
  #else
215
248
  struct utsname utsname;
216
249
 
@@ -231,9 +264,12 @@ get_ruby_platform(void)
231
264
  * The path will look something like: <cachedir>/12/34567890abcdef
232
265
  */
233
266
  static void
234
- bs_cache_path(const char * cachedir, const char * path, char ** cache_path)
267
+ bs_cache_path(const char * cachedir, const char * path, const char * extra, char (* cache_path)[MAX_CACHEPATH_SIZE])
235
268
  {
236
269
  uint64_t hash = fnv1a_64(path);
270
+ if (extra) {
271
+ hash ^= fnv1a_64(extra);
272
+ }
237
273
 
238
274
  uint8_t first_byte = (hash >> (64 - 8));
239
275
  uint64_t remainder = hash & 0x00ffffffffffffff;
@@ -268,8 +304,10 @@ cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2)
268
304
  * conversions on the ruby VALUE arguments before passing them along.
269
305
  */
270
306
  static VALUE
271
- bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
307
+ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args)
272
308
  {
309
+ FilePathValue(path_v);
310
+
273
311
  Check_Type(cachedir_v, T_STRING);
274
312
  Check_Type(path_v, T_STRING);
275
313
 
@@ -280,13 +318,16 @@ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
280
318
  char * cachedir = RSTRING_PTR(cachedir_v);
281
319
  char * path = RSTRING_PTR(path_v);
282
320
  char cache_path[MAX_CACHEPATH_SIZE];
283
-
284
- { /* generate cache path to cache_path */
285
- char * tmp = (char *)&cache_path;
286
- bs_cache_path(cachedir, path, &tmp);
321
+ char * extra = NULL;
322
+ if (!NIL_P(args)) {
323
+ VALUE args_serial = rb_marshal_dump(args, Qnil);
324
+ extra = RSTRING_PTR(args_serial);
287
325
  }
288
326
 
289
- return bs_fetch(path, path_v, cache_path, handler);
327
+ /* generate cache path to cache_path */
328
+ bs_cache_path(cachedir, path, extra, &cache_path);
329
+
330
+ return bs_fetch(path, path_v, cache_path, handler, args);
290
331
  }
291
332
 
292
333
  /*
@@ -294,14 +335,14 @@ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
294
335
  * was loaded.
295
336
  */
296
337
  static int
297
- open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenance)
338
+ open_current_file(char * path, struct bs_cache_key * key, const char ** errno_provenance)
298
339
  {
299
340
  struct stat statbuf;
300
341
  int fd;
301
342
 
302
343
  fd = open(path, O_RDONLY);
303
344
  if (fd < 0) {
304
- *errno_provenance = (char *)"bs_fetch:open_current_file:open";
345
+ *errno_provenance = "bs_fetch:open_current_file:open";
305
346
  return fd;
306
347
  }
307
348
  #ifdef _WIN32
@@ -309,7 +350,7 @@ open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenan
309
350
  #endif
310
351
 
311
352
  if (fstat(fd, &statbuf) < 0) {
312
- *errno_provenance = (char *)"bs_fetch:open_current_file:fstat";
353
+ *errno_provenance = "bs_fetch:open_current_file:fstat";
313
354
  close(fd);
314
355
  return -1;
315
356
  }
@@ -355,13 +396,13 @@ bs_read_key(int fd, struct bs_cache_key * key)
355
396
  * - ERROR_WITH_ERRNO (-1, errno is set)
356
397
  */
357
398
  static int
358
- open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_provenance)
399
+ open_cache_file(const char * path, struct bs_cache_key * key, const char ** errno_provenance)
359
400
  {
360
401
  int fd, res;
361
402
 
362
403
  fd = open(path, O_RDONLY);
363
404
  if (fd < 0) {
364
- *errno_provenance = (char *)"bs_fetch:open_cache_file:open";
405
+ *errno_provenance = "bs_fetch:open_cache_file:open";
365
406
  if (errno == ENOENT) return CACHE_MISSING_OR_INVALID;
366
407
  return ERROR_WITH_ERRNO;
367
408
  }
@@ -371,7 +412,7 @@ open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_prov
371
412
 
372
413
  res = bs_read_key(fd, key);
373
414
  if (res < 0) {
374
- *errno_provenance = (char *)"bs_fetch:open_cache_file:read";
415
+ *errno_provenance = "bs_fetch:open_cache_file:read";
375
416
  close(fd);
376
417
  return res;
377
418
  }
@@ -395,7 +436,7 @@ open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_prov
395
436
  * or exception, will be the final data returnable to the user.
396
437
  */
397
438
  static int
398
- fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag, char ** errno_provenance)
439
+ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE * output_data, int * exception_tag, const char ** errno_provenance)
399
440
  {
400
441
  char * data = NULL;
401
442
  ssize_t nread;
@@ -404,7 +445,7 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data,
404
445
  VALUE storage_data;
405
446
 
406
447
  if (data_size > 100000000000) {
407
- *errno_provenance = (char *)"bs_fetch:fetch_cached_data:datasize";
448
+ *errno_provenance = "bs_fetch:fetch_cached_data:datasize";
408
449
  errno = EINVAL; /* because wtf? */
409
450
  ret = -1;
410
451
  goto done;
@@ -412,7 +453,7 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data,
412
453
  data = ALLOC_N(char, data_size);
413
454
  nread = read(fd, data, data_size);
414
455
  if (nread < 0) {
415
- *errno_provenance = (char *)"bs_fetch:fetch_cached_data:read";
456
+ *errno_provenance = "bs_fetch:fetch_cached_data:read";
416
457
  ret = -1;
417
458
  goto done;
418
459
  }
@@ -421,9 +462,9 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data,
421
462
  goto done;
422
463
  }
423
464
 
424
- storage_data = rb_str_new_static(data, data_size);
465
+ storage_data = rb_str_new(data, data_size);
425
466
 
426
- *exception_tag = bs_storage_to_output(handler, storage_data, output_data);
467
+ *exception_tag = bs_storage_to_output(handler, args, storage_data, output_data);
427
468
  ret = 0;
428
469
  done:
429
470
  if (data != NULL) xfree(data);
@@ -464,30 +505,36 @@ mkpath(char * file_path, mode_t mode)
464
505
  * path.
465
506
  */
466
507
  static int
467
- atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char ** errno_provenance)
508
+ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, const char ** errno_provenance)
468
509
  {
469
510
  char template[MAX_CACHEPATH_SIZE + 20];
470
- char * dest;
471
511
  char * tmp_path;
472
- int fd, ret;
512
+ int fd, ret, attempt;
473
513
  ssize_t nwrite;
474
514
 
475
- dest = strncpy(template, path, MAX_CACHEPATH_SIZE);
476
- strcat(dest, ".tmp.XXXXXX");
515
+ for (attempt = 0; attempt < MAX_CREATE_TEMPFILE_ATTEMPT; ++attempt) {
516
+ tmp_path = strncpy(template, path, MAX_CACHEPATH_SIZE);
517
+ strcat(tmp_path, ".tmp.XXXXXX");
477
518
 
478
- tmp_path = mktemp(template);
479
- fd = open(tmp_path, O_WRONLY | O_CREAT, 0664);
480
- if (fd < 0) {
481
- if (mkpath(path, 0775) < 0) {
482
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:mkpath";
483
- return -1;
484
- }
485
- fd = open(tmp_path, O_WRONLY | O_CREAT, 0664);
486
- if (fd < 0) {
487
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:open";
519
+ // mkstemp modifies the template to be the actual created path
520
+ fd = mkstemp(tmp_path);
521
+ if (fd > 0) break;
522
+
523
+ if (attempt == 0 && mkpath(tmp_path, 0775) < 0) {
524
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:mkpath";
488
525
  return -1;
489
526
  }
490
527
  }
528
+ if (fd < 0) {
529
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:mkstemp";
530
+ return -1;
531
+ }
532
+
533
+ if (chmod(tmp_path, 0644) < 0) {
534
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:chmod";
535
+ return -1;
536
+ }
537
+
491
538
  #ifdef _WIN32
492
539
  setmode(fd, O_BINARY);
493
540
  #endif
@@ -495,11 +542,11 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
495
542
  key->data_size = RSTRING_LEN(data);
496
543
  nwrite = write(fd, key, KEY_SIZE);
497
544
  if (nwrite < 0) {
498
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:write";
545
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:write";
499
546
  return -1;
500
547
  }
501
548
  if (nwrite != KEY_SIZE) {
502
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:keysize";
549
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:keysize";
503
550
  errno = EIO; /* Lies but whatever */
504
551
  return -1;
505
552
  }
@@ -507,7 +554,7 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
507
554
  nwrite = write(fd, RSTRING_PTR(data), RSTRING_LEN(data));
508
555
  if (nwrite < 0) return -1;
509
556
  if (nwrite != RSTRING_LEN(data)) {
510
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:writelength";
557
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:writelength";
511
558
  errno = EIO; /* Lies but whatever */
512
559
  return -1;
513
560
  }
@@ -515,38 +562,27 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
515
562
  close(fd);
516
563
  ret = rename(tmp_path, path);
517
564
  if (ret < 0) {
518
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:rename";
565
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:rename";
566
+ return -1;
567
+ }
568
+ ret = chmod(path, 0664 & ~current_umask);
569
+ if (ret < 0) {
570
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:chmod";
519
571
  }
520
572
  return ret;
521
573
  }
522
574
 
523
- /*
524
- * Given an errno value (converted to a ruby Fixnum), return the corresponding
525
- * Errno::* constant. If none is found, return StandardError instead.
526
- */
527
- static VALUE
528
- prot_exception_for_errno(VALUE err)
529
- {
530
- if (err != INT2FIX(0)) {
531
- VALUE mErrno = rb_const_get(rb_cObject, rb_intern("Errno"));
532
- VALUE constants = rb_funcall(mErrno, rb_intern("constants"), 0);
533
- VALUE which = rb_funcall(constants, rb_intern("[]"), 1, err);
534
- return rb_funcall(mErrno, rb_intern("const_get"), 1, which);
535
- }
536
- return rb_eStandardError;
537
- }
538
-
539
575
 
540
576
  /* Read contents from an fd, whose contents are asserted to be +size+ bytes
541
577
  * long, into a buffer */
542
578
  static ssize_t
543
- bs_read_contents(int fd, size_t size, char ** contents, char ** errno_provenance)
579
+ bs_read_contents(int fd, size_t size, char ** contents, const char ** errno_provenance)
544
580
  {
545
581
  ssize_t nread;
546
582
  *contents = ALLOC_N(char, size);
547
583
  nread = read(fd, *contents, size);
548
584
  if (nread < 0) {
549
- *errno_provenance = (char *)"bs_fetch:bs_read_contents:read";
585
+ *errno_provenance = "bs_fetch:bs_read_contents:read";
550
586
  }
551
587
  return nread;
552
588
  }
@@ -596,13 +632,13 @@ bs_read_contents(int fd, size_t size, char ** contents, char ** errno_provenance
596
632
  * - Return storage_to_output(storage_data)
597
633
  */
598
634
  static VALUE
599
- bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
635
+ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args)
600
636
  {
601
637
  struct bs_cache_key cached_key, current_key;
602
638
  char * contents = NULL;
603
639
  int cache_fd = -1, current_fd = -1;
604
640
  int res, valid_cache = 0, exception_tag = 0;
605
- char * errno_provenance = NULL;
641
+ const char * errno_provenance = NULL;
606
642
 
607
643
  VALUE input_data; /* data read from source file, e.g. YAML or ruby source */
608
644
  VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
@@ -629,7 +665,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
629
665
  if (valid_cache) {
630
666
  /* Fetch the cache data and return it if we're able to load it successfully */
631
667
  res = fetch_cached_data(
632
- cache_fd, (ssize_t)cached_key.data_size, handler,
668
+ cache_fd, (ssize_t)cached_key.data_size, handler, args,
633
669
  &output_data, &exception_tag, &errno_provenance
634
670
  );
635
671
  if (exception_tag != 0) goto raise;
@@ -643,15 +679,15 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
643
679
 
644
680
  /* Read the contents of the source file into a buffer */
645
681
  if (bs_read_contents(current_fd, current_key.size, &contents, &errno_provenance) < 0) goto fail_errno;
646
- input_data = rb_str_new_static(contents, current_key.size);
682
+ input_data = rb_str_new(contents, current_key.size);
647
683
 
648
684
  /* Try to compile the input_data using input_to_storage(input_data) */
649
- exception_tag = bs_input_to_storage(handler, input_data, path_v, &storage_data);
685
+ exception_tag = bs_input_to_storage(handler, args, input_data, path_v, &storage_data);
650
686
  if (exception_tag != 0) goto raise;
651
687
  /* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
652
688
  * to cache anything; just return input_to_output(input_data) */
653
689
  if (storage_data == uncompilable) {
654
- bs_input_to_output(handler, input_data, &output_data, &exception_tag);
690
+ bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
655
691
  if (exception_tag != 0) goto raise;
656
692
  goto succeed;
657
693
  }
@@ -663,17 +699,17 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
663
699
  if (res < 0) goto fail_errno;
664
700
 
665
701
  /* Having written the cache, now convert storage_data to output_data */
666
- exception_tag = bs_storage_to_output(handler, storage_data, &output_data);
702
+ exception_tag = bs_storage_to_output(handler, args, storage_data, &output_data);
667
703
  if (exception_tag != 0) goto raise;
668
704
 
669
705
  /* If output_data is nil, delete the cache entry and generate the output
670
706
  * using input_to_output */
671
707
  if (NIL_P(output_data)) {
672
708
  if (unlink(cache_path) < 0) {
673
- errno_provenance = (char *)"bs_fetch:unlink";
709
+ errno_provenance = "bs_fetch:unlink";
674
710
  goto fail_errno;
675
711
  }
676
- bs_input_to_output(handler, input_data, &output_data, &exception_tag);
712
+ bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
677
713
  if (exception_tag != 0) goto raise;
678
714
  }
679
715
 
@@ -689,11 +725,7 @@ succeed:
689
725
  return output_data;
690
726
  fail_errno:
691
727
  CLEANUP;
692
- exception = rb_protect(prot_exception_for_errno, INT2FIX(errno), &res);
693
- if (res) exception = rb_eStandardError;
694
- if (errno_provenance != NULL) {
695
- exception = rb_exc_new_str(exception, rb_str_new2(errno_provenance));
696
- }
728
+ exception = rb_syserr_new(errno, errno_provenance);
697
729
  rb_exc_raise(exception);
698
730
  __builtin_unreachable();
699
731
  raise:
@@ -727,16 +759,19 @@ invalid_type_storage_data:
727
759
 
728
760
  struct s2o_data {
729
761
  VALUE handler;
762
+ VALUE args;
730
763
  VALUE storage_data;
731
764
  };
732
765
 
733
766
  struct i2o_data {
734
767
  VALUE handler;
768
+ VALUE args;
735
769
  VALUE input_data;
736
770
  };
737
771
 
738
772
  struct i2s_data {
739
773
  VALUE handler;
774
+ VALUE args;
740
775
  VALUE input_data;
741
776
  VALUE pathval;
742
777
  };
@@ -745,15 +780,16 @@ static VALUE
745
780
  prot_storage_to_output(VALUE arg)
746
781
  {
747
782
  struct s2o_data * data = (struct s2o_data *)arg;
748
- return rb_funcall(data->handler, rb_intern("storage_to_output"), 1, data->storage_data);
783
+ return rb_funcall(data->handler, rb_intern("storage_to_output"), 2, data->storage_data, data->args);
749
784
  }
750
785
 
751
786
  static int
752
- bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data)
787
+ bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * output_data)
753
788
  {
754
789
  int state;
755
790
  struct s2o_data s2o_data = {
756
791
  .handler = handler,
792
+ .args = args,
757
793
  .storage_data = storage_data,
758
794
  };
759
795
  *output_data = rb_protect(prot_storage_to_output, (VALUE)&s2o_data, &state);
@@ -761,10 +797,11 @@ bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data)
761
797
  }
762
798
 
763
799
  static void
764
- bs_input_to_output(VALUE handler, VALUE input_data, VALUE * output_data, int * exception_tag)
800
+ bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag)
765
801
  {
766
802
  struct i2o_data i2o_data = {
767
803
  .handler = handler,
804
+ .args = args,
768
805
  .input_data = input_data,
769
806
  };
770
807
  *output_data = rb_protect(prot_input_to_output, (VALUE)&i2o_data, exception_tag);
@@ -774,18 +811,18 @@ static VALUE
774
811
  prot_input_to_output(VALUE arg)
775
812
  {
776
813
  struct i2o_data * data = (struct i2o_data *)arg;
777
- return rb_funcall(data->handler, rb_intern("input_to_output"), 1, data->input_data);
814
+ return rb_funcall(data->handler, rb_intern("input_to_output"), 2, data->input_data, data->args);
778
815
  }
779
816
 
780
817
  static VALUE
781
818
  try_input_to_storage(VALUE arg)
782
819
  {
783
820
  struct i2s_data * data = (struct i2s_data *)arg;
784
- return rb_funcall(data->handler, rb_intern("input_to_storage"), 2, data->input_data, data->pathval);
821
+ return rb_funcall(data->handler, rb_intern("input_to_storage"), 3, data->input_data, data->pathval, data->args);
785
822
  }
786
823
 
787
824
  static VALUE
788
- rescue_input_to_storage(VALUE arg)
825
+ rescue_input_to_storage(VALUE arg, VALUE e)
789
826
  {
790
827
  return uncompilable;
791
828
  }
@@ -801,11 +838,12 @@ prot_input_to_storage(VALUE arg)
801
838
  }
802
839
 
803
840
  static int
804
- bs_input_to_storage(VALUE handler, VALUE input_data, VALUE pathval, VALUE * storage_data)
841
+ bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data)
805
842
  {
806
843
  int state;
807
844
  struct i2s_data i2s_data = {
808
845
  .handler = handler,
846
+ .args = args,
809
847
  .input_data = input_data,
810
848
  .pathval = pathval,
811
849
  };