bootsnap 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -32,7 +32,7 @@
32
32
  /*
33
33
  * An instance of this key is written as the first 64 bytes of each cache file.
34
34
  * The mtime and size members track whether the file contents have changed, and
35
- * the version, os_version, compile_option, and ruby_revision members track
35
+ * the version, ruby_platform, compile_option, and ruby_revision members track
36
36
  * changes to the environment that could invalidate compile results without
37
37
  * file contents having changed. The data_size member is not truly part of the
38
38
  * "key". Really, this could be called a "header" with the first six members
@@ -45,7 +45,7 @@
45
45
  */
46
46
  struct bs_cache_key {
47
47
  uint32_t version;
48
- uint32_t os_version;
48
+ uint32_t ruby_platform;
49
49
  uint32_t compile_option;
50
50
  uint32_t ruby_revision;
51
51
  uint64_t size;
@@ -67,9 +67,9 @@ STATIC_ASSERT(sizeof(struct bs_cache_key) == KEY_SIZE);
67
67
  /* Effectively a schema version. Bumping invalidates all previous caches */
68
68
  static const uint32_t current_version = 2;
69
69
 
70
- /* Derived from kernel or libc version; intended to roughly correspond to when
71
- * ABIs have changed, requiring recompilation of native gems. */
72
- static uint32_t current_os_version;
70
+ /* hash of e.g. "x86_64-darwin17", invalidating when ruby is recompiled on a
71
+ * new OS ABI, etc. */
72
+ static uint32_t current_ruby_platform;
73
73
  /* Invalidates cache when switching ruby versions */
74
74
  static uint32_t current_ruby_revision;
75
75
  /* Invalidates cache when RubyVM::InstructionSequence.compile_option changes */
@@ -92,10 +92,9 @@ static void bs_cache_path(const char * cachedir, const char * path, char ** cach
92
92
  static int bs_read_key(int fd, struct bs_cache_key * key);
93
93
  static int cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2);
94
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);
96
- static int fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag);
97
- static VALUE prot_exception_for_errno(VALUE err);
98
- static uint32_t get_os_version(void);
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 uint32_t get_ruby_platform(void);
99
98
 
100
99
  /*
101
100
  * Helper functions to call ruby methods on handler object without crashing on
@@ -136,7 +135,7 @@ Init_bootsnap(void)
136
135
  rb_eBootsnap_CompileCache_Uncompilable = rb_define_class_under(rb_mBootsnap_CompileCache, "Uncompilable", rb_eStandardError);
137
136
 
138
137
  current_ruby_revision = FIX2INT(rb_const_get(rb_cObject, rb_intern("RUBY_REVISION")));
139
- current_os_version = get_os_version();
138
+ current_ruby_platform = get_ruby_platform();
140
139
 
141
140
  uncompilable = rb_intern("__bootsnap_uncompilable__");
142
141
 
@@ -148,12 +147,17 @@ Init_bootsnap(void)
148
147
  /*
149
148
  * Bootsnap's ruby code registers a hook that notifies us via this function
150
149
  * when compile_option changes. These changes invalidate all existing caches.
150
+ *
151
+ * Note that on 32-bit platforms, a CRC32 can't be represented in a Fixnum, but
152
+ * can be represented by a uint.
151
153
  */
152
154
  static VALUE
153
155
  bs_compile_option_crc32_set(VALUE self, VALUE crc32_v)
154
156
  {
155
- Check_Type(crc32_v, T_FIXNUM);
156
- current_compile_option_crc32 = FIX2UINT(crc32_v);
157
+ if (!RB_TYPE_P(crc32_v, T_BIGNUM) && !RB_TYPE_P(crc32_v, T_FIXNUM)) {
158
+ Check_Type(crc32_v, T_FIXNUM);
159
+ }
160
+ current_compile_option_crc32 = NUM2UINT(crc32_v);
157
161
  return Qnil;
158
162
  }
159
163
 
@@ -168,10 +172,9 @@ bs_compile_option_crc32_set(VALUE self, VALUE crc32_v)
168
172
  * - 32 bits doesn't feel collision-resistant enough; 64 is nice.
169
173
  */
170
174
  static uint64_t
171
- fnv1a_64(const char *str)
175
+ fnv1a_64_iter(uint64_t h, const char *str)
172
176
  {
173
177
  unsigned char *s = (unsigned char *)str;
174
- uint64_t h = (uint64_t)0xcbf29ce484222325ULL;
175
178
 
176
179
  while (*s) {
177
180
  h ^= (uint64_t)*s++;
@@ -181,26 +184,42 @@ fnv1a_64(const char *str)
181
184
  return h;
182
185
  }
183
186
 
187
+ static uint64_t
188
+ fnv1a_64(const char *str)
189
+ {
190
+ uint64_t h = (uint64_t)0xcbf29ce484222325ULL;
191
+ return fnv1a_64_iter(h, str);
192
+ }
193
+
184
194
  /*
185
- * The idea here is that we want a cache key member that changes when the OS
186
- * changes in such a way as to make existing compiled ISeqs unloadable.
195
+ * When ruby's version doesn't change, but it's recompiled on a different OS
196
+ * (or OS version), we need to invalidate the cache.
197
+ *
198
+ * We actually factor in some extra information here, to be extra confident
199
+ * that we don't try to re-use caches that will not be compatible, by factoring
200
+ * in utsname.version.
187
201
  */
188
202
  static uint32_t
189
- get_os_version(void)
203
+ get_ruby_platform(void)
190
204
  {
191
- #ifdef _WIN32
192
- return (uint32_t)GetVersion();
193
- #else
194
205
  uint64_t hash;
195
- struct utsname utsname;
206
+ VALUE ruby_platform;
196
207
 
197
- /* Not worth crashing if this fails; lose cache invalidation potential */
198
- if (uname(&utsname) < 0) return 0;
208
+ ruby_platform = rb_const_get(rb_cObject, rb_intern("RUBY_PLATFORM"));
209
+ hash = fnv1a_64(RSTRING_PTR(ruby_platform));
199
210
 
200
- hash = fnv1a_64(utsname.version);
211
+ #ifdef _WIN32
212
+ return (uint32_t)(hash >> 32) ^ (uint32_t)GetVersion();
213
+ #else
214
+ struct utsname utsname;
215
+
216
+ /* Not worth crashing if this fails; lose extra cache invalidation potential */
217
+ if (uname(&utsname) >= 0) {
218
+ hash = fnv1a_64_iter(hash, utsname.version);
219
+ }
201
220
 
202
221
  return (uint32_t)(hash >> 32);
203
- #endif
222
+ #endif
204
223
  }
205
224
 
206
225
  /*
@@ -234,7 +253,7 @@ cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2)
234
253
  {
235
254
  return (
236
255
  k1->version == k2->version &&
237
- k1->os_version == k2->os_version &&
256
+ k1->ruby_platform == k2->ruby_platform &&
238
257
  k1->compile_option == k2->compile_option &&
239
258
  k1->ruby_revision == k2->ruby_revision &&
240
259
  k1->size == k2->size &&
@@ -274,24 +293,28 @@ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
274
293
  * was loaded.
275
294
  */
276
295
  static int
277
- open_current_file(char * path, struct bs_cache_key * key)
296
+ open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenance)
278
297
  {
279
298
  struct stat statbuf;
280
299
  int fd;
281
300
 
282
301
  fd = open(path, O_RDONLY);
283
- if (fd < 0) return fd;
302
+ if (fd < 0) {
303
+ *errno_provenance = (char *)"bs_fetch:open_current_file:open";
304
+ return fd;
305
+ }
284
306
  #ifdef _WIN32
285
307
  setmode(fd, O_BINARY);
286
308
  #endif
287
309
 
288
310
  if (fstat(fd, &statbuf) < 0) {
311
+ *errno_provenance = (char *)"bs_fetch:open_current_file:fstat";
289
312
  close(fd);
290
313
  return -1;
291
314
  }
292
315
 
293
316
  key->version = current_version;
294
- key->os_version = current_os_version;
317
+ key->ruby_platform = current_ruby_platform;
295
318
  key->compile_option = current_compile_option_crc32;
296
319
  key->ruby_revision = current_ruby_revision;
297
320
  key->size = (uint64_t)statbuf.st_size;
@@ -331,12 +354,13 @@ bs_read_key(int fd, struct bs_cache_key * key)
331
354
  * - ERROR_WITH_ERRNO (-1, errno is set)
332
355
  */
333
356
  static int
334
- open_cache_file(const char * path, struct bs_cache_key * key)
357
+ open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_provenance)
335
358
  {
336
359
  int fd, res;
337
360
 
338
361
  fd = open(path, O_RDONLY);
339
362
  if (fd < 0) {
363
+ *errno_provenance = (char *)"bs_fetch:open_cache_file:open";
340
364
  if (errno == ENOENT) return CACHE_MISSING_OR_INVALID;
341
365
  return ERROR_WITH_ERRNO;
342
366
  }
@@ -346,6 +370,7 @@ open_cache_file(const char * path, struct bs_cache_key * key)
346
370
 
347
371
  res = bs_read_key(fd, key);
348
372
  if (res < 0) {
373
+ *errno_provenance = (char *)"bs_fetch:open_cache_file:read";
349
374
  close(fd);
350
375
  return res;
351
376
  }
@@ -369,7 +394,7 @@ open_cache_file(const char * path, struct bs_cache_key * key)
369
394
  * or exception, will be the final data returnable to the user.
370
395
  */
371
396
  static int
372
- fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag)
397
+ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag, char ** errno_provenance)
373
398
  {
374
399
  char * data = NULL;
375
400
  ssize_t nread;
@@ -378,6 +403,7 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data,
378
403
  VALUE storage_data;
379
404
 
380
405
  if (data_size > 100000000000) {
406
+ *errno_provenance = (char *)"bs_fetch:fetch_cached_data:datasize";
381
407
  errno = EINVAL; /* because wtf? */
382
408
  ret = -1;
383
409
  goto done;
@@ -385,6 +411,7 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data,
385
411
  data = ALLOC_N(char, data_size);
386
412
  nread = read(fd, data, data_size);
387
413
  if (nread < 0) {
414
+ *errno_provenance = (char *)"bs_fetch:fetch_cached_data:read";
388
415
  ret = -1;
389
416
  goto done;
390
417
  }
@@ -436,23 +463,29 @@ mkpath(char * file_path, mode_t mode)
436
463
  * path.
437
464
  */
438
465
  static int
439
- atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data)
466
+ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char ** errno_provenance)
440
467
  {
441
468
  char template[MAX_CACHEPATH_SIZE + 20];
442
469
  char * dest;
443
470
  char * tmp_path;
444
- int fd;
471
+ int fd, ret;
445
472
  ssize_t nwrite;
446
473
 
447
474
  dest = strncpy(template, path, MAX_CACHEPATH_SIZE);
448
475
  strcat(dest, ".tmp.XXXXXX");
449
476
 
450
477
  tmp_path = mktemp(template);
451
- fd = open(tmp_path, O_WRONLY | O_CREAT, 0644);
478
+ fd = open(tmp_path, O_WRONLY | O_CREAT, 0664);
452
479
  if (fd < 0) {
453
- if (mkpath(path, 0755) < 0) return -1;
454
- fd = open(tmp_path, O_WRONLY | O_CREAT, 0644);
455
- if (fd < 0) return -1;
480
+ if (mkpath(path, 0775) < 0) {
481
+ *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:mkpath";
482
+ return -1;
483
+ }
484
+ fd = open(tmp_path, O_WRONLY | O_CREAT, 0664);
485
+ if (fd < 0) {
486
+ *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:open";
487
+ return -1;
488
+ }
456
489
  }
457
490
  #ifdef _WIN32
458
491
  setmode(fd, O_BINARY);
@@ -460,8 +493,12 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data)
460
493
 
461
494
  key->data_size = RSTRING_LEN(data);
462
495
  nwrite = write(fd, key, KEY_SIZE);
463
- if (nwrite < 0) return -1;
496
+ if (nwrite < 0) {
497
+ *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:write";
498
+ return -1;
499
+ }
464
500
  if (nwrite != KEY_SIZE) {
501
+ *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:keysize";
465
502
  errno = EIO; /* Lies but whatever */
466
503
  return -1;
467
504
  }
@@ -469,38 +506,32 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data)
469
506
  nwrite = write(fd, RSTRING_PTR(data), RSTRING_LEN(data));
470
507
  if (nwrite < 0) return -1;
471
508
  if (nwrite != RSTRING_LEN(data)) {
509
+ *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:writelength";
472
510
  errno = EIO; /* Lies but whatever */
473
511
  return -1;
474
512
  }
475
513
 
476
514
  close(fd);
477
- return rename(tmp_path, path);
478
- }
479
-
480
- /*
481
- * Given an errno value (converted to a ruby Fixnum), return the corresponding
482
- * Errno::* constant. If none is found, return StandardError instead.
483
- */
484
- static VALUE
485
- prot_exception_for_errno(VALUE err)
486
- {
487
- if (err != INT2FIX(0)) {
488
- VALUE mErrno = rb_const_get(rb_cObject, rb_intern("Errno"));
489
- VALUE constants = rb_funcall(mErrno, rb_intern("constants"), 0);
490
- VALUE which = rb_funcall(constants, rb_intern("[]"), 1, err);
491
- return rb_funcall(mErrno, rb_intern("const_get"), 1, which);
515
+ ret = rename(tmp_path, path);
516
+ if (ret < 0) {
517
+ *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:rename";
492
518
  }
493
- return rb_eStandardError;
519
+ return ret;
494
520
  }
495
521
 
496
522
 
497
523
  /* Read contents from an fd, whose contents are asserted to be +size+ bytes
498
524
  * long, into a buffer */
499
525
  static ssize_t
500
- bs_read_contents(int fd, size_t size, char ** contents)
526
+ bs_read_contents(int fd, size_t size, char ** contents, char ** errno_provenance)
501
527
  {
528
+ ssize_t nread;
502
529
  *contents = ALLOC_N(char, size);
503
- return read(fd, *contents, size);
530
+ nread = read(fd, *contents, size);
531
+ if (nread < 0) {
532
+ *errno_provenance = (char *)"bs_fetch:bs_read_contents:read";
533
+ }
534
+ return nread;
504
535
  }
505
536
 
506
537
  /*
@@ -508,24 +539,24 @@ bs_read_contents(int fd, size_t size, char ** contents)
508
539
  * Bootsnap::CompileCache::Native.fetch.
509
540
  *
510
541
  * There are three "formats" in use here:
511
- * 1. "input" fomat, which is what we load from the source file;
542
+ * 1. "input" format, which is what we load from the source file;
512
543
  * 2. "storage" format, which we write to the cache;
513
544
  * 3. "output" format, which is what we return.
514
545
  *
515
546
  * E.g., For ISeq compilation:
516
- * input: ruby source, as text
547
+ * input: ruby source, as text
517
548
  * storage: binary string (RubyVM::InstructionSequence#to_binary)
518
- * output: Instance of RubyVM::InstructionSequence
549
+ * output: Instance of RubyVM::InstructionSequence
519
550
  *
520
551
  * And for YAML:
521
- * input: yaml as text
552
+ * input: yaml as text
522
553
  * storage: MessagePack or Marshal text
523
- * output: ruby object, loaded from yaml/messagepack/marshal
554
+ * output: ruby object, loaded from yaml/messagepack/marshal
524
555
  *
525
- * The handler passed in must support three messages:
526
- * * storage_to_output(s) -> o
527
- * * input_to_output(i) -> o
528
- * * input_to_storage(i) -> s
556
+ * A handler<I,S,O> passed in must support three messages:
557
+ * * storage_to_output(S) -> O
558
+ * * input_to_output(I) -> O
559
+ * * input_to_storage(I) -> S
529
560
  * (input_to_storage may raise Bootsnap::CompileCache::Uncompilable, which
530
561
  * will prevent caching and cause output to be generated with
531
562
  * input_to_output)
@@ -553,7 +584,8 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
553
584
  struct bs_cache_key cached_key, current_key;
554
585
  char * contents = NULL;
555
586
  int cache_fd = -1, current_fd = -1;
556
- int res, valid_cache, exception_tag = 0;
587
+ int res, valid_cache = 0, exception_tag = 0;
588
+ char * errno_provenance = NULL;
557
589
 
558
590
  VALUE input_data; /* data read from source file, e.g. YAML or ruby source */
559
591
  VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
@@ -562,20 +594,27 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
562
594
  VALUE exception; /* ruby exception object to raise instead of returning */
563
595
 
564
596
  /* Open the source file and generate a cache key for it */
565
- current_fd = open_current_file(path, &current_key);
597
+ current_fd = open_current_file(path, &current_key, &errno_provenance);
566
598
  if (current_fd < 0) goto fail_errno;
567
599
 
568
600
  /* Open the cache key if it exists, and read its cache key in */
569
- cache_fd = open_cache_file(cache_path, &cached_key);
570
- if (cache_fd < 0 && cache_fd != CACHE_MISSING_OR_INVALID) goto fail_errno;
571
-
572
- /* True if the cache existed and no invalidating changes have occurred since
573
- * it was generated. */
574
- valid_cache = cache_key_equal(&current_key, &cached_key);
601
+ cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance);
602
+ if (cache_fd == CACHE_MISSING_OR_INVALID) {
603
+ /* This is ok: valid_cache remains false, we re-populate it. */
604
+ } else if (cache_fd < 0) {
605
+ goto fail_errno;
606
+ } else {
607
+ /* True if the cache existed and no invalidating changes have occurred since
608
+ * it was generated. */
609
+ valid_cache = cache_key_equal(&current_key, &cached_key);
610
+ }
575
611
 
576
612
  if (valid_cache) {
577
613
  /* Fetch the cache data and return it if we're able to load it successfully */
578
- res = fetch_cached_data(cache_fd, (ssize_t)cached_key.data_size, handler, &output_data, &exception_tag);
614
+ res = fetch_cached_data(
615
+ cache_fd, (ssize_t)cached_key.data_size, handler,
616
+ &output_data, &exception_tag, &errno_provenance
617
+ );
579
618
  if (exception_tag != 0) goto raise;
580
619
  else if (res == CACHE_MISSING_OR_INVALID) valid_cache = 0;
581
620
  else if (res == ERROR_WITH_ERRNO) goto fail_errno;
@@ -586,7 +625,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
586
625
  /* Cache is stale, invalid, or missing. Regenerate and write it out. */
587
626
 
588
627
  /* Read the contents of the source file into a buffer */
589
- if (bs_read_contents(current_fd, current_key.size, &contents) < 0) goto fail_errno;
628
+ if (bs_read_contents(current_fd, current_key.size, &contents, &errno_provenance) < 0) goto fail_errno;
590
629
  input_data = rb_str_new_static(contents, current_key.size);
591
630
 
592
631
  /* Try to compile the input_data using input_to_storage(input_data) */
@@ -603,7 +642,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
603
642
  if (!RB_TYPE_P(storage_data, T_STRING)) goto invalid_type_storage_data;
604
643
 
605
644
  /* Write the cache key and storage_data to the cache directory */
606
- res = atomic_write_cache_file(cache_path, &current_key, storage_data);
645
+ res = atomic_write_cache_file(cache_path, &current_key, storage_data, &errno_provenance);
607
646
  if (res < 0) goto fail_errno;
608
647
 
609
648
  /* Having written the cache, now convert storage_data to output_data */
@@ -613,7 +652,10 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
613
652
  /* If output_data is nil, delete the cache entry and generate the output
614
653
  * using input_to_output */
615
654
  if (NIL_P(output_data)) {
616
- if (unlink(cache_path) < 0) goto fail_errno;
655
+ if (unlink(cache_path) < 0) {
656
+ errno_provenance = (char *)"bs_fetch:unlink";
657
+ goto fail_errno;
658
+ }
617
659
  bs_input_to_output(handler, input_data, &output_data, &exception_tag);
618
660
  if (exception_tag != 0) goto raise;
619
661
  }
@@ -630,8 +672,7 @@ succeed:
630
672
  return output_data;
631
673
  fail_errno:
632
674
  CLEANUP;
633
- exception = rb_protect(prot_exception_for_errno, INT2FIX(errno), &res);
634
- if (res) exception = rb_eStandardError;
675
+ exception = rb_syserr_new(errno, errno_provenance);
635
676
  rb_exc_raise(exception);
636
677
  __builtin_unreachable();
637
678
  raise:
@@ -650,7 +691,17 @@ invalid_type_storage_data:
650
691
  /********************* Handler Wrappers **************************************/
651
692
  /*****************************************************************************
652
693
  * Everything after this point in the file is just wrappers to deal with ruby's
653
- * clunky method of handling exceptions from ruby methods invoked from C.
694
+ * clunky method of handling exceptions from ruby methods invoked from C:
695
+ *
696
+ * In order to call a ruby method from C, while protecting against crashing in
697
+ * the event of an exception, we must call the method with rb_protect().
698
+ *
699
+ * rb_protect takes a C function and precisely one argument; however, we want
700
+ * to pass multiple arguments, so we must create structs to wrap them up.
701
+ *
702
+ * These functions return an exception_tag, which, if non-zero, indicates an
703
+ * exception that should be jumped to with rb_jump_tag after cleaning up
704
+ * allocated resources.
654
705
  */
655
706
 
656
707
  struct s2o_data {
@@ -0,0 +1,12 @@
1
+ module Bootsnap
2
+ module_function
3
+
4
+ def bundler?
5
+ # Bundler environment variable
6
+ ['BUNDLE_BIN_PATH', 'BUNDLE_GEMFILE'].each do |current|
7
+ return true if ENV.key?(current)
8
+ end
9
+
10
+ false
11
+ end
12
+ end
@@ -68,4 +68,3 @@ module Bootsnap
68
68
  end
69
69
  end
70
70
  end
71
-
@@ -8,6 +8,7 @@ module Bootsnap
8
8
  end
9
9
 
10
10
  def self.input_to_storage(contents, _)
11
+ raise Uncompilable if contents.index("!ruby/object")
11
12
  obj = ::YAML.load(contents)
12
13
  msgpack_factory.packer.write(obj).to_s
13
14
  rescue NoMethodError, RangeError
@@ -36,7 +36,12 @@ module Bootsnap
36
36
  end
37
37
  $LOAD_PATH << ARCHDIR
38
38
  $LOAD_PATH << RUBYLIBDIR
39
- yield
39
+ begin
40
+ yield
41
+ rescue LoadError
42
+ $LOAD_PATH.replace(orig)
43
+ yield
44
+ end
40
45
  ensure
41
46
  $LOAD_PATH.replace(orig)
42
47
  end
@@ -1,4 +1,3 @@
1
- require_relative '../load_path_cache'
2
1
  require_relative '../explicit_require'
3
2
 
4
3
  module Bootsnap
@@ -9,8 +8,9 @@ module Bootsnap
9
8
  def initialize(store, path_obj, development_mode: false)
10
9
  @development_mode = development_mode
11
10
  @store = store
12
- @mutex = ::Thread::Mutex.new
11
+ @mutex = defined?(::Mutex) ? ::Mutex.new : ::Thread::Mutex.new # TODO: Remove once Ruby 2.2 support is dropped.
13
12
  @path_obj = path_obj
13
+ @has_relative_paths = nil
14
14
  reinitialize
15
15
  end
16
16
 
@@ -23,24 +23,21 @@ module Bootsnap
23
23
  end
24
24
 
25
25
  # { 'enumerator' => nil, 'enumerator.so' => nil, ... }
26
- BUILTIN_FEATURES = $LOADED_FEATURES.reduce({}) do |acc, feat|
26
+ BUILTIN_FEATURES = $LOADED_FEATURES.each_with_object({}) do |feat, features|
27
27
  # Builtin features are of the form 'enumerator.so'.
28
28
  # All others include paths.
29
- next acc unless feat.size < 20 && !feat.include?('/')
29
+ next unless feat.size < 20 && !feat.include?('/')
30
30
 
31
31
  base = File.basename(feat, '.*') # enumerator.so -> enumerator
32
32
  ext = File.extname(feat) # .so
33
33
 
34
- acc[feat] = nil # enumerator.so
35
- acc[base] = nil # enumerator
34
+ features[feat] = nil # enumerator.so
35
+ features[base] = nil # enumerator
36
36
 
37
- if [DOT_SO, *DL_EXTENSIONS].include?(ext)
38
- DL_EXTENSIONS.each do |ext|
39
- acc["#{base}#{ext}"] = nil # enumerator.bundle
40
- end
37
+ next unless [DOT_SO, *DL_EXTENSIONS].include?(ext)
38
+ DL_EXTENSIONS.each do |dl_ext|
39
+ features["#{base}#{dl_ext}"] = nil # enumerator.bundle
41
40
  end
42
-
43
- acc
44
41
  end.freeze
45
42
 
46
43
  # Try to resolve this feature to an absolute path without traversing the
@@ -67,7 +64,8 @@ module Bootsnap
67
64
  # If the extension was one of the ones we explicitly cache (.rb and the
68
65
  # native dynamic extension, e.g. .bundle or .so), we know it was a
69
66
  # failure and there's nothing more we can do to find the file.
70
- when '', *CACHED_EXTENSIONS # no extension, .rb, (.bundle or .so)
67
+ # no extension, .rb, (.bundle or .so)
68
+ when '', *CACHED_EXTENSIONS # rubocop:disable Performance/CaseWhenSplat
71
69
  nil
72
70
  # Ruby allows specifying native extensions as '.so' even when DLEXT
73
71
  # is '.bundle'. This is where we handle that case.
@@ -152,7 +150,7 @@ module Bootsnap
152
150
 
153
151
  def unshift_paths_locked(*paths)
154
152
  @store.transaction do
155
- paths.map(&:to_s).reverse.each do |path|
153
+ paths.map(&:to_s).reverse_each do |path|
156
154
  p = Path.new(path)
157
155
  next if p.non_directory?
158
156
  entries, dirs = p.entries_and_dirs(@store)
@@ -2,15 +2,6 @@ module Bootsnap
2
2
  module LoadPathCache
3
3
  module CoreExt
4
4
  module ActiveSupport
5
- def self.with_bootsnap_fallback(error)
6
- yield
7
- rescue error => e
8
- # NoMethodError is a NameError, but we only want to handle actual
9
- # NameError instances.
10
- raise unless e.class == error
11
- without_bootsnap_cache { yield }
12
- end
13
-
14
5
  def self.without_bootsnap_cache
15
6
  prev = Thread.current[:without_bootsnap_cache] || false
16
7
  Thread.current[:without_bootsnap_cache] = true
@@ -21,9 +12,8 @@ module Bootsnap
21
12
 
22
13
  module ClassMethods
23
14
  def autoload_paths=(o)
24
- r = super
15
+ super
25
16
  Bootsnap::LoadPathCache.autoload_paths_cache.reinitialize(o)
26
- r
27
17
  end
28
18
 
29
19
  def search_for_file(path)
@@ -50,13 +40,25 @@ module Bootsnap
50
40
  # behaviour. The gymnastics here are a bit awkward, but it prevents
51
41
  # 200+ lines of monkeypatches.
52
42
  def load_missing_constant(from_mod, const_name)
53
- CoreExt::ActiveSupport.with_bootsnap_fallback(NameError) { super }
43
+ super
44
+ rescue NameError => e
45
+ # NoMethodError is a NameError, but we only want to handle actual
46
+ # NameError instances.
47
+ raise unless e.class == NameError
48
+ # We can only confidently handle cases when *this* constant fails
49
+ # to load, not other constants referred to by it.
50
+ raise unless e.name == const_name
51
+ # If the constant was actually loaded, something else went wrong?
52
+ raise if from_mod.const_defined?(const_name)
53
+ CoreExt::ActiveSupport.without_bootsnap_cache { super }
54
54
  end
55
55
 
56
56
  # Signature has changed a few times over the years; easiest to not
57
57
  # reiterate it with version polymorphism here...
58
58
  def depend_on(*)
59
- CoreExt::ActiveSupport.with_bootsnap_fallback(LoadError) { super }
59
+ super
60
+ rescue LoadError
61
+ CoreExt::ActiveSupport.without_bootsnap_cache { super }
60
62
  end
61
63
  end
62
64
  end