bootsnap 1.7.5 → 1.11.1

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: b7dac6bb1ac2018e46d1cdac77a243cc1e2d46d8dc24deb0a9248320451549ba
4
- data.tar.gz: 9cd95e8c52993daffd79f1d21961c5754f88642250c9552881f30f26f9dcc462
3
+ metadata.gz: 1cc142349008790c310bcbc875a437996403242e579f85fb48f46eceecb383ad
4
+ data.tar.gz: fa1d21fafa8a4fa4d44ced76d597b773d3321e077dc815ccbcae3fe9dc4e661a
5
5
  SHA512:
6
- metadata.gz: e6cee7f1ed6c30d819e576fa79d1e627585996d139dd4add684fc22d4bf720ae1c0f778bb1ee23c2a7819d788c7956b9632f03b5861bb7f6e507f3562687b70e
7
- data.tar.gz: 505de0bcbfa90ab03fde256bea4dec4f5728482b61d452887da7a49c84894daff61c1aa0b16a2a114ddab142ae2a75d09f4447de80a6b33068ea0c15ec0fdd8f
6
+ metadata.gz: 81445538692ae0dc34b7309c44195fce5c864e508afe6eed4124d3d87bc1bfa07fd2ddb2c602faa2409f1537f8da63b885fd692bdc74e961febdd2a39e6e1919
7
+ data.tar.gz: 6389ce3a667c528660976665ce8184fb61d638da6cd4040e7b2d55441679574bdf691e4991294396c19a6e79bbf185ba1cdca848d5ea499a5b8e197dca143a5f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,114 @@
1
1
  # Unreleased
2
2
 
3
+ # 1.11.1
4
+
5
+ * Fix the `can't modify frozen Hash` error on load path cache mutation. See #411.
6
+
7
+ # 1.11.0
8
+
9
+ * Drop dependency on `fileutils`.
10
+
11
+ * Better respect `Kernel#require` duck typing. While it almost never comes up in practice, `Kernel#require`
12
+ follow a fairly intricate duck-typing protocol on its argument implemented as `rb_get_path(VALUE)` in MRI.
13
+ So when applicable we bind `rb_get_path` and use it for improved compatibility. See #396 and #406.
14
+
15
+ * Get rid of the `Kernel.require_relative` decorator by resolving `$LOAD_PATH` members to their real path.
16
+ This way we handle symlinks in `$LOAD_PATH` much more efficiently. See #402 for the detailed explanation.
17
+
18
+ * Drop support for Ruby 2.3 (to allow getting rid of the `Kernel.require_relative` decorator).
19
+
20
+ # 1.10.3
21
+
22
+ * Fix Regexp and Date type support in YAML compile cache. (#400)
23
+
24
+ * Improve the YAML compile cache to support `UTF-8` symbols. (#398, #399)
25
+ [The default `MessagePack` symbol serializer assumes all symbols are ASCII](https://github.com/msgpack/msgpack-ruby/pull/211),
26
+ because of this, non-ASCII compatible symbol would be restored with `ASCII_8BIT` encoding (AKA `BINARY`).
27
+ Bootsnap now properly cache them in `UTF-8`.
28
+
29
+ Note that the above only apply for actual YAML symbols (e..g `--- :foo`).
30
+ The issue is still present for string keys parsed with `YAML.load_file(..., symbolize_names: true)`, that is a bug
31
+ in `msgpack` that will hopefully be solved soon, see: https://github.com/msgpack/msgpack-ruby/pull/246
32
+
33
+ * Entirely disable the YAML compile cache if `Encoding.default_internal` is set to an encoding not supported by `msgpack`. (#398)
34
+ `Psych` coerce strings to `Encoding.default_internal`, but `MessagePack` doesn't. So in this scenario we can't provide
35
+ YAML caching at all without returning the strings in the wrong encoding.
36
+ This never came up in practice but might as well be safe.
37
+
38
+ # 1.10.2
39
+
40
+ * Reduce the `Kernel.require` extra stack frames some more. Now bootsnap should only add one extra frame per `require` call.
41
+
42
+ * Better check `freeze` option support in JSON compile cache.
43
+ Previously `JSON.load_file(..., freeze: true)` would be cached even when the msgpack version is missing support for it.
44
+
45
+ # 1.10.1
46
+
47
+ * Fix `Kernel#autoload`'s fallback path always being executed.
48
+
49
+ * Consider `unlink` failing with `ENOENT` as a success.
50
+
51
+ # 1.10.0
52
+
53
+ * Delay requiring `FileUtils`. (#285)
54
+ `FileUtils` can be installed as a gem, so it's best to wait for bundler to have setup the load path before requiring it.
55
+
56
+ * Improve support of Psych 4. (#392)
57
+ Since `1.8.0`, `YAML.load_file` was no longer cached when Psych 4 was used. This is because `load_file` loads
58
+ in safe mode by default, so the Bootsnap cache could defeat that safety.
59
+ Now when precompiling YAML files, Bootsnap first try to parse them in safe mode, and if it can't fallback to unsafe mode,
60
+ and the cache contains a flag that records whether it was generated in safe mode or not.
61
+ `YAML.unsafe_load_file` will use safe caches just fine, but `YAML.load_file` will fallback to uncached YAML parsing
62
+ if the cache was generated using unsafe parsing.
63
+
64
+ * Minimize the Kernel.require extra stack frames. (#393)
65
+ This should reduce the noise generated by bootsnap on `LoadError`.
66
+
67
+ # 1.9.4
68
+
69
+ * Ignore absolute paths in the loaded feature index. (#385)
70
+ This fixes a compatibility issue with Zeitwerk when Zeitwerk is loaded before bootsnap. It also should
71
+ reduce the memory usage and improve load performance of Zeitwerk managed files.
72
+
73
+ * Automatically invalidate the load path cache whenever the Ruby version change. (#387)
74
+ This is to avoid issues in case the same installation path is re-used for subsequent ruby patch releases.
75
+
76
+ # 1.9.3
77
+
78
+ * Only disable the compile cache for source files impacted by [Ruby 3.0.3 [Bug 18250]](https://bugs.ruby-lang.org/issues/18250).
79
+ This should keep the performance loss to a minimum.
80
+
81
+ # 1.9.2
82
+
83
+ * Disable compile cache if [Ruby 3.0.3's ISeq cache bug](https://bugs.ruby-lang.org/issues/18250) is detected.
84
+ AKA `iseq.rb:13 to_binary: wrong argument type false (expected Symbol)`
85
+ * Fix `Kernel.load` behavior: before `load 'a'` would load `a.rb` (and other tried extensions) and wouldn't load `a` unless `development_mode: true`, now only `a` would be loaded and files with extensions wouldn't be.
86
+
87
+ # 1.9.1
88
+
89
+ * Removed a forgotten debug statement in JSON precompilation.
90
+
91
+ # 1.9.0
92
+
93
+ * Added a compilation cache for `JSON.load_file`. (#370)
94
+
95
+ # 1.8.1
96
+
97
+ * Fixed support for older Psych. (#369)
98
+
99
+ # 1.8.0
100
+
101
+ * Improve support for Psych 4. (#368)
102
+
103
+ # 1.7.7
104
+
105
+ * Fix `require_relative` in evaled code on latest ruby 3.1.0-dev. (#366)
106
+
107
+ # 1.7.6
108
+
109
+ * Fix reliance on `set` to be required.
110
+ * Fix `Encoding::UndefinedConversionError` error for Rails applications when precompiling cache. (#364)
111
+
3
112
  # 1.7.5
4
113
 
5
114
  * Handle a regression of Ruby 2.7.3 causing Bootsnap to call the deprecated `untaint` method. (#360)
@@ -7,8 +116,8 @@
7
116
 
8
117
  # 1.7.4
9
118
 
10
- * Stop raising errors when encoutering various file system errors. The cache is now best effort,
11
- if somehow it can't be saved, bootsnapp will gracefully fallback to the original operation (e.g. `Kernel.require`).
119
+ * Stop raising errors when encountering various file system errors. The cache is now best effort,
120
+ if somehow it can't be saved, bootsnap will gracefully fallback to the original operation (e.g. `Kernel.require`).
12
121
  (#353, #177, #262)
13
122
 
14
123
  # 1.7.3
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2017 Shopify, Inc.
3
+ Copyright (c) 2017-present Shopify, Inc.
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -41,7 +41,7 @@ getting progressively slower, this is almost certainly the cause.**
41
41
  It's technically possible to simply specify `gem 'bootsnap', require: 'bootsnap/setup'`, but it's
42
42
  important to load Bootsnap as early as possible to get maximum performance improvement.
43
43
 
44
- You can see how this require works [here](https://github.com/Shopify/bootsnap/blob/master/lib/bootsnap/setup.rb).
44
+ You can see how this require works [here](https://github.com/Shopify/bootsnap/blob/main/lib/bootsnap/setup.rb).
45
45
 
46
46
  If you are not using Rails, or if you are but want more control over things, add this to your
47
47
  application setup immediately after `require 'bundler/setup'` (i.e. as early as possible: the sooner
@@ -161,7 +161,7 @@ The only directories considered "stable" are things under the Ruby install prefi
161
161
  "volatile".
162
162
 
163
163
  In addition to the [`Bootsnap::LoadPathCache::Cache`
164
- source](https://github.com/Shopify/bootsnap/blob/master/lib/bootsnap/load_path_cache/cache.rb),
164
+ source](https://github.com/Shopify/bootsnap/blob/main/lib/bootsnap/load_path_cache/cache.rb),
165
165
  this diagram may help clarify how entry resolution works:
166
166
 
167
167
  ![How path searching works](https://cloud.githubusercontent.com/assets/3074765/25388270/670b5652-299b-11e7-87fb-975647f68981.png)
data/exe/bootsnap CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'bootsnap/cli'
4
+ require "bootsnap/cli"
5
5
  exit Bootsnap::CLI.new(ARGV).run
@@ -75,7 +75,7 @@ struct bs_cache_key {
75
75
  STATIC_ASSERT(sizeof(struct bs_cache_key) == KEY_SIZE);
76
76
 
77
77
  /* Effectively a schema version. Bumping invalidates all previous caches */
78
- static const uint32_t current_version = 3;
78
+ static const uint32_t current_version = 4;
79
79
 
80
80
  /* hash of e.g. "x86_64-darwin17", invalidating when ruby is recompiled on a
81
81
  * new OS ABI, etc. */
@@ -91,8 +91,7 @@ static mode_t current_umask;
91
91
  static VALUE rb_mBootsnap;
92
92
  static VALUE rb_mBootsnap_CompileCache;
93
93
  static VALUE rb_mBootsnap_CompileCache_Native;
94
- static VALUE rb_eBootsnap_CompileCache_Uncompilable;
95
- static ID uncompilable;
94
+ static VALUE rb_cBootsnap_CompileCache_UNCOMPILABLE;
96
95
  static ID instrumentation_method;
97
96
  static VALUE sym_miss;
98
97
  static VALUE sym_stale;
@@ -120,10 +119,8 @@ static uint32_t get_ruby_platform(void);
120
119
  * exception.
121
120
  */
122
121
  static int bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * output_data);
123
- static VALUE prot_storage_to_output(VALUE arg);
124
122
  static VALUE prot_input_to_output(VALUE arg);
125
123
  static void bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag);
126
- static VALUE prot_input_to_storage(VALUE arg);
127
124
  static int bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data);
128
125
  struct s2o_data;
129
126
  struct i2o_data;
@@ -138,6 +135,12 @@ bs_rb_coverage_running(VALUE self)
138
135
  return RTEST(cov) ? Qtrue : Qfalse;
139
136
  }
140
137
 
138
+ static VALUE
139
+ bs_rb_get_path(VALUE self, VALUE fname)
140
+ {
141
+ return rb_get_path(fname);
142
+ }
143
+
141
144
  /*
142
145
  * Ruby C extensions are initialized by calling Init_<extname>.
143
146
  *
@@ -149,14 +152,17 @@ void
149
152
  Init_bootsnap(void)
150
153
  {
151
154
  rb_mBootsnap = rb_define_module("Bootsnap");
155
+
156
+ rb_define_singleton_method(rb_mBootsnap, "rb_get_path", bs_rb_get_path, 1);
157
+
152
158
  rb_mBootsnap_CompileCache = rb_define_module_under(rb_mBootsnap, "CompileCache");
153
159
  rb_mBootsnap_CompileCache_Native = rb_define_module_under(rb_mBootsnap_CompileCache, "Native");
154
- rb_eBootsnap_CompileCache_Uncompilable = rb_define_class_under(rb_mBootsnap_CompileCache, "Uncompilable", rb_eStandardError);
160
+ rb_cBootsnap_CompileCache_UNCOMPILABLE = rb_const_get(rb_mBootsnap_CompileCache, rb_intern("UNCOMPILABLE"));
161
+ rb_global_variable(&rb_cBootsnap_CompileCache_UNCOMPILABLE);
155
162
 
156
163
  current_ruby_revision = get_ruby_revision();
157
164
  current_ruby_platform = get_ruby_platform();
158
165
 
159
- uncompilable = rb_intern("__bootsnap_uncompilable__");
160
166
  instrumentation_method = rb_intern("_instrument");
161
167
 
162
168
  sym_miss = ID2SYM(rb_intern("miss"));
@@ -426,6 +432,7 @@ open_current_file(char * path, struct bs_cache_key * key, const char ** errno_pr
426
432
  #define ERROR_WITH_ERRNO -1
427
433
  #define CACHE_MISS -2
428
434
  #define CACHE_STALE -3
435
+ #define CACHE_UNCOMPILABLE -4
429
436
 
430
437
  /*
431
438
  * Read the cache key from the given fd, which must have position 0 (e.g.
@@ -507,14 +514,14 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE *
507
514
  if (data_size > 100000000000) {
508
515
  *errno_provenance = "bs_fetch:fetch_cached_data:datasize";
509
516
  errno = EINVAL; /* because wtf? */
510
- ret = -1;
517
+ ret = ERROR_WITH_ERRNO;
511
518
  goto done;
512
519
  }
513
520
  data = ALLOC_N(char, data_size);
514
521
  nread = read(fd, data, data_size);
515
522
  if (nread < 0) {
516
523
  *errno_provenance = "bs_fetch:fetch_cached_data:read";
517
- ret = -1;
524
+ ret = ERROR_WITH_ERRNO;
518
525
  goto done;
519
526
  }
520
527
  if (nread != data_size) {
@@ -525,6 +532,10 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE *
525
532
  storage_data = rb_str_new(data, data_size);
526
533
 
527
534
  *exception_tag = bs_storage_to_output(handler, args, storage_data, output_data);
535
+ if (*output_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
536
+ ret = CACHE_UNCOMPILABLE;
537
+ goto done;
538
+ }
528
539
  ret = 0;
529
540
  done:
530
541
  if (data != NULL) xfree(data);
@@ -737,7 +748,15 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
737
748
  &output_data, &exception_tag, &errno_provenance
738
749
  );
739
750
  if (exception_tag != 0) goto raise;
740
- else if (res == CACHE_MISS || res == CACHE_STALE) valid_cache = 0;
751
+ else if (res == CACHE_UNCOMPILABLE) {
752
+ /* If fetch_cached_data returned `Uncompilable` we fallback to `input_to_output`
753
+ This happens if we have say, an unsafe YAML cache, but try to load it in safe mode */
754
+ if (bs_read_contents(current_fd, current_key.size, &contents, &errno_provenance) < 0) goto fail_errno;
755
+ input_data = rb_str_new(contents, current_key.size);
756
+ bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
757
+ if (exception_tag != 0) goto raise;
758
+ goto succeed;
759
+ } else if (res == CACHE_MISS || res == CACHE_STALE) valid_cache = 0;
741
760
  else if (res == ERROR_WITH_ERRNO) goto fail_errno;
742
761
  else if (!NIL_P(output_data)) goto succeed; /* fast-path, goal */
743
762
  }
@@ -754,7 +773,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
754
773
  if (exception_tag != 0) goto raise;
755
774
  /* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
756
775
  * to cache anything; just return input_to_output(input_data) */
757
- if (storage_data == uncompilable) {
776
+ if (storage_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
758
777
  bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
759
778
  if (exception_tag != 0) goto raise;
760
779
  goto succeed;
@@ -772,12 +791,20 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args
772
791
  exception_tag = bs_storage_to_output(handler, args, storage_data, &output_data);
773
792
  if (exception_tag != 0) goto raise;
774
793
 
775
- /* If output_data is nil, delete the cache entry and generate the output
776
- * using input_to_output */
777
- if (NIL_P(output_data)) {
794
+ if (output_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
795
+ /* If storage_to_output returned `Uncompilable` we fallback to `input_to_output` */
796
+ bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
797
+ if (exception_tag != 0) goto raise;
798
+ } else if (NIL_P(output_data)) {
799
+ /* If output_data is nil, delete the cache entry and generate the output
800
+ * using input_to_output */
778
801
  if (unlink(cache_path) < 0) {
779
- errno_provenance = "bs_fetch:unlink";
780
- goto fail_errno;
802
+ /* If the cache was already deleted, it might be that another process did it before us.
803
+ * No point raising an error */
804
+ if (errno != ENOENT) {
805
+ errno_provenance = "bs_fetch:unlink";
806
+ goto fail_errno;
807
+ }
781
808
  }
782
809
  bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
783
810
  if (exception_tag != 0) goto raise;
@@ -856,7 +883,7 @@ bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
856
883
 
857
884
  /* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
858
885
  * to cache anything; just return false */
859
- if (storage_data == uncompilable) {
886
+ if (storage_data == rb_cBootsnap_CompileCache_UNCOMPILABLE) {
860
887
  goto fail;
861
888
  }
862
889
  /* If storage_data isn't a string, we can't cache it */
@@ -919,7 +946,7 @@ struct i2s_data {
919
946
  };
920
947
 
921
948
  static VALUE
922
- prot_storage_to_output(VALUE arg)
949
+ try_storage_to_output(VALUE arg)
923
950
  {
924
951
  struct s2o_data * data = (struct s2o_data *)arg;
925
952
  return rb_funcall(data->handler, rb_intern("storage_to_output"), 2, data->storage_data, data->args);
@@ -934,7 +961,7 @@ bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * outp
934
961
  .args = args,
935
962
  .storage_data = storage_data,
936
963
  };
937
- *output_data = rb_protect(prot_storage_to_output, (VALUE)&s2o_data, &state);
964
+ *output_data = rb_protect(try_storage_to_output, (VALUE)&s2o_data, &state);
938
965
  return state;
939
966
  }
940
967
 
@@ -963,22 +990,6 @@ try_input_to_storage(VALUE arg)
963
990
  return rb_funcall(data->handler, rb_intern("input_to_storage"), 2, data->input_data, data->pathval);
964
991
  }
965
992
 
966
- static VALUE
967
- rescue_input_to_storage(VALUE arg, VALUE e)
968
- {
969
- return uncompilable;
970
- }
971
-
972
- static VALUE
973
- prot_input_to_storage(VALUE arg)
974
- {
975
- struct i2s_data * data = (struct i2s_data *)arg;
976
- return rb_rescue2(
977
- try_input_to_storage, (VALUE)data,
978
- rescue_input_to_storage, Qnil,
979
- rb_eBootsnap_CompileCache_Uncompilable, 0);
980
- }
981
-
982
993
  static int
983
994
  bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data)
984
995
  {
@@ -988,6 +999,6 @@ bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval,
988
999
  .input_data = input_data,
989
1000
  .pathval = pathval,
990
1001
  };
991
- *storage_data = rb_protect(prot_input_to_storage, (VALUE)&i2s_data, &state);
1002
+ *storage_data = rb_protect(try_input_to_storage, (VALUE)&i2s_data, &state);
992
1003
  return state;
993
1004
  }
@@ -1,21 +1,23 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require("mkmf")
3
4
 
4
- if RUBY_ENGINE == 'ruby'
5
- $CFLAGS << ' -O3 '
6
- $CFLAGS << ' -std=c99'
5
+ if RUBY_ENGINE == "ruby"
6
+ $CFLAGS << " -O3 "
7
+ $CFLAGS << " -std=c99"
7
8
 
8
9
  # ruby.h has some -Wpedantic fails in some cases
9
10
  # (e.g. https://github.com/Shopify/bootsnap/issues/15)
10
- unless ['0', '', nil].include?(ENV['BOOTSNAP_PEDANTIC'])
11
- $CFLAGS << ' -Wall'
12
- $CFLAGS << ' -Werror'
13
- $CFLAGS << ' -Wextra'
14
- $CFLAGS << ' -Wpedantic'
11
+ unless ["0", "", nil].include?(ENV["BOOTSNAP_PEDANTIC"])
12
+ $CFLAGS << " -Wall"
13
+ $CFLAGS << " -Werror"
14
+ $CFLAGS << " -Wextra"
15
+ $CFLAGS << " -Wpedantic"
15
16
 
16
- $CFLAGS << ' -Wno-unused-parameter' # VALUE self has to be there but we don't care what it is.
17
- $CFLAGS << ' -Wno-keyword-macro' # hiding return
18
- $CFLAGS << ' -Wno-gcc-compat' # ruby.h 2.6.0 on macos 10.14, dunno
17
+ $CFLAGS << " -Wno-unused-parameter" # VALUE self has to be there but we don't care what it is.
18
+ $CFLAGS << " -Wno-keyword-macro" # hiding return
19
+ $CFLAGS << " -Wno-gcc-compat" # ruby.h 2.6.0 on macos 10.14, dunno
20
+ $CFLAGS << " -Wno-compound-token-split-by-macro"
19
21
  end
20
22
 
21
23
  create_makefile("bootsnap/bootsnap")
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Bootsnap
3
4
  extend(self)
4
5
 
@@ -37,7 +37,11 @@ module Bootsnap
37
37
 
38
38
  def initialize(jobs)
39
39
  @jobs = jobs
40
- @pipe_out, @to_io = IO.pipe
40
+ @pipe_out, @to_io = IO.pipe(binmode: true)
41
+ # Set the writer encoding to binary since IO.pipe only sets it for the reader.
42
+ # https://github.com/rails/rails/issues/16514#issuecomment-52313290
43
+ @to_io.set_encoding(Encoding::BINARY)
44
+
41
45
  @pid = nil
42
46
  end
43
47
 
@@ -59,6 +63,7 @@ module Bootsnap
59
63
  loop do
60
64
  job, *args = Marshal.load(@pipe_out)
61
65
  return if job == :exit
66
+
62
67
  @jobs.fetch(job).call(*args)
63
68
  end
64
69
  rescue IOError