bootsnap 0.3.0.pre → 0.3.0.pre2
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/ext/bootsnap/bootsnap.c +260 -72
- data/lib/bootsnap/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aa9caa4faf08302d5050a4d6f41e90920732beed
|
4
|
+
data.tar.gz: 60d4a539b9065e0771be2c330a614d4bd5fb8efe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 383af9b805278c8d682c368b52b1ec14dd8c1d391692422c269fc9f86d9199aeee3aecdf8e33e1f369ba028316633e97d58d20da176eae74d2d0879dec14a801
|
7
|
+
data.tar.gz: 307d8dda6d264b1179ee2a955f3fa45c9c01add22233934a333a9a50b122e3d5aa7cfb80199611d35e0e0518091c474c5f3b35d5f0c88912e5fe78e036df0977
|
data/CHANGELOG.md
CHANGED
data/ext/bootsnap/bootsnap.c
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
+
/*
|
2
|
+
* Suggested reading order:
|
3
|
+
* 1. Skim Init_bootsnap
|
4
|
+
* 2. Skim bs_fetch
|
5
|
+
* 3. The rest of everything
|
6
|
+
*
|
7
|
+
* Init_bootsnap sets up the ruby objects and binds bs_fetch to
|
8
|
+
* Bootsnap::CompileCache::Native.fetch.
|
9
|
+
*
|
10
|
+
* bs_fetch is the ultimate caller for for just about every other function in
|
11
|
+
* here.
|
12
|
+
*/
|
13
|
+
|
1
14
|
#include "bootsnap.h"
|
2
15
|
#include "ruby.h"
|
3
16
|
#include <stdint.h>
|
@@ -5,18 +18,29 @@
|
|
5
18
|
#include <errno.h>
|
6
19
|
#include <fcntl.h>
|
7
20
|
#include <sys/stat.h>
|
21
|
+
#include <sys/utsname.h>
|
8
22
|
|
9
|
-
|
10
|
-
|
11
|
-
#else
|
12
|
-
#include <sys/sysctl.h>
|
13
|
-
#endif
|
14
|
-
|
23
|
+
/* 1000 is an arbitrary limit; FNV64 plus some slashes brings the cap down to
|
24
|
+
* 981 for the cache dir */
|
15
25
|
#define MAX_CACHEPATH_SIZE 1000
|
16
26
|
#define MAX_CACHEDIR_SIZE 981
|
17
27
|
|
18
28
|
#define KEY_SIZE 64
|
19
29
|
|
30
|
+
/*
|
31
|
+
* An instance of this key is written as the first 64 bytes of each cache file.
|
32
|
+
* The mtime and size members track whether the file contents have changed, and
|
33
|
+
* the version, os_version, compile_option, and ruby_revision members track
|
34
|
+
* changes to the environment that could invalidate compile results without
|
35
|
+
* file contents having changed. The data_size member is not truly part of the
|
36
|
+
* "key". Really, this could be called a "header" with the first six members
|
37
|
+
* being an embedded "key" struct and an additional data_size member.
|
38
|
+
*
|
39
|
+
* The data_size indicates the remaining number of bytes in the cache file
|
40
|
+
* after the header (the size of the cached artifact).
|
41
|
+
*
|
42
|
+
* After data_size, the struct is padded to 64 bytes.
|
43
|
+
*/
|
20
44
|
struct bs_cache_key {
|
21
45
|
uint32_t version;
|
22
46
|
uint32_t os_version;
|
@@ -28,31 +52,40 @@ struct bs_cache_key {
|
|
28
52
|
uint8_t pad[24];
|
29
53
|
} __attribute__((packed));
|
30
54
|
|
55
|
+
/*
|
56
|
+
* If the struct padding isn't correct to pad the key to 64 bytes, refuse to
|
57
|
+
* compile.
|
58
|
+
*/
|
31
59
|
#define STATIC_ASSERT(X) STATIC_ASSERT2(X,__LINE__)
|
32
60
|
#define STATIC_ASSERT2(X,L) STATIC_ASSERT3(X,L)
|
33
61
|
#define STATIC_ASSERT3(X,L) STATIC_ASSERT_MSG(X,at_line_##L)
|
34
62
|
#define STATIC_ASSERT_MSG(COND,MSG) typedef char static_assertion_##MSG[(!!(COND))*2-1]
|
35
|
-
|
36
63
|
STATIC_ASSERT(sizeof(struct bs_cache_key) == KEY_SIZE);
|
37
64
|
|
65
|
+
/* Effectively a schema version. Bumping invalidates all previous caches */
|
38
66
|
static const uint32_t current_version = 2;
|
39
67
|
|
68
|
+
/* Derived from kernel or libc version; intended to roughly correspond to when
|
69
|
+
* ABIs have changed, requiring recompilation of native gems. */
|
40
70
|
static uint32_t current_os_version;
|
71
|
+
/* Invalidates cache when switching ruby versions */
|
41
72
|
static uint32_t current_ruby_revision;
|
73
|
+
/* Invalidates cache when RubyVM::InstructionSequence.compile_option changes */
|
42
74
|
static uint32_t current_compile_option_crc32 = 0;
|
43
75
|
|
76
|
+
/* Bootsnap::CompileCache::{Native, Uncompilable} */
|
44
77
|
static VALUE rb_mBootsnap;
|
45
78
|
static VALUE rb_mBootsnap_CompileCache;
|
46
79
|
static VALUE rb_mBootsnap_CompileCache_Native;
|
47
80
|
static VALUE rb_eBootsnap_CompileCache_Uncompilable;
|
48
81
|
static ID uncompilable;
|
49
82
|
|
50
|
-
/*
|
83
|
+
/* Functions exposed as module functions on Bootsnap::CompileCache::Native */
|
51
84
|
static VALUE bs_compile_option_crc32_set(VALUE self, VALUE crc32_v);
|
52
85
|
static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler);
|
53
86
|
|
54
87
|
/* Helpers */
|
55
|
-
static uint64_t
|
88
|
+
static uint64_t fnv1a_64(const char *str);
|
56
89
|
static void bs_cache_path(const char * cachedir, const char * path, char ** cache_path);
|
57
90
|
static int bs_read_key(int fd, struct bs_cache_key * key);
|
58
91
|
static int cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2);
|
@@ -62,29 +95,27 @@ static int fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * o
|
|
62
95
|
static VALUE prot_exception_for_errno(VALUE err);
|
63
96
|
static uint32_t get_os_version(void);
|
64
97
|
|
98
|
+
/*
|
99
|
+
* Helper functions to call ruby methods on handler object without crashing on
|
100
|
+
* exception.
|
101
|
+
*/
|
65
102
|
static int bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data);
|
66
103
|
static VALUE prot_storage_to_output(VALUE arg);
|
67
104
|
static VALUE prot_input_to_output(VALUE arg);
|
68
105
|
static void bs_input_to_output(VALUE handler, VALUE input_data, VALUE * output_data, int * exception_tag);
|
69
106
|
static VALUE prot_input_to_storage(VALUE arg);
|
70
107
|
static int bs_input_to_storage(VALUE handler, VALUE input_data, VALUE pathval, VALUE * storage_data);
|
108
|
+
struct s2o_data;
|
109
|
+
struct i2o_data;
|
110
|
+
struct i2s_data;
|
71
111
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
VALUE input_data;
|
80
|
-
};
|
81
|
-
|
82
|
-
struct i2s_data {
|
83
|
-
VALUE handler;
|
84
|
-
VALUE input_data;
|
85
|
-
VALUE pathval;
|
86
|
-
};
|
87
|
-
|
112
|
+
/*
|
113
|
+
* Ruby C extensions are initialized by calling Init_<extname>.
|
114
|
+
*
|
115
|
+
* This sets up the module hierarchy and attaches functions as methods.
|
116
|
+
*
|
117
|
+
* We also populate some semi-static information about the current OS and so on.
|
118
|
+
*/
|
88
119
|
void
|
89
120
|
Init_bootsnap(void)
|
90
121
|
{
|
@@ -102,6 +133,10 @@ Init_bootsnap(void)
|
|
102
133
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "compile_option_crc32=", bs_compile_option_crc32_set, 1);
|
103
134
|
}
|
104
135
|
|
136
|
+
/*
|
137
|
+
* Bootsnap's ruby code registers a hook that notifies us via this function
|
138
|
+
* when compile_option changes. These changes invalidate all existing caches.
|
139
|
+
*/
|
105
140
|
static VALUE
|
106
141
|
bs_compile_option_crc32_set(VALUE self, VALUE crc32_v)
|
107
142
|
{
|
@@ -110,11 +145,21 @@ bs_compile_option_crc32_set(VALUE self, VALUE crc32_v)
|
|
110
145
|
return Qnil;
|
111
146
|
}
|
112
147
|
|
148
|
+
/*
|
149
|
+
* We use FNV1a-64 to derive cache paths. The choice is somewhat arbitrary but
|
150
|
+
* it has several nice properties:
|
151
|
+
*
|
152
|
+
* - Tiny implementation
|
153
|
+
* - No external dependency
|
154
|
+
* - Solid performance
|
155
|
+
* - Solid randomness
|
156
|
+
* - 32 bits doesn't feel collision-resistant enough; 64 is nice.
|
157
|
+
*/
|
113
158
|
static uint64_t
|
114
|
-
|
159
|
+
fnv1a_64(const char *str)
|
115
160
|
{
|
116
161
|
unsigned char *s = (unsigned char *)str;
|
117
|
-
uint64_t h = (
|
162
|
+
uint64_t h = (uint64_t)0xcbf29ce484222325ULL;
|
118
163
|
|
119
164
|
while (*s) {
|
120
165
|
h ^= (uint64_t)*s++;
|
@@ -124,36 +169,44 @@ fnv_64a(const char *str)
|
|
124
169
|
return h;
|
125
170
|
}
|
126
171
|
|
127
|
-
/*
|
172
|
+
/*
|
173
|
+
* The idea here is that we want a cache key member that changes when the OS
|
174
|
+
* changes in such a way as to make existing compiled ISeqs unloadable.
|
175
|
+
*
|
176
|
+
* On Darwin and FreeBSD, this is the kernel version, since that tends to vary
|
128
177
|
* with whole-system upgrades. What we probably care about more is the libc
|
129
178
|
* version, which is what we explicitly ask for on linux.
|
130
179
|
* (and KERN_OSRELEASE came back empty for me one one linux box, so...?)
|
180
|
+
*
|
181
|
+
* I'm kind of guessing about the important factors here. We could probably do
|
182
|
+
* this better.
|
131
183
|
*/
|
132
184
|
static uint32_t
|
133
185
|
get_os_version(void)
|
134
186
|
{
|
135
187
|
size_t len;
|
136
188
|
uint64_t hash;
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
if (sysctl(mib, 2, NULL, &len, NULL, 0) < 0) return 0;
|
145
|
-
version = malloc(sizeof(char) * len);
|
146
|
-
if (sysctl(mib, 2, version, &len, NULL, 0) < 0) return 0;
|
147
|
-
hash = fnv_64a(version);
|
148
|
-
free(version);
|
149
|
-
#endif
|
189
|
+
struct utsname utsname;
|
190
|
+
|
191
|
+
/* Not worth crashing if this fails; lose cache invalidation potential */
|
192
|
+
if (uname(&utsname) < 0) return 0;
|
193
|
+
|
194
|
+
hash = fnv1a_64(utsname.version);
|
195
|
+
|
150
196
|
return (uint32_t)(hash >> 32);
|
151
197
|
}
|
152
198
|
|
199
|
+
/*
|
200
|
+
* Given a cache root directory and the full path to a file being cached,
|
201
|
+
* generate a path under the cache directory at which the cached artifact will
|
202
|
+
* be stored.
|
203
|
+
*
|
204
|
+
* The path will look something like: <cachedir>/12/34567890abcdef
|
205
|
+
*/
|
153
206
|
static void
|
154
207
|
bs_cache_path(const char * cachedir, const char * path, char ** cache_path)
|
155
208
|
{
|
156
|
-
uint64_t hash =
|
209
|
+
uint64_t hash = fnv1a_64(path);
|
157
210
|
|
158
211
|
uint8_t first_byte = (hash >> (64 - 8));
|
159
212
|
uint64_t remainder = hash & 0x00ffffffffffffff;
|
@@ -161,6 +214,14 @@ bs_cache_path(const char * cachedir, const char * path, char ** cache_path)
|
|
161
214
|
sprintf(*cache_path, "%s/%02x/%014llx", cachedir, first_byte, remainder);
|
162
215
|
}
|
163
216
|
|
217
|
+
/*
|
218
|
+
* Test whether a newly-generated cache key based on the file as it exists on
|
219
|
+
* disk matches the one that was generated when the file was cached (or really
|
220
|
+
* compare any two keys).
|
221
|
+
*
|
222
|
+
* The data_size member is not compared, as it serves more of a "header"
|
223
|
+
* function.
|
224
|
+
*/
|
164
225
|
static int
|
165
226
|
cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2)
|
166
227
|
{
|
@@ -174,6 +235,11 @@ cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2)
|
|
174
235
|
);
|
175
236
|
}
|
176
237
|
|
238
|
+
/*
|
239
|
+
* Entrypoint for Bootsnap::CompileCache::Native.fetch. The real work is done
|
240
|
+
* in bs_fetch; this function just performs some basic typechecks and
|
241
|
+
* conversions on the ruby VALUE arguments before passing them along.
|
242
|
+
*/
|
177
243
|
static VALUE
|
178
244
|
bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
|
179
245
|
{
|
@@ -196,6 +262,10 @@ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
|
|
196
262
|
return bs_fetch(path, path_v, cache_path, handler);
|
197
263
|
}
|
198
264
|
|
265
|
+
/*
|
266
|
+
* Open the file we want to load/cache and generate a cache key for it if it
|
267
|
+
* was loaded.
|
268
|
+
*/
|
199
269
|
static int
|
200
270
|
open_current_file(char * path, struct bs_cache_key * key)
|
201
271
|
{
|
@@ -224,8 +294,13 @@ open_current_file(char * path, struct bs_cache_key * key)
|
|
224
294
|
#define CACHE_MISSING_OR_INVALID -2
|
225
295
|
|
226
296
|
/*
|
227
|
-
*
|
228
|
-
*
|
297
|
+
* Read the cache key from the given fd, which must have position 0 (e.g.
|
298
|
+
* freshly opened file).
|
299
|
+
*
|
300
|
+
* Possible return values:
|
301
|
+
* - 0 (OK, key was loaded)
|
302
|
+
* - CACHE_MISSING_OR_INVALID (-2)
|
303
|
+
* - ERROR_WITH_ERRNO (-1, errno is set)
|
229
304
|
*/
|
230
305
|
static int
|
231
306
|
bs_read_key(int fd, struct bs_cache_key * key)
|
@@ -237,8 +312,13 @@ bs_read_key(int fd, struct bs_cache_key * key)
|
|
237
312
|
}
|
238
313
|
|
239
314
|
/*
|
240
|
-
*
|
241
|
-
*
|
315
|
+
* Open the cache file at a given path, if it exists, and read its key into the
|
316
|
+
* struct.
|
317
|
+
*
|
318
|
+
* Possible return values:
|
319
|
+
* - 0 (OK, key was loaded)
|
320
|
+
* - CACHE_MISSING_OR_INVALID (-2)
|
321
|
+
* - ERROR_WITH_ERRNO (-1, errno is set)
|
242
322
|
*/
|
243
323
|
static int
|
244
324
|
open_cache_file(const char * path, struct bs_cache_key * key)
|
@@ -260,10 +340,25 @@ open_cache_file(const char * path, struct bs_cache_key * key)
|
|
260
340
|
return fd;
|
261
341
|
}
|
262
342
|
|
343
|
+
/*
|
344
|
+
* The cache file is laid out like:
|
345
|
+
* 0...64 : bs_cache_key
|
346
|
+
* 64..-1 : cached artifact
|
347
|
+
*
|
348
|
+
* This function takes a file descriptor whose position is pre-set to 64, and
|
349
|
+
* the data_size (corresponding to the remaining number of bytes) listed in the
|
350
|
+
* cache header.
|
351
|
+
*
|
352
|
+
* We load the text from this file into a buffer, and pass it to the ruby-land
|
353
|
+
* handler with exception handling via the exception_tag param.
|
354
|
+
*
|
355
|
+
* Data is returned via the output_data parameter, which, if there's no error
|
356
|
+
* or exception, will be the final data returnable to the user.
|
357
|
+
*/
|
263
358
|
static int
|
264
359
|
fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag)
|
265
360
|
{
|
266
|
-
char * data;
|
361
|
+
char * data = NULL;
|
267
362
|
ssize_t nread;
|
268
363
|
int ret;
|
269
364
|
|
@@ -271,7 +366,8 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data,
|
|
271
366
|
|
272
367
|
if (data_size > 100000000000) {
|
273
368
|
errno = EINVAL; /* because wtf? */
|
274
|
-
|
369
|
+
ret = -1;
|
370
|
+
goto done;
|
275
371
|
}
|
276
372
|
data = ALLOC_N(char, data_size);
|
277
373
|
nread = read(fd, data, data_size);
|
@@ -289,17 +385,14 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data,
|
|
289
385
|
*exception_tag = bs_storage_to_output(handler, storage_data, output_data);
|
290
386
|
ret = 0;
|
291
387
|
done:
|
292
|
-
xfree(data);
|
388
|
+
if (data != NULL) xfree(data);
|
293
389
|
return ret;
|
294
390
|
}
|
295
391
|
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
return read(fd, *contents, size);
|
301
|
-
}
|
302
|
-
|
392
|
+
/*
|
393
|
+
* Like mkdir -p, this recursively creates directory parents of a file. e.g.
|
394
|
+
* given /a/b/c, creates /a and /a/b.
|
395
|
+
*/
|
303
396
|
static int
|
304
397
|
mkpath(char * file_path, mode_t mode)
|
305
398
|
{
|
@@ -320,6 +413,11 @@ mkpath(char * file_path, mode_t mode)
|
|
320
413
|
return 0;
|
321
414
|
}
|
322
415
|
|
416
|
+
/*
|
417
|
+
* Write a cache header/key and a compiled artifact to a given cache path by
|
418
|
+
* writing to a tmpfile and then renaming the tmpfile over top of the final
|
419
|
+
* path.
|
420
|
+
*/
|
323
421
|
static int
|
324
422
|
atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data)
|
325
423
|
{
|
@@ -359,6 +457,76 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data)
|
|
359
457
|
return rename(tmp_path, path);
|
360
458
|
}
|
361
459
|
|
460
|
+
/*
|
461
|
+
* Given an errno value (converted to a ruby Fixnum), return the corresponding
|
462
|
+
* Errno::* constant. If none is found, return StandardError instead.
|
463
|
+
*/
|
464
|
+
static VALUE
|
465
|
+
prot_exception_for_errno(VALUE err)
|
466
|
+
{
|
467
|
+
if (err != INT2FIX(0)) {
|
468
|
+
VALUE mErrno = rb_const_get(rb_cObject, rb_intern("Errno"));
|
469
|
+
VALUE constants = rb_funcall(mErrno, rb_intern("constants"), 0);
|
470
|
+
VALUE which = rb_funcall(constants, rb_intern("[]"), 1, err);
|
471
|
+
return rb_funcall(mErrno, rb_intern("const_get"), 1, which);
|
472
|
+
}
|
473
|
+
return rb_eStandardError;
|
474
|
+
}
|
475
|
+
|
476
|
+
|
477
|
+
/* Read contents from an fd, whose contents are asserted to be +size+ bytes
|
478
|
+
* long, into a buffer */
|
479
|
+
static ssize_t
|
480
|
+
bs_read_contents(int fd, size_t size, char ** contents)
|
481
|
+
{
|
482
|
+
*contents = ALLOC_N(char, size);
|
483
|
+
return read(fd, *contents, size);
|
484
|
+
}
|
485
|
+
|
486
|
+
/*
|
487
|
+
* This is the meat of the extension. bs_fetch is
|
488
|
+
* Bootsnap::CompileCache::Native.fetch.
|
489
|
+
*
|
490
|
+
* There are three "formats" in use here:
|
491
|
+
* 1. "input" fomat, which is what we load from the source file;
|
492
|
+
* 2. "storage" format, which we write to the cache;
|
493
|
+
* 3. "output" format, which is what we return.
|
494
|
+
*
|
495
|
+
* E.g., For ISeq compilation:
|
496
|
+
* input: ruby source, as text
|
497
|
+
* storage: binary string (RubyVM::InstructionSequence#to_binary)
|
498
|
+
* output: Instance of RubyVM::InstructionSequence
|
499
|
+
*
|
500
|
+
* And for YAML:
|
501
|
+
* input: yaml as text
|
502
|
+
* storage: MessagePack or Marshal text
|
503
|
+
* output: ruby object, loaded from yaml/messagepack/marshal
|
504
|
+
*
|
505
|
+
* The handler passed in must support three messages:
|
506
|
+
* * storage_to_output(s) -> o
|
507
|
+
* * input_to_output(i) -> o
|
508
|
+
* * input_to_storage(i) -> s
|
509
|
+
* (input_to_storage may raise Bootsnap::CompileCache::Uncompilable, which
|
510
|
+
* will prevent caching and cause output to be generated with
|
511
|
+
* input_to_output)
|
512
|
+
*
|
513
|
+
* The semantics of this function are basically:
|
514
|
+
*
|
515
|
+
* return storage_to_output(cache[path]) if cache[path]
|
516
|
+
* storage = input_to_storage(input)
|
517
|
+
* cache[path] = storage
|
518
|
+
* return storage_to_output(storage)
|
519
|
+
*
|
520
|
+
* Or expanded a bit:
|
521
|
+
*
|
522
|
+
* - Check if the cache file exists and is up to date.
|
523
|
+
* - If it is, load this data to storage_data.
|
524
|
+
* - return storage_to_output(storage_data)
|
525
|
+
* - Read the file to input_data
|
526
|
+
* - Generate storage_data using input_to_storage(input_data)
|
527
|
+
* - Write storage_data data, with a cache key, to the cache file.
|
528
|
+
* - Return storage_to_output(storage_data)
|
529
|
+
*/
|
362
530
|
static VALUE
|
363
531
|
bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
364
532
|
{
|
@@ -371,14 +539,18 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
371
539
|
VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
|
372
540
|
VALUE output_data; /* return data, e.g. ruby hash or loaded iseq */
|
373
541
|
|
374
|
-
VALUE exception;
|
542
|
+
VALUE exception; /* ruby exception object to raise instead of returning */
|
375
543
|
|
544
|
+
/* Open the source file and generate a cache key for it */
|
376
545
|
current_fd = open_current_file(path, ¤t_key);
|
377
546
|
if (current_fd < 0) goto fail_errno;
|
378
547
|
|
548
|
+
/* Open the cache key if it exists, and read its cache key in */
|
379
549
|
cache_fd = open_cache_file(cache_path, &cached_key);
|
380
550
|
if (cache_fd < 0 && cache_fd != CACHE_MISSING_OR_INVALID) goto fail_errno;
|
381
551
|
|
552
|
+
/* True if the cache existed and no invalidating changes have occurred since
|
553
|
+
* it was generated. */
|
382
554
|
valid_cache = cache_key_equal(¤t_key, &cached_key);
|
383
555
|
|
384
556
|
if (valid_cache) {
|
@@ -393,30 +565,40 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
393
565
|
cache_fd = -1;
|
394
566
|
/* Cache is stale, invalid, or missing. Regenerate and write it out. */
|
395
567
|
|
568
|
+
/* Read the contents of the source file into a buffer */
|
396
569
|
if (bs_read_contents(current_fd, current_key.size, &contents) < 0) goto fail_errno;
|
397
570
|
input_data = rb_str_new_static(contents, current_key.size);
|
398
571
|
|
572
|
+
/* Try to compile the input_data using input_to_storage(input_data) */
|
399
573
|
exception_tag = bs_input_to_storage(handler, input_data, path_v, &storage_data);
|
400
574
|
if (exception_tag != 0) goto raise;
|
575
|
+
/* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
|
576
|
+
* to cache anything; just return input_to_output(input_data) */
|
401
577
|
if (storage_data == uncompilable) {
|
402
578
|
bs_input_to_output(handler, input_data, &output_data, &exception_tag);
|
403
579
|
if (exception_tag != 0) goto raise;
|
404
580
|
goto succeed;
|
405
581
|
}
|
582
|
+
/* If storage_data isn't a string, we can't cache it */
|
406
583
|
if (!RB_TYPE_P(storage_data, T_STRING)) goto invalid_type_storage_data;
|
407
584
|
|
585
|
+
/* Write the cache key and storage_data to the cache directory */
|
408
586
|
res = atomic_write_cache_file(cache_path, ¤t_key, storage_data);
|
409
587
|
if (res < 0) goto fail_errno;
|
410
588
|
|
589
|
+
/* Having written the cache, now convert storage_data to output_data */
|
411
590
|
exception_tag = bs_storage_to_output(handler, storage_data, &output_data);
|
412
591
|
if (exception_tag != 0) goto raise;
|
413
592
|
|
593
|
+
/* If output_data is nil, delete the cache entry and generate the output
|
594
|
+
* using input_to_output */
|
414
595
|
if (NIL_P(output_data)) {
|
415
596
|
if (unlink(cache_path) < 0) goto fail_errno;
|
416
597
|
bs_input_to_output(handler, input_data, &output_data, &exception_tag);
|
417
598
|
if (exception_tag != 0) goto raise;
|
418
599
|
}
|
419
|
-
|
600
|
+
|
601
|
+
goto succeed; /* output_data is now the correct return. */
|
420
602
|
|
421
603
|
#define CLEANUP \
|
422
604
|
if (contents != NULL) xfree(contents); \
|
@@ -444,22 +626,28 @@ invalid_type_storage_data:
|
|
444
626
|
#undef CLEANUP
|
445
627
|
}
|
446
628
|
|
447
|
-
static VALUE
|
448
|
-
prot_exception_for_errno(VALUE err)
|
449
|
-
{
|
450
|
-
if (err != INT2FIX(0)) {
|
451
|
-
VALUE mErrno = rb_const_get(rb_cObject, rb_intern("Errno"));
|
452
|
-
VALUE constants = rb_funcall(mErrno, rb_intern("constants"), 0);
|
453
|
-
VALUE which = rb_funcall(constants, rb_intern("[]"), 1, err);
|
454
|
-
return rb_funcall(mErrno, rb_intern("const_get"), 1, which);
|
455
|
-
}
|
456
|
-
return rb_eStandardError;
|
457
|
-
}
|
458
|
-
|
459
|
-
|
460
629
|
/*****************************************************************************/
|
461
630
|
/********************* Handler Wrappers **************************************/
|
462
|
-
|
631
|
+
/*****************************************************************************
|
632
|
+
* Everything after this point in the file is just wrappers to deal with ruby's
|
633
|
+
* clunky method of handling exceptions from ruby methods invoked from C.
|
634
|
+
*/
|
635
|
+
|
636
|
+
struct s2o_data {
|
637
|
+
VALUE handler;
|
638
|
+
VALUE storage_data;
|
639
|
+
};
|
640
|
+
|
641
|
+
struct i2o_data {
|
642
|
+
VALUE handler;
|
643
|
+
VALUE input_data;
|
644
|
+
};
|
645
|
+
|
646
|
+
struct i2s_data {
|
647
|
+
VALUE handler;
|
648
|
+
VALUE input_data;
|
649
|
+
VALUE pathval;
|
650
|
+
};
|
463
651
|
|
464
652
|
static VALUE
|
465
653
|
prot_storage_to_output(VALUE arg)
|
data/lib/bootsnap/version.rb
CHANGED
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: 0.3.0.
|
4
|
+
version: 0.3.0.pre2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Burke Libbey
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-05-
|
11
|
+
date: 2017-05-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|