bootsnap 1.4.5 → 1.6.0

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 +46 -0
  3. data/README.md +17 -3
  4. data/exe/bootsnap +5 -0
  5. data/ext/bootsnap/bootsnap.c +183 -65
  6. data/ext/bootsnap/extconf.rb +1 -0
  7. data/lib/bootsnap/bundler.rb +1 -0
  8. data/lib/bootsnap/cli/worker_pool.rb +131 -0
  9. data/lib/bootsnap/cli.rb +246 -0
  10. data/lib/bootsnap/compile_cache/iseq.rb +22 -7
  11. data/lib/bootsnap/compile_cache/yaml.rb +89 -39
  12. data/lib/bootsnap/compile_cache.rb +3 -2
  13. data/lib/bootsnap/explicit_require.rb +1 -0
  14. data/lib/bootsnap/load_path_cache/cache.rb +8 -8
  15. data/lib/bootsnap/load_path_cache/change_observer.rb +2 -1
  16. data/lib/bootsnap/load_path_cache/core_ext/active_support.rb +1 -0
  17. data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +18 -5
  18. data/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb +1 -0
  19. data/lib/bootsnap/load_path_cache/loaded_features_index.rb +33 -10
  20. data/lib/bootsnap/load_path_cache/path.rb +3 -2
  21. data/lib/bootsnap/load_path_cache/path_scanner.rb +39 -26
  22. data/lib/bootsnap/load_path_cache/realpath_cache.rb +5 -5
  23. data/lib/bootsnap/load_path_cache/store.rb +6 -5
  24. data/lib/bootsnap/load_path_cache.rb +1 -1
  25. data/lib/bootsnap/setup.rb +1 -0
  26. data/lib/bootsnap/version.rb +2 -1
  27. data/lib/bootsnap.rb +4 -2
  28. metadata +15 -28
  29. data/.github/CODEOWNERS +0 -2
  30. data/.github/probots.yml +0 -2
  31. data/.gitignore +0 -17
  32. data/.rubocop.yml +0 -20
  33. data/.travis.yml +0 -21
  34. data/CODE_OF_CONDUCT.md +0 -74
  35. data/CONTRIBUTING.md +0 -21
  36. data/Gemfile +0 -8
  37. data/README.jp.md +0 -231
  38. data/Rakefile +0 -12
  39. data/bin/ci +0 -10
  40. data/bin/console +0 -14
  41. data/bin/setup +0 -8
  42. data/bin/test-minimal-support +0 -7
  43. data/bin/testunit +0 -8
  44. data/bootsnap.gemspec +0 -45
  45. data/dev.yml +0 -10
  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: '029d63ba428f470d2cd2ef194d857e20689e7c8e377e2eaaaab83570f366034a'
4
+ data.tar.gz: 36a55f9d12dddef6ea3c4739f586c9236d7f4bc677a8b6747dfd7465d46eeca2
5
5
  SHA512:
6
- metadata.gz: c251990457406cd51726eec8d62e0e1028bbeade6e24f266cb743d6f00f53650841beed9ee8e1795c78febd18ea234691125920c16aa5a95031718a28619bb31
7
- data.tar.gz: 554a885bf2264f088618c41864fb84069d0664b35af0ae4b619c8fbb1cf285c80fad564d36845161f8044c35c6b53a33e66f27eb75c6715f25869af2795c97f6
6
+ metadata.gz: 131ec17c4e4912f387c18778250e689467ebfcc80e91eb884237307af1a57a3c89d4fb20c772fbc0330123a796d631861a9cf145bd32c54183077bbc97d7fa6f
7
+ data.tar.gz: d0c92454c8a5d8b16a25908fd0ae134fc817c5977958bde8424baca2bb6c96e60eb648c9f770bf7c7f58a3dd98871d4e7b0e3a0950c269100fded45ca9fddfa3
data/CHANGELOG.md CHANGED
@@ -1,3 +1,49 @@
1
+ # Unreleased
2
+
3
+ # 1.6.0
4
+
5
+ * Fix a Ruby 2.7/3.0 issue with `YAML.load_file` keyword arguments. (#342)
6
+ * `bootsnap precompile` CLI use multiple processes to complete faster. (#341)
7
+ * `bootsnap precompile` CLI also precompile YAML files. (#340)
8
+ * 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)
9
+ * Changed the compile cache directory from `$BOOTSNAP_CACHE_DIR/bootsnap-compile-cache` to `$BOOTSNAP_CACHE_DIR/bootsnap/compile-cache` for ease of use. (#334)
10
+
11
+ # 1.5.1
12
+
13
+ * Workaround a Ruby bug in InstructionSequence.compile_file. (#332)
14
+
15
+ # 1.5.0
16
+
17
+ * Add a command line to statically precompile the ISeq cache. (#326)
18
+
19
+ # 1.4.9
20
+
21
+ * [Windows support](https://github.com/Shopify/bootsnap/pull/319)
22
+ * [Fix potential crash](https://github.com/Shopify/bootsnap/pull/322)
23
+
24
+ # 1.4.8
25
+
26
+ * [Prevent FallbackScan from polluting exception cause](https://github.com/Shopify/bootsnap/pull/314)
27
+
28
+ # 1.4.7
29
+
30
+ * Various performance enhancements
31
+ * Fix race condition in heavy concurrent load scenarios that would cause bootsnap to raise
32
+
33
+ # 1.4.6
34
+
35
+ * Fix bug that was erroneously considering that files containing `.` in the names were being
36
+ required if a different file with the same name was already being required
37
+
38
+ Example:
39
+
40
+ require 'foo'
41
+ require 'foo.en'
42
+
43
+ Before bootsnap was considering `foo.en` to be the same file as `foo`
44
+
45
+ * Use glibc as part of the ruby_platform cache key
46
+
1
47
  # 1.4.5
2
48
 
3
49
  * MRI 2.7 support
data/README.md CHANGED
@@ -1,4 +1,4 @@
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).
@@ -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
 
@@ -214,7 +215,7 @@ Bootsnap writes a cache file containing a 64 byte header followed by the cache c
214
215
  is a cache key including several fields:
215
216
 
216
217
  * `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);
218
+ * `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
219
  * `compile_option`, which changes with `RubyVM::InstructionSequence.compile_option` does;
219
220
  * `ruby_revision`, the version of Ruby this was compiled with;
220
221
  * `size`, the size of the source file;
@@ -294,6 +295,19 @@ open /c/nope.bundle -> -1
294
295
  # (nothing!)
295
296
  ```
296
297
 
298
+ ## Precompilation
299
+
300
+ In development environments the bootsnap compilation cache is generated on the fly when source files are loaded.
301
+ But in production environments, such as docker images, you might need to precompile the cache.
302
+
303
+ To do so you can use the `bootsnap precompile` command.
304
+
305
+ Example:
306
+
307
+ ```bash
308
+ $ bundle exec bootsnap precompile --gemfile app/ lib/
309
+ ```
310
+
297
311
  ## When not to use Bootsnap
298
312
 
299
313
  *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
@@ -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
@@ -65,7 +70,7 @@ struct bs_cache_key {
65
70
  STATIC_ASSERT(sizeof(struct bs_cache_key) == KEY_SIZE);
66
71
 
67
72
  /* Effectively a schema version. Bumping invalidates all previous caches */
68
- static const uint32_t current_version = 2;
73
+ static const uint32_t current_version = 3;
69
74
 
70
75
  /* hash of e.g. "x86_64-darwin17", invalidating when ruby is recompiled on a
71
76
  * new OS ABI, etc. */
@@ -86,16 +91,18 @@ static ID uncompilable;
86
91
 
87
92
  /* Functions exposed as module functions on Bootsnap::CompileCache::Native */
88
93
  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);
94
+ static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args);
95
+ static VALUE bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler);
90
96
 
91
97
  /* Helpers */
92
98
  static uint64_t fnv1a_64(const char *str);
93
- static void bs_cache_path(const char * cachedir, const char * path, char ** cache_path);
99
+ static void bs_cache_path(const char * cachedir, const char * path, char (* cache_path)[MAX_CACHEPATH_SIZE]);
94
100
  static int bs_read_key(int fd, struct bs_cache_key * key);
95
101
  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);
102
+ static VALUE bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args);
103
+ static VALUE bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler);
104
+ static int open_current_file(char * path, struct bs_cache_key * key, const char ** errno_provenance);
105
+ 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
106
  static uint32_t get_ruby_revision(void);
100
107
  static uint32_t get_ruby_platform(void);
101
108
 
@@ -103,12 +110,12 @@ static uint32_t get_ruby_platform(void);
103
110
  * Helper functions to call ruby methods on handler object without crashing on
104
111
  * exception.
105
112
  */
106
- static int bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data);
113
+ static int bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * output_data);
107
114
  static VALUE prot_storage_to_output(VALUE arg);
108
115
  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);
116
+ static void bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag);
110
117
  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);
118
+ static int bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data);
112
119
  struct s2o_data;
113
120
  struct i2o_data;
114
121
  struct i2s_data;
@@ -143,7 +150,8 @@ Init_bootsnap(void)
143
150
  uncompilable = rb_intern("__bootsnap_uncompilable__");
144
151
 
145
152
  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);
153
+ rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch, 4);
154
+ rb_define_module_function(rb_mBootsnap_CompileCache_Native, "precompile", bs_rb_precompile, 3);
147
155
  rb_define_module_function(rb_mBootsnap_CompileCache_Native, "compile_option_crc32=", bs_compile_option_crc32_set, 1);
148
156
 
149
157
  current_umask = umask(0777);
@@ -236,6 +244,9 @@ get_ruby_platform(void)
236
244
 
237
245
  #ifdef _WIN32
238
246
  return (uint32_t)(hash >> 32) ^ (uint32_t)GetVersion();
247
+ #elif defined(__GLIBC__)
248
+ hash = fnv1a_64_iter(hash, gnu_get_libc_version());
249
+ return (uint32_t)(hash >> 32);
239
250
  #else
240
251
  struct utsname utsname;
241
252
 
@@ -256,10 +267,9 @@ get_ruby_platform(void)
256
267
  * The path will look something like: <cachedir>/12/34567890abcdef
257
268
  */
258
269
  static void
259
- bs_cache_path(const char * cachedir, const char * path, char ** cache_path)
270
+ bs_cache_path(const char * cachedir, const char * path, char (* cache_path)[MAX_CACHEPATH_SIZE])
260
271
  {
261
272
  uint64_t hash = fnv1a_64(path);
262
-
263
273
  uint8_t first_byte = (hash >> (64 - 8));
264
274
  uint64_t remainder = hash & 0x00ffffffffffffff;
265
275
 
@@ -293,7 +303,7 @@ cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2)
293
303
  * conversions on the ruby VALUE arguments before passing them along.
294
304
  */
295
305
  static VALUE
296
- bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
306
+ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args)
297
307
  {
298
308
  FilePathValue(path_v);
299
309
 
@@ -308,27 +318,51 @@ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
308
318
  char * path = RSTRING_PTR(path_v);
309
319
  char cache_path[MAX_CACHEPATH_SIZE];
310
320
 
311
- { /* generate cache path to cache_path */
312
- char * tmp = (char *)&cache_path;
313
- bs_cache_path(cachedir, path, &tmp);
314
- }
321
+ /* generate cache path to cache_path */
322
+ bs_cache_path(cachedir, path, &cache_path);
315
323
 
316
- return bs_fetch(path, path_v, cache_path, handler);
324
+ return bs_fetch(path, path_v, cache_path, handler, args);
317
325
  }
318
326
 
327
+ /*
328
+ * Entrypoint for Bootsnap::CompileCache::Native.precompile.
329
+ * Similar to fetch, but it only generate the cache if missing
330
+ * and doesn't return the content.
331
+ */
332
+ static VALUE
333
+ bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
334
+ {
335
+ FilePathValue(path_v);
336
+
337
+ Check_Type(cachedir_v, T_STRING);
338
+ Check_Type(path_v, T_STRING);
339
+
340
+ if (RSTRING_LEN(cachedir_v) > MAX_CACHEDIR_SIZE) {
341
+ rb_raise(rb_eArgError, "cachedir too long");
342
+ }
343
+
344
+ char * cachedir = RSTRING_PTR(cachedir_v);
345
+ char * path = RSTRING_PTR(path_v);
346
+ char cache_path[MAX_CACHEPATH_SIZE];
347
+
348
+ /* generate cache path to cache_path */
349
+ bs_cache_path(cachedir, path, &cache_path);
350
+
351
+ return bs_precompile(path, path_v, cache_path, handler);
352
+ }
319
353
  /*
320
354
  * Open the file we want to load/cache and generate a cache key for it if it
321
355
  * was loaded.
322
356
  */
323
357
  static int
324
- open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenance)
358
+ open_current_file(char * path, struct bs_cache_key * key, const char ** errno_provenance)
325
359
  {
326
360
  struct stat statbuf;
327
361
  int fd;
328
362
 
329
363
  fd = open(path, O_RDONLY);
330
364
  if (fd < 0) {
331
- *errno_provenance = (char *)"bs_fetch:open_current_file:open";
365
+ *errno_provenance = "bs_fetch:open_current_file:open";
332
366
  return fd;
333
367
  }
334
368
  #ifdef _WIN32
@@ -336,7 +370,7 @@ open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenan
336
370
  #endif
337
371
 
338
372
  if (fstat(fd, &statbuf) < 0) {
339
- *errno_provenance = (char *)"bs_fetch:open_current_file:fstat";
373
+ *errno_provenance = "bs_fetch:open_current_file:fstat";
340
374
  close(fd);
341
375
  return -1;
342
376
  }
@@ -382,13 +416,13 @@ bs_read_key(int fd, struct bs_cache_key * key)
382
416
  * - ERROR_WITH_ERRNO (-1, errno is set)
383
417
  */
384
418
  static int
385
- open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_provenance)
419
+ open_cache_file(const char * path, struct bs_cache_key * key, const char ** errno_provenance)
386
420
  {
387
421
  int fd, res;
388
422
 
389
423
  fd = open(path, O_RDONLY);
390
424
  if (fd < 0) {
391
- *errno_provenance = (char *)"bs_fetch:open_cache_file:open";
425
+ *errno_provenance = "bs_fetch:open_cache_file:open";
392
426
  if (errno == ENOENT) return CACHE_MISSING_OR_INVALID;
393
427
  return ERROR_WITH_ERRNO;
394
428
  }
@@ -398,7 +432,7 @@ open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_prov
398
432
 
399
433
  res = bs_read_key(fd, key);
400
434
  if (res < 0) {
401
- *errno_provenance = (char *)"bs_fetch:open_cache_file:read";
435
+ *errno_provenance = "bs_fetch:open_cache_file:read";
402
436
  close(fd);
403
437
  return res;
404
438
  }
@@ -422,7 +456,7 @@ open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_prov
422
456
  * or exception, will be the final data returnable to the user.
423
457
  */
424
458
  static int
425
- fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag, char ** errno_provenance)
459
+ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE * output_data, int * exception_tag, const char ** errno_provenance)
426
460
  {
427
461
  char * data = NULL;
428
462
  ssize_t nread;
@@ -431,7 +465,7 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data,
431
465
  VALUE storage_data;
432
466
 
433
467
  if (data_size > 100000000000) {
434
- *errno_provenance = (char *)"bs_fetch:fetch_cached_data:datasize";
468
+ *errno_provenance = "bs_fetch:fetch_cached_data:datasize";
435
469
  errno = EINVAL; /* because wtf? */
436
470
  ret = -1;
437
471
  goto done;
@@ -439,7 +473,7 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data,
439
473
  data = ALLOC_N(char, data_size);
440
474
  nread = read(fd, data, data_size);
441
475
  if (nread < 0) {
442
- *errno_provenance = (char *)"bs_fetch:fetch_cached_data:read";
476
+ *errno_provenance = "bs_fetch:fetch_cached_data:read";
443
477
  ret = -1;
444
478
  goto done;
445
479
  }
@@ -448,9 +482,9 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data,
448
482
  goto done;
449
483
  }
450
484
 
451
- storage_data = rb_str_new_static(data, data_size);
485
+ storage_data = rb_str_new(data, data_size);
452
486
 
453
- *exception_tag = bs_storage_to_output(handler, storage_data, output_data);
487
+ *exception_tag = bs_storage_to_output(handler, args, storage_data, output_data);
454
488
  ret = 0;
455
489
  done:
456
490
  if (data != NULL) xfree(data);
@@ -491,29 +525,36 @@ mkpath(char * file_path, mode_t mode)
491
525
  * path.
492
526
  */
493
527
  static int
494
- atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char ** errno_provenance)
528
+ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, const char ** errno_provenance)
495
529
  {
496
530
  char template[MAX_CACHEPATH_SIZE + 20];
497
531
  char * tmp_path;
498
- int fd, ret;
532
+ int fd, ret, attempt;
499
533
  ssize_t nwrite;
500
534
 
501
- tmp_path = strncpy(template, path, MAX_CACHEPATH_SIZE);
502
- strcat(tmp_path, ".tmp.XXXXXX");
535
+ for (attempt = 0; attempt < MAX_CREATE_TEMPFILE_ATTEMPT; ++attempt) {
536
+ tmp_path = strncpy(template, path, MAX_CACHEPATH_SIZE);
537
+ strcat(tmp_path, ".tmp.XXXXXX");
503
538
 
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";
539
+ // mkstemp modifies the template to be the actual created path
540
+ fd = mkstemp(tmp_path);
541
+ if (fd > 0) break;
542
+
543
+ if (attempt == 0 && mkpath(tmp_path, 0775) < 0) {
544
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:mkpath";
514
545
  return -1;
515
546
  }
516
547
  }
548
+ if (fd < 0) {
549
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:mkstemp";
550
+ return -1;
551
+ }
552
+
553
+ if (chmod(tmp_path, 0644) < 0) {
554
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:chmod";
555
+ return -1;
556
+ }
557
+
517
558
  #ifdef _WIN32
518
559
  setmode(fd, O_BINARY);
519
560
  #endif
@@ -521,11 +562,11 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
521
562
  key->data_size = RSTRING_LEN(data);
522
563
  nwrite = write(fd, key, KEY_SIZE);
523
564
  if (nwrite < 0) {
524
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:write";
565
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:write";
525
566
  return -1;
526
567
  }
527
568
  if (nwrite != KEY_SIZE) {
528
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:keysize";
569
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:keysize";
529
570
  errno = EIO; /* Lies but whatever */
530
571
  return -1;
531
572
  }
@@ -533,7 +574,7 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
533
574
  nwrite = write(fd, RSTRING_PTR(data), RSTRING_LEN(data));
534
575
  if (nwrite < 0) return -1;
535
576
  if (nwrite != RSTRING_LEN(data)) {
536
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:writelength";
577
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:writelength";
537
578
  errno = EIO; /* Lies but whatever */
538
579
  return -1;
539
580
  }
@@ -541,12 +582,12 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
541
582
  close(fd);
542
583
  ret = rename(tmp_path, path);
543
584
  if (ret < 0) {
544
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:rename";
585
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:rename";
545
586
  return -1;
546
587
  }
547
588
  ret = chmod(path, 0664 & ~current_umask);
548
589
  if (ret < 0) {
549
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:chmod";
590
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:chmod";
550
591
  }
551
592
  return ret;
552
593
  }
@@ -555,13 +596,13 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
555
596
  /* Read contents from an fd, whose contents are asserted to be +size+ bytes
556
597
  * long, into a buffer */
557
598
  static ssize_t
558
- bs_read_contents(int fd, size_t size, char ** contents, char ** errno_provenance)
599
+ bs_read_contents(int fd, size_t size, char ** contents, const char ** errno_provenance)
559
600
  {
560
601
  ssize_t nread;
561
602
  *contents = ALLOC_N(char, size);
562
603
  nread = read(fd, *contents, size);
563
604
  if (nread < 0) {
564
- *errno_provenance = (char *)"bs_fetch:bs_read_contents:read";
605
+ *errno_provenance = "bs_fetch:bs_read_contents:read";
565
606
  }
566
607
  return nread;
567
608
  }
@@ -611,13 +652,13 @@ bs_read_contents(int fd, size_t size, char ** contents, char ** errno_provenance
611
652
  * - Return storage_to_output(storage_data)
612
653
  */
613
654
  static VALUE
614
- bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
655
+ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args)
615
656
  {
616
657
  struct bs_cache_key cached_key, current_key;
617
658
  char * contents = NULL;
618
659
  int cache_fd = -1, current_fd = -1;
619
660
  int res, valid_cache = 0, exception_tag = 0;
620
- char * errno_provenance = NULL;
661
+ const char * errno_provenance = NULL;
621
662
 
622
663
  VALUE input_data; /* data read from source file, e.g. YAML or ruby source */
623
664
  VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
@@ -644,7 +685,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
644
685
  if (valid_cache) {
645
686
  /* Fetch the cache data and return it if we're able to load it successfully */
646
687
  res = fetch_cached_data(
647
- cache_fd, (ssize_t)cached_key.data_size, handler,
688
+ cache_fd, (ssize_t)cached_key.data_size, handler, args,
648
689
  &output_data, &exception_tag, &errno_provenance
649
690
  );
650
691
  if (exception_tag != 0) goto raise;
@@ -658,15 +699,15 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
658
699
 
659
700
  /* Read the contents of the source file into a buffer */
660
701
  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);
702
+ input_data = rb_str_new(contents, current_key.size);
662
703
 
663
704
  /* 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);
705
+ exception_tag = bs_input_to_storage(handler, args, input_data, path_v, &storage_data);
665
706
  if (exception_tag != 0) goto raise;
666
707
  /* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
667
708
  * to cache anything; just return input_to_output(input_data) */
668
709
  if (storage_data == uncompilable) {
669
- bs_input_to_output(handler, input_data, &output_data, &exception_tag);
710
+ bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
670
711
  if (exception_tag != 0) goto raise;
671
712
  goto succeed;
672
713
  }
@@ -678,17 +719,17 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
678
719
  if (res < 0) goto fail_errno;
679
720
 
680
721
  /* Having written the cache, now convert storage_data to output_data */
681
- exception_tag = bs_storage_to_output(handler, storage_data, &output_data);
722
+ exception_tag = bs_storage_to_output(handler, args, storage_data, &output_data);
682
723
  if (exception_tag != 0) goto raise;
683
724
 
684
725
  /* If output_data is nil, delete the cache entry and generate the output
685
726
  * using input_to_output */
686
727
  if (NIL_P(output_data)) {
687
728
  if (unlink(cache_path) < 0) {
688
- errno_provenance = (char *)"bs_fetch:unlink";
729
+ errno_provenance = "bs_fetch:unlink";
689
730
  goto fail_errno;
690
731
  }
691
- bs_input_to_output(handler, input_data, &output_data, &exception_tag);
732
+ bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
692
733
  if (exception_tag != 0) goto raise;
693
734
  }
694
735
 
@@ -719,6 +760,79 @@ invalid_type_storage_data:
719
760
  #undef CLEANUP
720
761
  }
721
762
 
763
+ static VALUE
764
+ bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
765
+ {
766
+ struct bs_cache_key cached_key, current_key;
767
+ char * contents = NULL;
768
+ int cache_fd = -1, current_fd = -1;
769
+ int res, valid_cache = 0, exception_tag = 0;
770
+ const char * errno_provenance = NULL;
771
+
772
+ VALUE input_data; /* data read from source file, e.g. YAML or ruby source */
773
+ VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
774
+
775
+ /* Open the source file and generate a cache key for it */
776
+ current_fd = open_current_file(path, &current_key, &errno_provenance);
777
+ if (current_fd < 0) goto fail;
778
+
779
+ /* Open the cache key if it exists, and read its cache key in */
780
+ cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance);
781
+ if (cache_fd == CACHE_MISSING_OR_INVALID) {
782
+ /* This is ok: valid_cache remains false, we re-populate it. */
783
+ } else if (cache_fd < 0) {
784
+ goto fail;
785
+ } else {
786
+ /* True if the cache existed and no invalidating changes have occurred since
787
+ * it was generated. */
788
+ valid_cache = cache_key_equal(&current_key, &cached_key);
789
+ }
790
+
791
+ if (valid_cache) {
792
+ goto succeed;
793
+ }
794
+
795
+ close(cache_fd);
796
+ cache_fd = -1;
797
+ /* Cache is stale, invalid, or missing. Regenerate and write it out. */
798
+
799
+ /* Read the contents of the source file into a buffer */
800
+ if (bs_read_contents(current_fd, current_key.size, &contents, &errno_provenance) < 0) goto fail;
801
+ input_data = rb_str_new(contents, current_key.size);
802
+
803
+ /* Try to compile the input_data using input_to_storage(input_data) */
804
+ exception_tag = bs_input_to_storage(handler, Qnil, input_data, path_v, &storage_data);
805
+ if (exception_tag != 0) goto fail;
806
+
807
+ /* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
808
+ * to cache anything; just return false */
809
+ if (storage_data == uncompilable) {
810
+ goto fail;
811
+ }
812
+ /* If storage_data isn't a string, we can't cache it */
813
+ if (!RB_TYPE_P(storage_data, T_STRING)) goto fail;
814
+
815
+ /* Write the cache key and storage_data to the cache directory */
816
+ res = atomic_write_cache_file(cache_path, &current_key, storage_data, &errno_provenance);
817
+ if (res < 0) goto fail;
818
+
819
+ goto succeed;
820
+
821
+ #define CLEANUP \
822
+ if (contents != NULL) xfree(contents); \
823
+ if (current_fd >= 0) close(current_fd); \
824
+ if (cache_fd >= 0) close(cache_fd);
825
+
826
+ succeed:
827
+ CLEANUP;
828
+ return Qtrue;
829
+ fail:
830
+ CLEANUP;
831
+ return Qfalse;
832
+ #undef CLEANUP
833
+ }
834
+
835
+
722
836
  /*****************************************************************************/
723
837
  /********************* Handler Wrappers **************************************/
724
838
  /*****************************************************************************
@@ -738,11 +852,13 @@ invalid_type_storage_data:
738
852
 
739
853
  struct s2o_data {
740
854
  VALUE handler;
855
+ VALUE args;
741
856
  VALUE storage_data;
742
857
  };
743
858
 
744
859
  struct i2o_data {
745
860
  VALUE handler;
861
+ VALUE args;
746
862
  VALUE input_data;
747
863
  };
748
864
 
@@ -756,15 +872,16 @@ static VALUE
756
872
  prot_storage_to_output(VALUE arg)
757
873
  {
758
874
  struct s2o_data * data = (struct s2o_data *)arg;
759
- return rb_funcall(data->handler, rb_intern("storage_to_output"), 1, data->storage_data);
875
+ return rb_funcall(data->handler, rb_intern("storage_to_output"), 2, data->storage_data, data->args);
760
876
  }
761
877
 
762
878
  static int
763
- bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data)
879
+ bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * output_data)
764
880
  {
765
881
  int state;
766
882
  struct s2o_data s2o_data = {
767
883
  .handler = handler,
884
+ .args = args,
768
885
  .storage_data = storage_data,
769
886
  };
770
887
  *output_data = rb_protect(prot_storage_to_output, (VALUE)&s2o_data, &state);
@@ -772,10 +889,11 @@ bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data)
772
889
  }
773
890
 
774
891
  static void
775
- bs_input_to_output(VALUE handler, VALUE input_data, VALUE * output_data, int * exception_tag)
892
+ bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag)
776
893
  {
777
894
  struct i2o_data i2o_data = {
778
895
  .handler = handler,
896
+ .args = args,
779
897
  .input_data = input_data,
780
898
  };
781
899
  *output_data = rb_protect(prot_input_to_output, (VALUE)&i2o_data, exception_tag);
@@ -785,7 +903,7 @@ static VALUE
785
903
  prot_input_to_output(VALUE arg)
786
904
  {
787
905
  struct i2o_data * data = (struct i2o_data *)arg;
788
- return rb_funcall(data->handler, rb_intern("input_to_output"), 1, data->input_data);
906
+ return rb_funcall(data->handler, rb_intern("input_to_output"), 2, data->input_data, data->args);
789
907
  }
790
908
 
791
909
  static VALUE
@@ -796,7 +914,7 @@ try_input_to_storage(VALUE arg)
796
914
  }
797
915
 
798
916
  static VALUE
799
- rescue_input_to_storage(VALUE arg)
917
+ rescue_input_to_storage(VALUE arg, VALUE e)
800
918
  {
801
919
  return uncompilable;
802
920
  }
@@ -812,7 +930,7 @@ prot_input_to_storage(VALUE arg)
812
930
  }
813
931
 
814
932
  static int
815
- bs_input_to_storage(VALUE handler, VALUE input_data, VALUE pathval, VALUE * storage_data)
933
+ bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data)
816
934
  {
817
935
  int state;
818
936
  struct i2s_data i2s_data = {
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require("mkmf")
2
3
  $CFLAGS << ' -O3 '
3
4
  $CFLAGS << ' -std=c99'
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Bootsnap
2
3
  extend(self)
3
4