bootsnap 1.4.2 → 1.4.7

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
2
  SHA256:
3
- metadata.gz: 53b49581be59c8137e2d6093fb7706b2ec9846185fa5b443d8d63e74bb9e9926
4
- data.tar.gz: b45d5d8dc92c81f2749f546c7244fd8d0d968ed152c9c35365389d6226056e03
3
+ metadata.gz: 3678f8871286d394bbfc31303d0123aae9374370aeda42701336206293447f9a
4
+ data.tar.gz: 88a8005d7a4fac49b6a1dde52e2d2ec891fd5fa35766cdfb46f52c83cf441b25
5
5
  SHA512:
6
- metadata.gz: b348f404b75266017a81355a1b6cf0053474c088b8fac2cd991cff3cf44c468373573892677a39982fb4a9c58ad22005488e8b481b2a5b41bfbd1b9cdf227ace
7
- data.tar.gz: a20cc7e1e3c32784c808618883654782c94afef692a19d413a2374ee54ea566e7a646280d0360b6f5296149e5c322d33ac1379671ec94e0e94e4afdb5c4a7ff0
6
+ metadata.gz: 1025b1d2a7b60083e6e7bf460cf4aae1ad1c6b2376e024fae199ea0728837b897f417e89b30fcfb3a7fb3260993846566df0537523ee8f703f7bc7d5c1590ace
7
+ data.tar.gz: 64686090f0dcf1c8835b59690e700b0abd394edf399733cd2ca53d952bdd5f1cc3109d381bac8079f3e64e9c9e12913e132e1fdf07ea3262891914cef4f9c299
@@ -5,10 +5,10 @@ AllCops:
5
5
  Exclude:
6
6
  - 'vendor/**/*'
7
7
  - 'tmp/**/*'
8
- TargetRubyVersion: '2.2'
8
+ TargetRubyVersion: '2.3'
9
9
 
10
10
  # This doesn't take into account retrying from an exception
11
- Lint/HandleExceptions:
11
+ Lint/SuppressedException:
12
12
  Enabled: false
13
13
 
14
14
  # allow String.new to create mutable strings
@@ -6,8 +6,10 @@ os:
6
6
  - osx
7
7
 
8
8
  rvm:
9
- - ruby-2.4
10
- - ruby-2.5
9
+ - '2.4'
10
+ - '2.5'
11
+ - '2.6'
12
+ - '2.7'
11
13
  - ruby-head
12
14
 
13
15
  matrix:
@@ -17,5 +19,7 @@ matrix:
17
19
  - rvm: jruby
18
20
  os: linux
19
21
  env: MINIMAL_SUPPORT=1
20
-
22
+ - rvm: truffleruby
23
+ os: linux
24
+ env: MINIMAL_SUPPORT=1
21
25
  script: bin/ci
@@ -1,3 +1,35 @@
1
+ # 1.4.7
2
+
3
+ * Various performance enhancements
4
+ * Fix race condition in heavy concurrent load scenarios that would cause bootsnap to raise
5
+
6
+ # 1.4.6
7
+
8
+ * Fix bug that was erroneously considering that files containing `.` in the names were being
9
+ required if a different file with the same name was already being required
10
+
11
+ Example:
12
+
13
+ require 'foo'
14
+ require 'foo.en'
15
+
16
+ Before bootsnap was considering `foo.en` to be the same file as `foo`
17
+
18
+ * Use glibc as part of the ruby_platform cache key
19
+
20
+ # 1.4.5
21
+
22
+ * MRI 2.7 support
23
+ * Fixed concurrency bugs
24
+
25
+ # 1.4.4
26
+
27
+ * Disable ISeq cache in `bootsnap/setup` by default in Ruby 2.5
28
+
29
+ # 1.4.3
30
+
31
+ * Fix some cache permissions and umask issues after switch to mkstemp
32
+
1
33
  # 1.4.2
2
34
 
3
35
  * Fix bug when removing features loaded by relative path from `$LOADED_FEATURES`
data/Gemfile CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  source 'https://rubygems.org'
2
3
 
3
4
  # Specify your gem's dependencies in bootsnap.gemspec
@@ -152,7 +152,7 @@ close n
152
152
  Bootsnap は、64バイトのヘッダーとそれに続くキャッシュの内容を含んだキャッシュファイルを書き込みます。ヘッダーは、次のいくつかのフィールドで構成されるキャッシュキーです。
153
153
 
154
154
  - `version`、Bootsnapにハードコードされる基本的なスキーマのバージョン
155
- - `os_version`、(macOS, BSDの) 現在のカーネルバージョンか (Linuxの) glibc のバージョンのハッシュ
155
+ - `ruby_platform`、`RUBY_PLATFORM`(x86_64-linux-gnuなど)変数とglibcバージョン(Linuxの場合)またはOSバージョン(BSD、macOSの場合は` uname -v`)のハッシュ
156
156
  - `compile_option`、`RubyVM::InstructionSequence.compile_option` の返り値
157
157
  - `ruby_revision`、コンパイルされたRubyのバージョン
158
158
  - `size`、ソースファイルのサイズ
data/README.md CHANGED
@@ -214,7 +214,7 @@ Bootsnap writes a cache file containing a 64 byte header followed by the cache c
214
214
  is a cache key including several fields:
215
215
 
216
216
  * `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);
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)
218
218
  * `compile_option`, which changes with `RubyVM::InstructionSequence.compile_option` does;
219
219
  * `ruby_revision`, the version of Ruby this was compiled with;
220
220
  * `size`, the size of the source file;
data/Rakefile CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require('rake/extensiontask')
2
3
  require('bundler/gem_tasks')
3
4
 
@@ -9,4 +10,8 @@ Rake::ExtensionTask.new do |ext|
9
10
  ext.gem_spec = gemspec
10
11
  end
11
12
 
12
- task(default: :compile)
13
+ task :test do
14
+ sh 'bin/testunit'
15
+ end
16
+
17
+ task(default: %i(compile test))
data/bin/ci CHANGED
@@ -5,6 +5,5 @@ set -euxo pipefail
5
5
  if [[ "${MINIMAL_SUPPORT-0}" -eq 1 ]]; then
6
6
  exec bin/test-minimal-support
7
7
  else
8
- rake
9
- exec bin/testunit
8
+ exec rake
10
9
  fi
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require("bundler/setup")
4
5
  require("bootsnap")
@@ -1,4 +1,5 @@
1
1
  # coding: utf-8
2
+ # frozen_string_literal: true
2
3
  lib = File.expand_path('../lib', __FILE__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
  require('bootsnap/version')
@@ -26,7 +27,7 @@ Gem::Specification.new do |spec|
26
27
  end
27
28
  spec.require_paths = %w(lib)
28
29
 
29
- spec.required_ruby_version = '>= 2.0.0'
30
+ spec.required_ruby_version = '>= 2.3.0'
30
31
 
31
32
  if RUBY_PLATFORM =~ /java/
32
33
  spec.platform = 'java'
@@ -36,7 +37,7 @@ Gem::Specification.new do |spec|
36
37
  end
37
38
 
38
39
  spec.add_development_dependency("bundler")
39
- spec.add_development_dependency('rake', '~> 10.0')
40
+ spec.add_development_dependency('rake')
40
41
  spec.add_development_dependency('rake-compiler', '~> 0')
41
42
  spec.add_development_dependency("minitest", "~> 5.0")
42
43
  spec.add_development_dependency("mocha", "~> 1.2")
@@ -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;
@@ -88,12 +95,13 @@ static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handl
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, 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
101
  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);
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 * output_data, int * exception_tag, const char ** errno_provenance);
104
+ static uint32_t get_ruby_revision(void);
97
105
  static uint32_t get_ruby_platform(void);
98
106
 
99
107
  /*
@@ -134,7 +142,7 @@ Init_bootsnap(void)
134
142
  rb_mBootsnap_CompileCache_Native = rb_define_module_under(rb_mBootsnap_CompileCache, "Native");
135
143
  rb_eBootsnap_CompileCache_Uncompilable = rb_define_class_under(rb_mBootsnap_CompileCache, "Uncompilable", rb_eStandardError);
136
144
 
137
- current_ruby_revision = FIX2INT(rb_const_get(rb_cObject, rb_intern("RUBY_REVISION")));
145
+ current_ruby_revision = get_ruby_revision();
138
146
  current_ruby_platform = get_ruby_platform();
139
147
 
140
148
  uncompilable = rb_intern("__bootsnap_uncompilable__");
@@ -142,6 +150,9 @@ Init_bootsnap(void)
142
150
  rb_define_module_function(rb_mBootsnap_CompileCache_Native, "coverage_running?", bs_rb_coverage_running, 0);
143
151
  rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch, 3);
144
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);
145
156
  }
146
157
 
147
158
  /*
@@ -191,6 +202,26 @@ fnv1a_64(const char *str)
191
202
  return fnv1a_64_iter(h, str);
192
203
  }
193
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
+
194
225
  /*
195
226
  * When ruby's version doesn't change, but it's recompiled on a different OS
196
227
  * (or OS version), we need to invalidate the cache.
@@ -210,6 +241,9 @@ get_ruby_platform(void)
210
241
 
211
242
  #ifdef _WIN32
212
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);
213
247
  #else
214
248
  struct utsname utsname;
215
249
 
@@ -230,7 +264,7 @@ get_ruby_platform(void)
230
264
  * The path will look something like: <cachedir>/12/34567890abcdef
231
265
  */
232
266
  static void
233
- bs_cache_path(const char * cachedir, const char * path, char ** cache_path)
267
+ bs_cache_path(const char * cachedir, const char * path, char (* cache_path)[MAX_CACHEPATH_SIZE])
234
268
  {
235
269
  uint64_t hash = fnv1a_64(path);
236
270
 
@@ -282,10 +316,8 @@ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
282
316
  char * path = RSTRING_PTR(path_v);
283
317
  char cache_path[MAX_CACHEPATH_SIZE];
284
318
 
285
- { /* generate cache path to cache_path */
286
- char * tmp = (char *)&cache_path;
287
- bs_cache_path(cachedir, path, &tmp);
288
- }
319
+ /* generate cache path to cache_path */
320
+ bs_cache_path(cachedir, path, &cache_path);
289
321
 
290
322
  return bs_fetch(path, path_v, cache_path, handler);
291
323
  }
@@ -295,14 +327,14 @@ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
295
327
  * was loaded.
296
328
  */
297
329
  static int
298
- open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenance)
330
+ open_current_file(char * path, struct bs_cache_key * key, const char ** errno_provenance)
299
331
  {
300
332
  struct stat statbuf;
301
333
  int fd;
302
334
 
303
335
  fd = open(path, O_RDONLY);
304
336
  if (fd < 0) {
305
- *errno_provenance = (char *)"bs_fetch:open_current_file:open";
337
+ *errno_provenance = "bs_fetch:open_current_file:open";
306
338
  return fd;
307
339
  }
308
340
  #ifdef _WIN32
@@ -310,7 +342,7 @@ open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenan
310
342
  #endif
311
343
 
312
344
  if (fstat(fd, &statbuf) < 0) {
313
- *errno_provenance = (char *)"bs_fetch:open_current_file:fstat";
345
+ *errno_provenance = "bs_fetch:open_current_file:fstat";
314
346
  close(fd);
315
347
  return -1;
316
348
  }
@@ -356,13 +388,13 @@ bs_read_key(int fd, struct bs_cache_key * key)
356
388
  * - ERROR_WITH_ERRNO (-1, errno is set)
357
389
  */
358
390
  static int
359
- open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_provenance)
391
+ open_cache_file(const char * path, struct bs_cache_key * key, const char ** errno_provenance)
360
392
  {
361
393
  int fd, res;
362
394
 
363
395
  fd = open(path, O_RDONLY);
364
396
  if (fd < 0) {
365
- *errno_provenance = (char *)"bs_fetch:open_cache_file:open";
397
+ *errno_provenance = "bs_fetch:open_cache_file:open";
366
398
  if (errno == ENOENT) return CACHE_MISSING_OR_INVALID;
367
399
  return ERROR_WITH_ERRNO;
368
400
  }
@@ -372,7 +404,7 @@ open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_prov
372
404
 
373
405
  res = bs_read_key(fd, key);
374
406
  if (res < 0) {
375
- *errno_provenance = (char *)"bs_fetch:open_cache_file:read";
407
+ *errno_provenance = "bs_fetch:open_cache_file:read";
376
408
  close(fd);
377
409
  return res;
378
410
  }
@@ -396,7 +428,7 @@ open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_prov
396
428
  * or exception, will be the final data returnable to the user.
397
429
  */
398
430
  static int
399
- fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag, char ** errno_provenance)
431
+ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag, const char ** errno_provenance)
400
432
  {
401
433
  char * data = NULL;
402
434
  ssize_t nread;
@@ -405,7 +437,7 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data,
405
437
  VALUE storage_data;
406
438
 
407
439
  if (data_size > 100000000000) {
408
- *errno_provenance = (char *)"bs_fetch:fetch_cached_data:datasize";
440
+ *errno_provenance = "bs_fetch:fetch_cached_data:datasize";
409
441
  errno = EINVAL; /* because wtf? */
410
442
  ret = -1;
411
443
  goto done;
@@ -413,7 +445,7 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data,
413
445
  data = ALLOC_N(char, data_size);
414
446
  nread = read(fd, data, data_size);
415
447
  if (nread < 0) {
416
- *errno_provenance = (char *)"bs_fetch:fetch_cached_data:read";
448
+ *errno_provenance = "bs_fetch:fetch_cached_data:read";
417
449
  ret = -1;
418
450
  goto done;
419
451
  }
@@ -465,30 +497,36 @@ mkpath(char * file_path, mode_t mode)
465
497
  * path.
466
498
  */
467
499
  static int
468
- atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char ** errno_provenance)
500
+ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, const char ** errno_provenance)
469
501
  {
470
502
  char template[MAX_CACHEPATH_SIZE + 20];
471
503
  char * tmp_path;
472
- int fd, ret;
504
+ int fd, ret, attempt;
473
505
  ssize_t nwrite;
474
506
 
475
- tmp_path = strncpy(template, path, MAX_CACHEPATH_SIZE);
476
- strcat(tmp_path, ".tmp.XXXXXX");
507
+ for (attempt = 0; attempt < MAX_CREATE_TEMPFILE_ATTEMPT; ++attempt) {
508
+ tmp_path = strncpy(template, path, MAX_CACHEPATH_SIZE);
509
+ strcat(tmp_path, ".tmp.XXXXXX");
477
510
 
478
- // mkstemp modifies the template to be the actual created path
479
- fd = mkstemp(tmp_path);
480
- if (fd < 0) {
481
- if (mkpath(tmp_path, 0775) < 0) {
482
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:mkpath";
483
- return -1;
484
- }
485
- close(fd);
486
- fd = open(tmp_path, O_WRONLY | O_CREAT, 0664);
487
- if (fd < 0) {
488
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:open";
511
+ // mkstemp modifies the template to be the actual created path
512
+ fd = mkstemp(tmp_path);
513
+ if (fd > 0) break;
514
+
515
+ if (attempt == 0 && mkpath(tmp_path, 0775) < 0) {
516
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:mkpath";
489
517
  return -1;
490
518
  }
491
519
  }
520
+ if (fd < 0) {
521
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:mkstemp";
522
+ return -1;
523
+ }
524
+
525
+ if (chmod(tmp_path, 0644) < 0) {
526
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:chmod";
527
+ return -1;
528
+ }
529
+
492
530
  #ifdef _WIN32
493
531
  setmode(fd, O_BINARY);
494
532
  #endif
@@ -496,11 +534,11 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
496
534
  key->data_size = RSTRING_LEN(data);
497
535
  nwrite = write(fd, key, KEY_SIZE);
498
536
  if (nwrite < 0) {
499
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:write";
537
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:write";
500
538
  return -1;
501
539
  }
502
540
  if (nwrite != KEY_SIZE) {
503
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:keysize";
541
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:keysize";
504
542
  errno = EIO; /* Lies but whatever */
505
543
  return -1;
506
544
  }
@@ -508,7 +546,7 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
508
546
  nwrite = write(fd, RSTRING_PTR(data), RSTRING_LEN(data));
509
547
  if (nwrite < 0) return -1;
510
548
  if (nwrite != RSTRING_LEN(data)) {
511
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:writelength";
549
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:writelength";
512
550
  errno = EIO; /* Lies but whatever */
513
551
  return -1;
514
552
  }
@@ -516,7 +554,12 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
516
554
  close(fd);
517
555
  ret = rename(tmp_path, path);
518
556
  if (ret < 0) {
519
- *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:rename";
557
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:rename";
558
+ return -1;
559
+ }
560
+ ret = chmod(path, 0664 & ~current_umask);
561
+ if (ret < 0) {
562
+ *errno_provenance = "bs_fetch:atomic_write_cache_file:chmod";
520
563
  }
521
564
  return ret;
522
565
  }
@@ -525,13 +568,13 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char
525
568
  /* Read contents from an fd, whose contents are asserted to be +size+ bytes
526
569
  * long, into a buffer */
527
570
  static ssize_t
528
- bs_read_contents(int fd, size_t size, char ** contents, char ** errno_provenance)
571
+ bs_read_contents(int fd, size_t size, char ** contents, const char ** errno_provenance)
529
572
  {
530
573
  ssize_t nread;
531
574
  *contents = ALLOC_N(char, size);
532
575
  nread = read(fd, *contents, size);
533
576
  if (nread < 0) {
534
- *errno_provenance = (char *)"bs_fetch:bs_read_contents:read";
577
+ *errno_provenance = "bs_fetch:bs_read_contents:read";
535
578
  }
536
579
  return nread;
537
580
  }
@@ -587,7 +630,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
587
630
  char * contents = NULL;
588
631
  int cache_fd = -1, current_fd = -1;
589
632
  int res, valid_cache = 0, exception_tag = 0;
590
- char * errno_provenance = NULL;
633
+ const char * errno_provenance = NULL;
591
634
 
592
635
  VALUE input_data; /* data read from source file, e.g. YAML or ruby source */
593
636
  VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
@@ -655,7 +698,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
655
698
  * using input_to_output */
656
699
  if (NIL_P(output_data)) {
657
700
  if (unlink(cache_path) < 0) {
658
- errno_provenance = (char *)"bs_fetch:unlink";
701
+ errno_provenance = "bs_fetch:unlink";
659
702
  goto fail_errno;
660
703
  }
661
704
  bs_input_to_output(handler, input_data, &output_data, &exception_tag);
@@ -766,7 +809,7 @@ try_input_to_storage(VALUE arg)
766
809
  }
767
810
 
768
811
  static VALUE
769
- rescue_input_to_storage(VALUE arg)
812
+ rescue_input_to_storage(VALUE arg, VALUE e)
770
813
  {
771
814
  return uncompilable;
772
815
  }
@@ -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,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative('bootsnap/version')
2
4
  require_relative('bootsnap/bundler')
3
5
  require_relative('bootsnap/load_path_cache')
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Bootsnap
2
3
  extend(self)
3
4
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Bootsnap
2
3
  module CompileCache
3
4
  Error = Class.new(StandardError)
@@ -28,7 +29,7 @@ module Bootsnap
28
29
  raise(
29
30
  PermissionError,
30
31
  "bootsnap doesn't have permission to write cache entries in '#{cpath}' " \
31
- "(or, less likely, doesn't have permisison to read '#{path}')",
32
+ "(or, less likely, doesn't have permission to read '#{path}')",
32
33
  )
33
34
  end
34
35
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require('bootsnap/bootsnap')
2
3
  require('zlib')
3
4
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require('bootsnap/bootsnap')
2
3
 
3
4
  module Bootsnap
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Bootsnap
2
3
  module ExplicitRequire
3
4
  ARCHDIR = RbConfig::CONFIG['archdir']
@@ -46,7 +46,7 @@ module Bootsnap
46
46
  # loadpath.
47
47
  def find(feature)
48
48
  reinitialize if (@has_relative_paths && dir_changed?) || stale?
49
- feature = feature.to_s
49
+ feature = feature.to_s.freeze
50
50
  return feature if absolute_path?(feature)
51
51
  return expand_path(feature) if feature.start_with?('./')
52
52
  @mutex.synchronize do
@@ -67,7 +67,7 @@ module Bootsnap
67
67
  # native dynamic extension, e.g. .bundle or .so), we know it was a
68
68
  # failure and there's nothing more we can do to find the file.
69
69
  # no extension, .rb, (.bundle or .so)
70
- when '', *CACHED_EXTENSIONS # rubocop:disable Performance/CaseWhenSplat
70
+ when '', *CACHED_EXTENSIONS
71
71
  nil
72
72
  # Ruby allows specifying native extensions as '.so' even when DLEXT
73
73
  # is '.bundle'. This is where we handle that case.
@@ -144,7 +144,7 @@ module Bootsnap
144
144
  expanded_path = p.expanded_path
145
145
  entries, dirs = p.entries_and_dirs(@store)
146
146
  # push -> low precedence -> set only if unset
147
- dirs.each { |dir| @dirs[dir] ||= path }
147
+ dirs.each { |dir| @dirs[dir] ||= path }
148
148
  entries.each { |rel| @index[rel] ||= expanded_path }
149
149
  end
150
150
  end
@@ -178,25 +178,25 @@ module Bootsnap
178
178
 
179
179
  if DLEXT2
180
180
  def search_index(f)
181
- try_index(f + DOT_RB) || try_index(f + DLEXT) || try_index(f + DLEXT2) || try_index(f)
181
+ try_index("#{f}#{DOT_RB}") || try_index("#{f}#{DLEXT}") || try_index("#{f}#{DLEXT2}") || try_index(f)
182
182
  end
183
183
 
184
184
  def maybe_append_extension(f)
185
- try_ext(f + DOT_RB) || try_ext(f + DLEXT) || try_ext(f + DLEXT2) || f
185
+ try_ext("#{f}#{DOT_RB}") || try_ext("#{f}#{DLEXT}") || try_ext("#{f}#{DLEXT2}") || f
186
186
  end
187
187
  else
188
188
  def search_index(f)
189
- try_index(f + DOT_RB) || try_index(f + DLEXT) || try_index(f)
189
+ try_index("#{f}#{DOT_RB}") || try_index("#{f}#{DLEXT}") || try_index(f)
190
190
  end
191
191
 
192
192
  def maybe_append_extension(f)
193
- try_ext(f + DOT_RB) || try_ext(f + DLEXT) || f
193
+ try_ext("#{f}#{DOT_RB}") || try_ext("#{f}#{DLEXT}") || f
194
194
  end
195
195
  end
196
196
 
197
197
  def try_index(f)
198
198
  if (p = @index[f])
199
- p + '/' + f
199
+ "#{p}/#{f}"
200
200
  end
201
201
  end
202
202
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Bootsnap
2
3
  module LoadPathCache
3
4
  module ChangeObserver
@@ -25,7 +26,7 @@ module Bootsnap
25
26
  super
26
27
  end
27
28
 
28
- # uniq! keeps the first occurance of each path, otherwise preserving
29
+ # uniq! keeps the first occurrence of each path, otherwise preserving
29
30
  # order, preserving the effective load path
30
31
  def uniq!(*args)
31
32
  ret = super
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Bootsnap
2
3
  module LoadPathCache
3
4
  module CoreExt
@@ -1,8 +1,9 @@
1
+ # frozen_string_literal: true
1
2
  module Bootsnap
2
3
  module LoadPathCache
3
4
  module CoreExt
4
5
  def self.make_load_error(path)
5
- err = LoadError.new("cannot load such file -- #{path}")
6
+ err = LoadError.new(+"cannot load such file -- #{path}")
6
7
  err.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true)
7
8
  err.define_singleton_method(:path) { path }
8
9
  err
@@ -55,7 +56,7 @@ module Kernel
55
56
  end
56
57
 
57
58
  # load also allows relative paths from pwd even when not in $:
58
- if File.exist?(relative = File.expand_path(path))
59
+ if File.exist?(relative = File.expand_path(path).freeze)
59
60
  return load_without_bootsnap(relative, wrap)
60
61
  end
61
62
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  class << $LOADED_FEATURES
2
3
  alias_method(:delete_without_bootsnap, :delete)
3
4
  def delete(key)
@@ -40,7 +40,7 @@ module Bootsnap
40
40
  # /a/b/lib/my/foo.rb
41
41
  # ^^^^^^^^^
42
42
  short = feat[(lpe.length + 1)..-1]
43
- stripped = strip_extension(short)
43
+ stripped = strip_extension_if_elidable(short)
44
44
  @lfi[short] = hash
45
45
  @lfi[stripped] = hash
46
46
  end
@@ -94,13 +94,14 @@ module Bootsnap
94
94
 
95
95
  hash = long.hash
96
96
 
97
- # do we have 'bundler' or 'bundler.rb'?
98
- altname = if File.extname(short) != ''
99
- # strip the path from 'bundler.rb' -> 'bundler'
100
- strip_extension(short)
101
- elsif long && (ext = File.extname(long))
102
- # get the extension from the expanded path if given
103
- # 'bundler' + '.rb'
97
+ # Do we have a filename with an elidable extension, e.g.,
98
+ # 'bundler.rb', or 'libgit2.so'?
99
+ altname = if extension_elidable?(short)
100
+ # Strip the extension off, e.g. 'bundler.rb' -> 'bundler'.
101
+ strip_extension_if_elidable(short)
102
+ elsif long && (ext = File.extname(long.freeze))
103
+ # We already know the extension of the actual file this
104
+ # resolves to, so put that back on.
104
105
  short + ext
105
106
  end
106
107
 
@@ -117,8 +118,30 @@ module Bootsnap
117
118
  STRIP_EXTENSION = /\.[^.]*?$/
118
119
  private_constant(:STRIP_EXTENSION)
119
120
 
120
- def strip_extension(f)
121
- f.sub(STRIP_EXTENSION, '')
121
+ # Might Ruby automatically search for this extension if
122
+ # someone tries to 'require' the file without it? E.g. Ruby
123
+ # will implicitly try 'x.rb' if you ask for 'x'.
124
+ #
125
+ # This is complex and platform-dependent, and the Ruby docs are a little
126
+ # handwavy about what will be tried when and in what order.
127
+ # So optimistically pretend that all known elidable extensions
128
+ # will be tried on all platforms, and that people are unlikely
129
+ # to name files in a way that assumes otherwise.
130
+ # (E.g. It's unlikely that someone will know that their code
131
+ # will _never_ run on MacOS, and therefore think they can get away
132
+ # with calling a Ruby file 'x.dylib.rb' and then requiring it as 'x.dylib'.)
133
+ #
134
+ # See <https://ruby-doc.org/core-2.6.4/Kernel.html#method-i-require>.
135
+ def extension_elidable?(f)
136
+ f.to_s.end_with?('.rb', '.so', '.o', '.dll', '.dylib')
137
+ end
138
+
139
+ def strip_extension_if_elidable(f)
140
+ if extension_elidable?(f)
141
+ f.sub(STRIP_EXTENSION, '')
142
+ else
143
+ f
144
+ end
122
145
  end
123
146
  end
124
147
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require_relative('path_scanner')
2
3
 
3
4
  module Bootsnap
@@ -20,7 +21,7 @@ module Bootsnap
20
21
  attr_reader(:path)
21
22
 
22
23
  def initialize(path)
23
- @path = path.to_s
24
+ @path = path.to_s.freeze
24
25
  end
25
26
 
26
27
  # True if the path exists, but represents a non-directory object
@@ -59,7 +60,7 @@ module Bootsnap
59
60
  end
60
61
 
61
62
  def expanded_path
62
- File.expand_path(path)
63
+ File.expand_path(path).freeze
63
64
  end
64
65
 
65
66
  private
@@ -5,7 +5,6 @@ require_relative('../explicit_require')
5
5
  module Bootsnap
6
6
  module LoadPathCache
7
7
  module PathScanner
8
- ALL_FILES = "/{,**/*/**/}*"
9
8
  REQUIRABLE_EXTENSIONS = [DOT_RB] + DL_EXTENSIONS
10
9
  NORMALIZE_NATIVE_EXTENSIONS = !DL_EXTENSIONS.include?(LoadPathCache::DOT_SO)
11
10
  ALTERNATIVE_NATIVE_EXTENSIONS_PATTERN = /\.(o|bundle|dylib)\z/
@@ -16,34 +15,48 @@ module Bootsnap
16
15
  ''
17
16
  end
18
17
 
19
- def self.call(path)
20
- path = path.to_s
21
-
22
- relative_slice = (path.size + 1)..-1
23
- # If the bundle path is a descendent of this path, we do additional
24
- # checks to prevent recursing into the bundle path as we recurse
25
- # through this path. We don't want to scan the bundle path because
26
- # anything useful in it will be present on other load path items.
27
- #
28
- # This can happen if, for example, the user adds '.' to the load path,
29
- # and the bundle path is '.bundle'.
30
- contains_bundle_path = BUNDLE_PATH.start_with?(path)
31
-
32
- dirs = []
33
- requirables = []
34
-
35
- Dir.glob(path + ALL_FILES).each do |absolute_path|
36
- next if contains_bundle_path && absolute_path.start_with?(BUNDLE_PATH)
37
- relative_path = absolute_path.slice(relative_slice)
38
-
39
- if File.directory?(absolute_path)
40
- dirs << relative_path
41
- elsif REQUIRABLE_EXTENSIONS.include?(File.extname(relative_path))
42
- requirables << relative_path
18
+ class << self
19
+ def call(path)
20
+ path = File.expand_path(path.to_s).freeze
21
+ return [[], []] unless File.directory?(path)
22
+
23
+ # If the bundle path is a descendent of this path, we do additional
24
+ # checks to prevent recursing into the bundle path as we recurse
25
+ # through this path. We don't want to scan the bundle path because
26
+ # anything useful in it will be present on other load path items.
27
+ #
28
+ # This can happen if, for example, the user adds '.' to the load path,
29
+ # and the bundle path is '.bundle'.
30
+ contains_bundle_path = BUNDLE_PATH.start_with?(path)
31
+
32
+ dirs = []
33
+ requirables = []
34
+ walk(path, nil) do |relative_path, absolute_path, is_directory|
35
+ if is_directory
36
+ dirs << relative_path
37
+ !contains_bundle_path || !absolute_path.start_with?(BUNDLE_PATH)
38
+ elsif relative_path.end_with?(*REQUIRABLE_EXTENSIONS)
39
+ requirables << relative_path
40
+ end
43
41
  end
42
+ [requirables, dirs]
44
43
  end
45
44
 
46
- [requirables, dirs]
45
+ def walk(absolute_dir_path, relative_dir_path, &block)
46
+ Dir.foreach(absolute_dir_path) do |name|
47
+ next if name.start_with?('.')
48
+ relative_path = relative_dir_path ? "#{relative_dir_path}/#{name}" : name.freeze
49
+
50
+ absolute_path = "#{absolute_dir_path}/#{name}"
51
+ if File.directory?(absolute_path)
52
+ if yield relative_path, absolute_path, true
53
+ walk(absolute_path, relative_path, &block)
54
+ end
55
+ else
56
+ yield relative_path, absolute_path, false
57
+ end
58
+ end
59
+ end
47
60
  end
48
61
  end
49
62
  end
@@ -15,15 +15,15 @@ module Bootsnap
15
15
 
16
16
  def realpath(caller_location, path)
17
17
  base = File.dirname(caller_location)
18
- file = find_file(File.expand_path(path, base))
19
- dir = File.dirname(file)
20
- File.join(dir, File.basename(file))
18
+ abspath = File.expand_path(path, base).freeze
19
+ find_file(abspath)
21
20
  end
22
21
 
23
22
  def find_file(name)
24
- ['', *CACHED_EXTENSIONS].each do |ext|
23
+ return File.realpath(name).freeze if File.exist?(name)
24
+ CACHED_EXTENSIONS.each do |ext|
25
25
  filename = "#{name}#{ext}"
26
- return File.realpath(filename) if File.exist?(filename)
26
+ return File.realpath(filename).freeze if File.exist?(filename)
27
27
  end
28
28
  name
29
29
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require_relative('../explicit_require')
2
3
 
3
4
  Bootsnap::ExplicitRequire.with_gems('msgpack') { require('msgpack') }
@@ -11,7 +12,8 @@ module Bootsnap
11
12
 
12
13
  def initialize(store_path)
13
14
  @store_path = store_path
14
- @in_txn = false
15
+ # TODO: Remove conditional once Ruby 2.2 support is dropped.
16
+ @txn_mutex = defined?(::Mutex) ? ::Mutex.new : ::Thread::Mutex.new
15
17
  @dirty = false
16
18
  load_data
17
19
  end
@@ -21,7 +23,7 @@ module Bootsnap
21
23
  end
22
24
 
23
25
  def fetch(key)
24
- raise(SetOutsideTransactionNotAllowed) unless @in_txn
26
+ raise(SetOutsideTransactionNotAllowed) unless @txn_mutex.owned?
25
27
  v = get(key)
26
28
  unless v
27
29
  @dirty = true
@@ -32,7 +34,7 @@ module Bootsnap
32
34
  end
33
35
 
34
36
  def set(key, value)
35
- raise(SetOutsideTransactionNotAllowed) unless @in_txn
37
+ raise(SetOutsideTransactionNotAllowed) unless @txn_mutex.owned?
36
38
  if value != @data[key]
37
39
  @dirty = true
38
40
  @data[key] = value
@@ -40,12 +42,14 @@ module Bootsnap
40
42
  end
41
43
 
42
44
  def transaction
43
- raise(NestedTransactionError) if @in_txn
44
- @in_txn = true
45
- yield
46
- ensure
47
- commit_transaction
48
- @in_txn = false
45
+ raise(NestedTransactionError) if @txn_mutex.owned?
46
+ @txn_mutex.synchronize do
47
+ begin
48
+ yield
49
+ ensure
50
+ commit_transaction
51
+ end
52
+ end
49
53
  end
50
54
 
51
55
  private
@@ -60,11 +64,11 @@ module Bootsnap
60
64
  def load_data
61
65
  @data = begin
62
66
  MessagePack.load(File.binread(@store_path))
63
- # handle malformed data due to upgrade incompatability
64
- rescue Errno::ENOENT, MessagePack::MalformedFormatError, MessagePack::UnknownExtTypeError, EOFError
65
- {}
66
- rescue ArgumentError => e
67
- e.message =~ /negative array size/ ? {} : raise
67
+ # handle malformed data due to upgrade incompatibility
68
+ rescue Errno::ENOENT, MessagePack::MalformedFormatError, MessagePack::UnknownExtTypeError, EOFError
69
+ {}
70
+ rescue ArgumentError => e
71
+ e.message =~ /negative array size/ ? {} : raise
68
72
  end
69
73
  end
70
74
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require_relative('../bootsnap')
2
3
 
3
4
  env = ENV['RAILS_ENV'] || ENV['RACK_ENV'] || ENV['ENV']
@@ -24,12 +25,15 @@ unless cache_dir
24
25
  cache_dir = File.join(app_root, 'tmp', 'cache')
25
26
  end
26
27
 
28
+ ruby_version = Gem::Version.new(RUBY_VERSION)
29
+ iseq_cache_enabled = ruby_version < Gem::Version.new('2.5.0') || ruby_version >= Gem::Version.new('2.6.0')
30
+
27
31
  Bootsnap.setup(
28
32
  cache_dir: cache_dir,
29
33
  development_mode: development_mode,
30
34
  load_path_cache: true,
31
35
  autoload_paths_cache: true, # assume rails. open to PRs to impl. detection
32
36
  disable_trace: false,
33
- compile_cache_iseq: true,
37
+ compile_cache_iseq: iseq_cache_enabled,
34
38
  compile_cache_yaml: true,
35
39
  )
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Bootsnap
2
- VERSION = "1.4.2"
3
+ VERSION = "1.4.7"
3
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bootsnap
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.2
4
+ version: 1.4.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Burke Libbey
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-03-26 00:00:00.000000000 Z
11
+ date: 2020-07-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -28,16 +28,16 @@ dependencies:
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '10.0'
40
+ version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rake-compiler
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -160,15 +160,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
160
160
  requirements:
161
161
  - - ">="
162
162
  - !ruby/object:Gem::Version
163
- version: 2.0.0
163
+ version: 2.3.0
164
164
  required_rubygems_version: !ruby/object:Gem::Requirement
165
165
  requirements:
166
166
  - - ">="
167
167
  - !ruby/object:Gem::Version
168
168
  version: '0'
169
169
  requirements: []
170
- rubyforge_project:
171
- rubygems_version: 2.7.6
170
+ rubygems_version: 3.0.2
172
171
  signing_key:
173
172
  specification_version: 4
174
173
  summary: Boot large ruby/rails apps faster