bootsnap 0.3.0.pre → 0.3.0.pre2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|