bootsnap 0.2.15 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +27 -0
- data/README.md +59 -43
- data/bootsnap.gemspec +8 -5
- data/ext/bootsnap/bootsnap.c +575 -394
- data/ext/bootsnap/bootsnap.h +1 -3
- data/lib/bootsnap/compile_cache/iseq.rb +10 -13
- data/lib/bootsnap/compile_cache/yaml.rb +4 -1
- data/lib/bootsnap/compile_cache.rb +5 -6
- data/lib/bootsnap/load_path_cache/cache.rb +25 -4
- data/lib/bootsnap/load_path_cache/path.rb +21 -11
- data/lib/bootsnap/load_path_cache/path_scanner.rb +0 -3
- data/lib/bootsnap/load_path_cache/store.rb +1 -1
- data/lib/bootsnap/setup.rb +47 -0
- data/lib/bootsnap/version.rb +1 -1
- data/lib/bootsnap.rb +1 -0
- metadata +5 -18
data/ext/bootsnap/bootsnap.c
CHANGED
|
@@ -1,125 +1,132 @@
|
|
|
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"
|
|
15
|
+
#include "ruby.h"
|
|
16
|
+
#include <stdint.h>
|
|
2
17
|
#include <sys/types.h>
|
|
3
|
-
#include <sys/xattr.h>
|
|
4
|
-
#include <sys/stat.h>
|
|
5
18
|
#include <errno.h>
|
|
6
|
-
#include <unistd.h>
|
|
7
19
|
#include <fcntl.h>
|
|
8
|
-
#include <
|
|
9
|
-
#
|
|
10
|
-
|
|
11
|
-
#ifdef __APPLE__
|
|
12
|
-
// Used for the OS Directives to define the os_version constant
|
|
13
|
-
#include <Availability.h>
|
|
14
|
-
#define _ENOATTR ENOATTR
|
|
15
|
-
#else
|
|
16
|
-
#define _ENOATTR ENODATA
|
|
20
|
+
#include <sys/stat.h>
|
|
21
|
+
#ifndef _WIN32
|
|
22
|
+
#include <sys/utsname.h>
|
|
17
23
|
#endif
|
|
18
24
|
|
|
19
|
-
/*
|
|
20
|
-
*
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
static VALUE rb_mBootsnap;
|
|
26
|
-
static VALUE rb_mBootsnap_CompileCache;
|
|
27
|
-
static VALUE rb_mBootsnap_CompileCache_Native;
|
|
28
|
-
static VALUE rb_eBootsnap_CompileCache_Uncompilable;
|
|
29
|
-
static uint32_t current_ruby_revision;
|
|
30
|
-
static uint32_t current_compile_option_crc32 = 0;
|
|
31
|
-
static ID uncompilable;
|
|
25
|
+
/* 1000 is an arbitrary limit; FNV64 plus some slashes brings the cap down to
|
|
26
|
+
* 981 for the cache dir */
|
|
27
|
+
#define MAX_CACHEPATH_SIZE 1000
|
|
28
|
+
#define MAX_CACHEDIR_SIZE 981
|
|
32
29
|
|
|
33
|
-
|
|
34
|
-
uint64_t hit;
|
|
35
|
-
uint64_t unwritable;
|
|
36
|
-
uint64_t uncompilable;
|
|
37
|
-
uint64_t miss;
|
|
38
|
-
uint64_t fail;
|
|
39
|
-
uint64_t retry;
|
|
40
|
-
};
|
|
41
|
-
static struct stats stats = {
|
|
42
|
-
.hit = 0,
|
|
43
|
-
.unwritable = 0,
|
|
44
|
-
.uncompilable = 0,
|
|
45
|
-
.miss = 0,
|
|
46
|
-
.fail = 0,
|
|
47
|
-
.retry = 0,
|
|
48
|
-
};
|
|
30
|
+
#define KEY_SIZE 64
|
|
49
31
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
32
|
+
/*
|
|
33
|
+
* An instance of this key is written as the first 64 bytes of each cache file.
|
|
34
|
+
* The mtime and size members track whether the file contents have changed, and
|
|
35
|
+
* the version, os_version, compile_option, and ruby_revision members track
|
|
36
|
+
* changes to the environment that could invalidate compile results without
|
|
37
|
+
* file contents having changed. The data_size member is not truly part of the
|
|
38
|
+
* "key". Really, this could be called a "header" with the first six members
|
|
39
|
+
* being an embedded "key" struct and an additional data_size member.
|
|
40
|
+
*
|
|
41
|
+
* The data_size indicates the remaining number of bytes in the cache file
|
|
42
|
+
* after the header (the size of the cached artifact).
|
|
43
|
+
*
|
|
44
|
+
* After data_size, the struct is padded to 64 bytes.
|
|
45
|
+
*/
|
|
46
|
+
struct bs_cache_key {
|
|
47
|
+
uint32_t version;
|
|
48
|
+
uint32_t os_version;
|
|
53
49
|
uint32_t compile_option;
|
|
54
|
-
uint32_t data_size;
|
|
55
50
|
uint32_t ruby_revision;
|
|
51
|
+
uint64_t size;
|
|
56
52
|
uint64_t mtime;
|
|
53
|
+
uint64_t data_size; /* not used for equality */
|
|
54
|
+
uint8_t pad[24];
|
|
57
55
|
} __attribute__((packed));
|
|
58
56
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
static
|
|
77
|
-
|
|
78
|
-
static
|
|
79
|
-
|
|
80
|
-
#ifdef __MAC_10_15 // Mac OS 10.15 (future)
|
|
81
|
-
static const int os_version = 15;
|
|
82
|
-
#elif __MAC_10_14 // Mac OS 10.14 (future)
|
|
83
|
-
static const int os_version = 14;
|
|
84
|
-
#elif __MAC_10_13 // Mac OS 10.13 (future)
|
|
85
|
-
static const int os_version = 13;
|
|
86
|
-
#elif __MAC_10_12 // Mac OS X Sierra
|
|
87
|
-
static const int os_version = 12;
|
|
88
|
-
#elif __MAC_10_11 // Mac OS X El Capitan
|
|
89
|
-
static const int os_version = 11;
|
|
90
|
-
# else
|
|
91
|
-
static const int os_version = 0;
|
|
92
|
-
#endif
|
|
57
|
+
/*
|
|
58
|
+
* If the struct padding isn't correct to pad the key to 64 bytes, refuse to
|
|
59
|
+
* compile.
|
|
60
|
+
*/
|
|
61
|
+
#define STATIC_ASSERT(X) STATIC_ASSERT2(X,__LINE__)
|
|
62
|
+
#define STATIC_ASSERT2(X,L) STATIC_ASSERT3(X,L)
|
|
63
|
+
#define STATIC_ASSERT3(X,L) STATIC_ASSERT_MSG(X,at_line_##L)
|
|
64
|
+
#define STATIC_ASSERT_MSG(COND,MSG) typedef char static_assertion_##MSG[(!!(COND))*2-1]
|
|
65
|
+
STATIC_ASSERT(sizeof(struct bs_cache_key) == KEY_SIZE);
|
|
66
|
+
|
|
67
|
+
/* Effectively a schema version. Bumping invalidates all previous caches */
|
|
68
|
+
static const uint32_t current_version = 2;
|
|
69
|
+
|
|
70
|
+
/* Derived from kernel or libc version; intended to roughly correspond to when
|
|
71
|
+
* ABIs have changed, requiring recompilation of native gems. */
|
|
72
|
+
static uint32_t current_os_version;
|
|
73
|
+
/* Invalidates cache when switching ruby versions */
|
|
74
|
+
static uint32_t current_ruby_revision;
|
|
75
|
+
/* Invalidates cache when RubyVM::InstructionSequence.compile_option changes */
|
|
76
|
+
static uint32_t current_compile_option_crc32 = 0;
|
|
93
77
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
#define SETXATTR_TRAILER
|
|
101
|
-
#define REMOVEXATTR_TRAILER
|
|
102
|
-
#endif
|
|
78
|
+
/* Bootsnap::CompileCache::{Native, Uncompilable} */
|
|
79
|
+
static VALUE rb_mBootsnap;
|
|
80
|
+
static VALUE rb_mBootsnap_CompileCache;
|
|
81
|
+
static VALUE rb_mBootsnap_CompileCache_Native;
|
|
82
|
+
static VALUE rb_eBootsnap_CompileCache_Uncompilable;
|
|
83
|
+
static ID uncompilable;
|
|
103
84
|
|
|
104
|
-
/*
|
|
105
|
-
static
|
|
106
|
-
static
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
static
|
|
110
|
-
static
|
|
111
|
-
static
|
|
112
|
-
static
|
|
85
|
+
/* Functions exposed as module functions on Bootsnap::CompileCache::Native */
|
|
86
|
+
static VALUE bs_compile_option_crc32_set(VALUE self, VALUE crc32_v);
|
|
87
|
+
static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler);
|
|
88
|
+
|
|
89
|
+
/* Helpers */
|
|
90
|
+
static uint64_t fnv1a_64(const char *str);
|
|
91
|
+
static void bs_cache_path(const char * cachedir, const char * path, char ** cache_path);
|
|
92
|
+
static int bs_read_key(int fd, struct bs_cache_key * key);
|
|
93
|
+
static int cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2);
|
|
94
|
+
static VALUE bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler);
|
|
95
|
+
static int open_current_file(char * path, struct bs_cache_key * key);
|
|
96
|
+
static int fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag);
|
|
113
97
|
static VALUE prot_exception_for_errno(VALUE err);
|
|
98
|
+
static uint32_t get_os_version(void);
|
|
99
|
+
|
|
100
|
+
/*
|
|
101
|
+
* Helper functions to call ruby methods on handler object without crashing on
|
|
102
|
+
* exception.
|
|
103
|
+
*/
|
|
104
|
+
static int bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data);
|
|
105
|
+
static VALUE prot_storage_to_output(VALUE arg);
|
|
114
106
|
static VALUE prot_input_to_output(VALUE arg);
|
|
115
107
|
static void bs_input_to_output(VALUE handler, VALUE input_data, VALUE * output_data, int * exception_tag);
|
|
116
108
|
static VALUE prot_input_to_storage(VALUE arg);
|
|
117
109
|
static int bs_input_to_storage(VALUE handler, VALUE input_data, VALUE pathval, VALUE * storage_data);
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
110
|
+
struct s2o_data;
|
|
111
|
+
struct i2o_data;
|
|
112
|
+
struct i2s_data;
|
|
113
|
+
|
|
114
|
+
/* https://bugs.ruby-lang.org/issues/13667 */
|
|
115
|
+
extern VALUE rb_get_coverages(void);
|
|
116
|
+
static VALUE
|
|
117
|
+
bs_rb_coverage_running(VALUE self)
|
|
118
|
+
{
|
|
119
|
+
VALUE cov = rb_get_coverages();
|
|
120
|
+
return RTEST(cov) ? Qtrue : Qfalse;
|
|
121
|
+
}
|
|
122
122
|
|
|
123
|
+
/*
|
|
124
|
+
* Ruby C extensions are initialized by calling Init_<extname>.
|
|
125
|
+
*
|
|
126
|
+
* This sets up the module hierarchy and attaches functions as methods.
|
|
127
|
+
*
|
|
128
|
+
* We also populate some semi-static information about the current OS and so on.
|
|
129
|
+
*/
|
|
123
130
|
void
|
|
124
131
|
Init_bootsnap(void)
|
|
125
132
|
{
|
|
@@ -127,358 +134,558 @@ Init_bootsnap(void)
|
|
|
127
134
|
rb_mBootsnap_CompileCache = rb_define_module_under(rb_mBootsnap, "CompileCache");
|
|
128
135
|
rb_mBootsnap_CompileCache_Native = rb_define_module_under(rb_mBootsnap_CompileCache, "Native");
|
|
129
136
|
rb_eBootsnap_CompileCache_Uncompilable = rb_define_class_under(rb_mBootsnap_CompileCache, "Uncompilable", rb_eStandardError);
|
|
137
|
+
|
|
130
138
|
current_ruby_revision = FIX2INT(rb_const_get(rb_cObject, rb_intern("RUBY_REVISION")));
|
|
139
|
+
current_os_version = get_os_version();
|
|
131
140
|
|
|
132
141
|
uncompilable = rb_intern("__bootsnap_uncompilable__");
|
|
133
142
|
|
|
134
|
-
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "
|
|
135
|
-
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "
|
|
143
|
+
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "coverage_running?", bs_rb_coverage_running, 0);
|
|
144
|
+
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch, 3);
|
|
136
145
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "compile_option_crc32=", bs_compile_option_crc32_set, 1);
|
|
137
146
|
}
|
|
138
147
|
|
|
148
|
+
/*
|
|
149
|
+
* Bootsnap's ruby code registers a hook that notifies us via this function
|
|
150
|
+
* when compile_option changes. These changes invalidate all existing caches.
|
|
151
|
+
*/
|
|
139
152
|
static VALUE
|
|
140
|
-
|
|
141
|
-
{
|
|
142
|
-
VALUE ret = rb_hash_new();
|
|
143
|
-
rb_hash_aset(ret, ID2SYM(rb_intern("hit")), INT2NUM(stats.hit));
|
|
144
|
-
rb_hash_aset(ret, ID2SYM(rb_intern("miss")), INT2NUM(stats.miss));
|
|
145
|
-
rb_hash_aset(ret, ID2SYM(rb_intern("unwritable")), INT2NUM(stats.unwritable));
|
|
146
|
-
rb_hash_aset(ret, ID2SYM(rb_intern("uncompilable")), INT2NUM(stats.uncompilable));
|
|
147
|
-
rb_hash_aset(ret, ID2SYM(rb_intern("fail")), INT2NUM(stats.fail));
|
|
148
|
-
rb_hash_aset(ret, ID2SYM(rb_intern("retry")), INT2NUM(stats.retry));
|
|
149
|
-
return ret;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
static VALUE
|
|
153
|
-
bs_compile_option_crc32_set(VALUE self, VALUE crc32val)
|
|
153
|
+
bs_compile_option_crc32_set(VALUE self, VALUE crc32_v)
|
|
154
154
|
{
|
|
155
|
-
Check_Type(
|
|
156
|
-
current_compile_option_crc32 = FIX2UINT(
|
|
155
|
+
Check_Type(crc32_v, T_FIXNUM);
|
|
156
|
+
current_compile_option_crc32 = FIX2UINT(crc32_v);
|
|
157
157
|
return Qnil;
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
-
|
|
161
|
-
|
|
160
|
+
/*
|
|
161
|
+
* We use FNV1a-64 to derive cache paths. The choice is somewhat arbitrary but
|
|
162
|
+
* it has several nice properties:
|
|
163
|
+
*
|
|
164
|
+
* - Tiny implementation
|
|
165
|
+
* - No external dependency
|
|
166
|
+
* - Solid performance
|
|
167
|
+
* - Solid randomness
|
|
168
|
+
* - 32 bits doesn't feel collision-resistant enough; 64 is nice.
|
|
169
|
+
*/
|
|
170
|
+
static uint64_t
|
|
171
|
+
fnv1a_64(const char *str)
|
|
172
|
+
{
|
|
173
|
+
unsigned char *s = (unsigned char *)str;
|
|
174
|
+
uint64_t h = (uint64_t)0xcbf29ce484222325ULL;
|
|
162
175
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if (state) exception = rb_eStandardError; \
|
|
168
|
-
goto fail; \
|
|
169
|
-
} while(0);
|
|
176
|
+
while (*s) {
|
|
177
|
+
h ^= (uint64_t)*s++;
|
|
178
|
+
h += (h << 1) + (h << 4) + (h << 5) + (h << 7) + (h << 8) + (h << 40);
|
|
179
|
+
}
|
|
170
180
|
|
|
171
|
-
|
|
172
|
-
|
|
181
|
+
return h;
|
|
182
|
+
}
|
|
173
183
|
|
|
174
|
-
|
|
175
|
-
|
|
184
|
+
/*
|
|
185
|
+
* The idea here is that we want a cache key member that changes when the OS
|
|
186
|
+
* changes in such a way as to make existing compiled ISeqs unloadable.
|
|
187
|
+
*/
|
|
188
|
+
static uint32_t
|
|
189
|
+
get_os_version(void)
|
|
190
|
+
{
|
|
191
|
+
#ifdef _WIN32
|
|
192
|
+
return (uint32_t)GetVersion();
|
|
193
|
+
#else
|
|
194
|
+
uint64_t hash;
|
|
195
|
+
struct utsname utsname;
|
|
176
196
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
output_data = final; \
|
|
180
|
-
goto cleanup; \
|
|
181
|
-
} while(0);
|
|
197
|
+
/* Not worth crashing if this fails; lose cache invalidation potential */
|
|
198
|
+
if (uname(&utsname) < 0) return 0;
|
|
182
199
|
|
|
183
|
-
|
|
184
|
-
bs_fetch(VALUE self, VALUE pathval, VALUE handler)
|
|
185
|
-
{
|
|
186
|
-
const char * path;
|
|
200
|
+
hash = fnv1a_64(utsname.version);
|
|
187
201
|
|
|
188
|
-
|
|
189
|
-
|
|
202
|
+
return (uint32_t)(hash >> 32);
|
|
203
|
+
#endif
|
|
204
|
+
}
|
|
190
205
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
206
|
+
/*
|
|
207
|
+
* Given a cache root directory and the full path to a file being cached,
|
|
208
|
+
* generate a path under the cache directory at which the cached artifact will
|
|
209
|
+
* be stored.
|
|
210
|
+
*
|
|
211
|
+
* The path will look something like: <cachedir>/12/34567890abcdef
|
|
212
|
+
*/
|
|
213
|
+
static void
|
|
214
|
+
bs_cache_path(const char * cachedir, const char * path, char ** cache_path)
|
|
215
|
+
{
|
|
216
|
+
uint64_t hash = fnv1a_64(path);
|
|
198
217
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
VALUE output_data; /* return data, e.g. ruby hash or loaded iseq */
|
|
218
|
+
uint8_t first_byte = (hash >> (64 - 8));
|
|
219
|
+
uint64_t remainder = hash & 0x00ffffffffffffff;
|
|
202
220
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
#define rb_raise error!
|
|
206
|
-
|
|
207
|
-
retry = 0;
|
|
208
|
-
begin:
|
|
209
|
-
output_data = Qnil;
|
|
210
|
-
contents = 0;
|
|
211
|
-
|
|
212
|
-
/* Blow up if we can't turn our argument into a char* */
|
|
213
|
-
Check_Type(pathval, T_STRING);
|
|
214
|
-
path = RSTRING_PTR(pathval);
|
|
215
|
-
|
|
216
|
-
/* open the file, get its mtime and read the cache key xattr */
|
|
217
|
-
CHECK_C(fd = bs_open(path, &writable), "open");
|
|
218
|
-
CHECK_C( fstat(fd, &statbuf), "fstat");
|
|
219
|
-
CHECK_C(valid_cache = bs_get_cache(fd, &cache_key), "fgetxattr");
|
|
220
|
-
|
|
221
|
-
/* `valid_cache` is true if the cache key isn't trivially invalid, e.g. built
|
|
222
|
-
* with a different RUBY_REVISION */
|
|
223
|
-
if (valid_cache && cache_key.mtime == (uint64_t)statbuf.st_mtime) {
|
|
224
|
-
/* if the mtimes match, assume the cache is valid. fetch the cached data. */
|
|
225
|
-
ret = bs_fetch_data(fd, (size_t)cache_key.data_size, handler, &output_data, &exception_tag);
|
|
226
|
-
if (ret == -1 && errno == _ENOATTR) {
|
|
227
|
-
/* the key was present, but the data was missing. remove the key, and
|
|
228
|
-
* start over */
|
|
229
|
-
CHECK_C(fremovexattr(fd, xattr_key_name REMOVEXATTR_TRAILER), "fremovexattr");
|
|
230
|
-
goto retry;
|
|
231
|
-
}
|
|
232
|
-
CHECK_RB0();
|
|
233
|
-
CHECK_C(ret, "fgetxattr/fetch-data");
|
|
234
|
-
if (!NIL_P(output_data)) {
|
|
235
|
-
stats.hit++;
|
|
236
|
-
SUCCEED(output_data); /* this is the fast-path to shoot for */
|
|
237
|
-
}
|
|
238
|
-
valid_cache = false; /* invalid cache; we'll want to regenerate it */
|
|
239
|
-
}
|
|
221
|
+
sprintf(*cache_path, "%s/%02x/%014llx", cachedir, first_byte, remainder);
|
|
222
|
+
}
|
|
240
223
|
|
|
241
|
-
|
|
242
|
-
|
|
224
|
+
/*
|
|
225
|
+
* Test whether a newly-generated cache key based on the file as it exists on
|
|
226
|
+
* disk matches the one that was generated when the file was cached (or really
|
|
227
|
+
* compare any two keys).
|
|
228
|
+
*
|
|
229
|
+
* The data_size member is not compared, as it serves more of a "header"
|
|
230
|
+
* function.
|
|
231
|
+
*/
|
|
232
|
+
static int
|
|
233
|
+
cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2)
|
|
234
|
+
{
|
|
235
|
+
return (
|
|
236
|
+
k1->version == k2->version &&
|
|
237
|
+
k1->os_version == k2->os_version &&
|
|
238
|
+
k1->compile_option == k2->compile_option &&
|
|
239
|
+
k1->ruby_revision == k2->ruby_revision &&
|
|
240
|
+
k1->size == k2->size &&
|
|
241
|
+
k1->mtime == k2->mtime
|
|
242
|
+
);
|
|
243
|
+
}
|
|
243
244
|
|
|
244
|
-
|
|
245
|
-
|
|
245
|
+
/*
|
|
246
|
+
* Entrypoint for Bootsnap::CompileCache::Native.fetch. The real work is done
|
|
247
|
+
* in bs_fetch; this function just performs some basic typechecks and
|
|
248
|
+
* conversions on the ruby VALUE arguments before passing them along.
|
|
249
|
+
*/
|
|
250
|
+
static VALUE
|
|
251
|
+
bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
|
|
252
|
+
{
|
|
253
|
+
Check_Type(cachedir_v, T_STRING);
|
|
254
|
+
Check_Type(path_v, T_STRING);
|
|
246
255
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
* the input format to the output format and return */
|
|
250
|
-
if (!writable) {
|
|
251
|
-
stats.unwritable++;
|
|
252
|
-
CHECK_RB(bs_input_to_output(handler, input_data, &output_data, &exception_tag));
|
|
253
|
-
SUCCEED(output_data);
|
|
256
|
+
if (RSTRING_LEN(cachedir_v) > MAX_CACHEDIR_SIZE) {
|
|
257
|
+
rb_raise(rb_eArgError, "cachedir too long");
|
|
254
258
|
}
|
|
255
259
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
stats.miss++;
|
|
260
|
+
char * cachedir = RSTRING_PTR(cachedir_v);
|
|
261
|
+
char * path = RSTRING_PTR(path_v);
|
|
262
|
+
char cache_path[MAX_CACHEPATH_SIZE];
|
|
260
263
|
|
|
261
|
-
/*
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
if (storage_data == uncompilable) {
|
|
265
|
-
/* The handler can raise Bootsnap::CompileCache::Uncompilable. When it does this,
|
|
266
|
-
* we just call the input_to_output handler method, bypassing the storage format. */
|
|
267
|
-
CHECK_RB(bs_input_to_output(handler, input_data, &output_data, &exception_tag));
|
|
268
|
-
stats.uncompilable++;
|
|
269
|
-
SUCCEED(output_data);
|
|
264
|
+
{ /* generate cache path to cache_path */
|
|
265
|
+
char * tmp = (char *)&cache_path;
|
|
266
|
+
bs_cache_path(cachedir, path, &tmp);
|
|
270
267
|
}
|
|
271
268
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
goto invalid_type_storage_data;
|
|
275
|
-
}
|
|
269
|
+
return bs_fetch(path, path_v, cache_path, handler);
|
|
270
|
+
}
|
|
276
271
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
272
|
+
/*
|
|
273
|
+
* Open the file we want to load/cache and generate a cache key for it if it
|
|
274
|
+
* was loaded.
|
|
275
|
+
*/
|
|
276
|
+
static int
|
|
277
|
+
open_current_file(char * path, struct bs_cache_key * key)
|
|
278
|
+
{
|
|
279
|
+
struct stat statbuf;
|
|
280
|
+
int fd;
|
|
285
281
|
|
|
286
|
-
|
|
282
|
+
fd = open(path, O_RDONLY);
|
|
283
|
+
if (fd < 0) return fd;
|
|
284
|
+
#ifdef _WIN32
|
|
285
|
+
setmode(fd, O_BINARY);
|
|
286
|
+
#endif
|
|
287
287
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
288
|
+
if (fstat(fd, &statbuf) < 0) {
|
|
289
|
+
close(fd);
|
|
290
|
+
return -1;
|
|
291
|
+
}
|
|
292
292
|
|
|
293
|
-
|
|
294
|
-
|
|
293
|
+
key->version = current_version;
|
|
294
|
+
key->os_version = current_os_version;
|
|
295
|
+
key->compile_option = current_compile_option_crc32;
|
|
296
|
+
key->ruby_revision = current_ruby_revision;
|
|
297
|
+
key->size = (uint64_t)statbuf.st_size;
|
|
298
|
+
key->mtime = (uint64_t)statbuf.st_mtime;
|
|
295
299
|
|
|
296
|
-
|
|
297
|
-
|
|
300
|
+
return fd;
|
|
301
|
+
}
|
|
298
302
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
/* deletion here is best effort; no need to fail if it does */
|
|
302
|
-
fremovexattr(fd, xattr_key_name REMOVEXATTR_TRAILER);
|
|
303
|
-
fremovexattr(fd, xattr_data_name REMOVEXATTR_TRAILER);
|
|
304
|
-
CHECK_RB(bs_input_to_output(handler, input_data, &output_data, &exception_tag));
|
|
305
|
-
}
|
|
303
|
+
#define ERROR_WITH_ERRNO -1
|
|
304
|
+
#define CACHE_MISSING_OR_INVALID -2
|
|
306
305
|
|
|
307
|
-
|
|
306
|
+
/*
|
|
307
|
+
* Read the cache key from the given fd, which must have position 0 (e.g.
|
|
308
|
+
* freshly opened file).
|
|
309
|
+
*
|
|
310
|
+
* Possible return values:
|
|
311
|
+
* - 0 (OK, key was loaded)
|
|
312
|
+
* - CACHE_MISSING_OR_INVALID (-2)
|
|
313
|
+
* - ERROR_WITH_ERRNO (-1, errno is set)
|
|
314
|
+
*/
|
|
315
|
+
static int
|
|
316
|
+
bs_read_key(int fd, struct bs_cache_key * key)
|
|
317
|
+
{
|
|
318
|
+
ssize_t nread = read(fd, key, KEY_SIZE);
|
|
319
|
+
if (nread < 0) return ERROR_WITH_ERRNO;
|
|
320
|
+
if (nread < KEY_SIZE) return CACHE_MISSING_OR_INVALID;
|
|
321
|
+
return 0;
|
|
322
|
+
}
|
|
308
323
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
324
|
+
/*
|
|
325
|
+
* Open the cache file at a given path, if it exists, and read its key into the
|
|
326
|
+
* struct.
|
|
327
|
+
*
|
|
328
|
+
* Possible return values:
|
|
329
|
+
* - 0 (OK, key was loaded)
|
|
330
|
+
* - CACHE_MISSING_OR_INVALID (-2)
|
|
331
|
+
* - ERROR_WITH_ERRNO (-1, errno is set)
|
|
332
|
+
*/
|
|
333
|
+
static int
|
|
334
|
+
open_cache_file(const char * path, struct bs_cache_key * key)
|
|
335
|
+
{
|
|
336
|
+
int fd, res;
|
|
314
337
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
fail:
|
|
320
|
-
CLEANUP;
|
|
321
|
-
stats.fail++;
|
|
322
|
-
rb_exc_raise(exception);
|
|
323
|
-
__builtin_unreachable();
|
|
324
|
-
invalid_type_storage_data:
|
|
325
|
-
CLEANUP;
|
|
326
|
-
stats.fail++;
|
|
327
|
-
Check_Type(storage_data, T_STRING);
|
|
328
|
-
__builtin_unreachable();
|
|
329
|
-
retry:
|
|
330
|
-
CLEANUP;
|
|
331
|
-
stats.retry++;
|
|
332
|
-
if (retry == 1) {
|
|
333
|
-
rb_raise(rb_eRuntimeError, "internal error in bootsnap");
|
|
334
|
-
__builtin_unreachable();
|
|
338
|
+
fd = open(path, O_RDONLY);
|
|
339
|
+
if (fd < 0) {
|
|
340
|
+
if (errno == ENOENT) return CACHE_MISSING_OR_INVALID;
|
|
341
|
+
return ERROR_WITH_ERRNO;
|
|
335
342
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
+
#ifdef _WIN32
|
|
344
|
+
setmode(fd, O_BINARY);
|
|
345
|
+
#endif
|
|
346
|
+
|
|
347
|
+
res = bs_read_key(fd, key);
|
|
348
|
+
if (res < 0) {
|
|
349
|
+
close(fd);
|
|
350
|
+
return res;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return fd;
|
|
343
354
|
}
|
|
344
355
|
|
|
356
|
+
/*
|
|
357
|
+
* The cache file is laid out like:
|
|
358
|
+
* 0...64 : bs_cache_key
|
|
359
|
+
* 64..-1 : cached artifact
|
|
360
|
+
*
|
|
361
|
+
* This function takes a file descriptor whose position is pre-set to 64, and
|
|
362
|
+
* the data_size (corresponding to the remaining number of bytes) listed in the
|
|
363
|
+
* cache header.
|
|
364
|
+
*
|
|
365
|
+
* We load the text from this file into a buffer, and pass it to the ruby-land
|
|
366
|
+
* handler with exception handling via the exception_tag param.
|
|
367
|
+
*
|
|
368
|
+
* Data is returned via the output_data parameter, which, if there's no error
|
|
369
|
+
* or exception, will be the final data returnable to the user.
|
|
370
|
+
*/
|
|
345
371
|
static int
|
|
346
|
-
|
|
372
|
+
fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag)
|
|
347
373
|
{
|
|
374
|
+
char * data = NULL;
|
|
375
|
+
ssize_t nread;
|
|
348
376
|
int ret;
|
|
349
|
-
ssize_t nbytes;
|
|
350
|
-
void * xattr_data;
|
|
351
|
-
VALUE storage_data;
|
|
352
377
|
|
|
353
|
-
|
|
354
|
-
*exception_tag = 0;
|
|
378
|
+
VALUE storage_data;
|
|
355
379
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
if (nbytes == -1) {
|
|
380
|
+
if (data_size > 100000000000) {
|
|
381
|
+
errno = EINVAL; /* because wtf? */
|
|
359
382
|
ret = -1;
|
|
360
383
|
goto done;
|
|
361
384
|
}
|
|
362
|
-
|
|
363
|
-
|
|
385
|
+
data = ALLOC_N(char, data_size);
|
|
386
|
+
nread = read(fd, data, data_size);
|
|
387
|
+
if (nread < 0) {
|
|
364
388
|
ret = -1;
|
|
365
389
|
goto done;
|
|
366
390
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
*exception_tag = ret;
|
|
371
|
-
errno = 0;
|
|
391
|
+
if (nread != data_size) {
|
|
392
|
+
ret = CACHE_MISSING_OR_INVALID;
|
|
393
|
+
goto done;
|
|
372
394
|
}
|
|
373
|
-
done:
|
|
374
|
-
xfree(xattr_data);
|
|
375
|
-
return ret;
|
|
376
|
-
}
|
|
377
395
|
|
|
378
|
-
|
|
379
|
-
bs_update_key(int fd, uint32_t data_size, uint64_t current_mtime)
|
|
380
|
-
{
|
|
381
|
-
struct xattr_key xattr_key;
|
|
382
|
-
|
|
383
|
-
xattr_key = (struct xattr_key){
|
|
384
|
-
.version = current_version,
|
|
385
|
-
.os_version = os_version,
|
|
386
|
-
.data_size = data_size,
|
|
387
|
-
.compile_option = current_compile_option_crc32,
|
|
388
|
-
.ruby_revision = current_ruby_revision,
|
|
389
|
-
.mtime = current_mtime,
|
|
390
|
-
};
|
|
396
|
+
storage_data = rb_str_new_static(data, data_size);
|
|
391
397
|
|
|
392
|
-
|
|
398
|
+
*exception_tag = bs_storage_to_output(handler, storage_data, output_data);
|
|
399
|
+
ret = 0;
|
|
400
|
+
done:
|
|
401
|
+
if (data != NULL) xfree(data);
|
|
402
|
+
return ret;
|
|
393
403
|
}
|
|
394
404
|
|
|
395
405
|
/*
|
|
396
|
-
*
|
|
397
|
-
*
|
|
406
|
+
* Like mkdir -p, this recursively creates directory parents of a file. e.g.
|
|
407
|
+
* given /a/b/c, creates /a and /a/b.
|
|
398
408
|
*/
|
|
399
409
|
static int
|
|
400
|
-
|
|
410
|
+
mkpath(char * file_path, mode_t mode)
|
|
401
411
|
{
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
*
|
|
408
|
-
|
|
409
|
-
|
|
412
|
+
/* It would likely be more efficient to count back until we
|
|
413
|
+
* find a component that *does* exist, but this will only run
|
|
414
|
+
* at most 256 times, so it seems not worthwhile to change. */
|
|
415
|
+
char * p;
|
|
416
|
+
for (p = strchr(file_path + 1, '/'); p; p = strchr(p + 1, '/')) {
|
|
417
|
+
*p = '\0';
|
|
418
|
+
#ifdef _WIN32
|
|
419
|
+
if (mkdir(file_path) == -1) {
|
|
420
|
+
#else
|
|
421
|
+
if (mkdir(file_path, mode) == -1) {
|
|
422
|
+
#endif
|
|
423
|
+
if (errno != EEXIST) {
|
|
424
|
+
*p = '/';
|
|
425
|
+
return -1;
|
|
426
|
+
}
|
|
410
427
|
}
|
|
411
|
-
|
|
428
|
+
*p = '/';
|
|
412
429
|
}
|
|
413
|
-
return
|
|
430
|
+
return 0;
|
|
414
431
|
}
|
|
415
432
|
|
|
416
433
|
/*
|
|
417
|
-
*
|
|
418
|
-
*
|
|
419
|
-
*
|
|
420
|
-
* 1: valid cache
|
|
421
|
-
* -1: fgetxattr failed, errno is set
|
|
434
|
+
* Write a cache header/key and a compiled artifact to a given cache path by
|
|
435
|
+
* writing to a tmpfile and then renaming the tmpfile over top of the final
|
|
436
|
+
* path.
|
|
422
437
|
*/
|
|
423
438
|
static int
|
|
424
|
-
|
|
439
|
+
atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data)
|
|
425
440
|
{
|
|
426
|
-
|
|
441
|
+
char template[MAX_CACHEPATH_SIZE + 20];
|
|
442
|
+
char * dest;
|
|
443
|
+
char * tmp_path;
|
|
444
|
+
int fd;
|
|
445
|
+
ssize_t nwrite;
|
|
446
|
+
|
|
447
|
+
dest = strncpy(template, path, MAX_CACHEPATH_SIZE);
|
|
448
|
+
strcat(dest, ".tmp.XXXXXX");
|
|
449
|
+
|
|
450
|
+
tmp_path = mktemp(template);
|
|
451
|
+
fd = open(tmp_path, O_WRONLY | O_CREAT, 0644);
|
|
452
|
+
if (fd < 0) {
|
|
453
|
+
if (mkpath(path, 0755) < 0) return -1;
|
|
454
|
+
fd = open(tmp_path, O_WRONLY | O_CREAT, 0644);
|
|
455
|
+
if (fd < 0) return -1;
|
|
456
|
+
}
|
|
457
|
+
#ifdef _WIN32
|
|
458
|
+
setmode(fd, O_BINARY);
|
|
459
|
+
#endif
|
|
460
|
+
|
|
461
|
+
key->data_size = RSTRING_LEN(data);
|
|
462
|
+
nwrite = write(fd, key, KEY_SIZE);
|
|
463
|
+
if (nwrite < 0) return -1;
|
|
464
|
+
if (nwrite != KEY_SIZE) {
|
|
465
|
+
errno = EIO; /* Lies but whatever */
|
|
466
|
+
return -1;
|
|
467
|
+
}
|
|
427
468
|
|
|
428
|
-
|
|
429
|
-
if (
|
|
469
|
+
nwrite = write(fd, RSTRING_PTR(data), RSTRING_LEN(data));
|
|
470
|
+
if (nwrite < 0) return -1;
|
|
471
|
+
if (nwrite != RSTRING_LEN(data)) {
|
|
472
|
+
errno = EIO; /* Lies but whatever */
|
|
430
473
|
return -1;
|
|
431
474
|
}
|
|
432
475
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
key->os_version == os_version && \
|
|
436
|
-
key->compile_option == current_compile_option_crc32 && \
|
|
437
|
-
key->ruby_revision == current_ruby_revision);
|
|
476
|
+
close(fd);
|
|
477
|
+
return rename(tmp_path, path);
|
|
438
478
|
}
|
|
439
479
|
|
|
440
480
|
/*
|
|
441
|
-
*
|
|
442
|
-
*
|
|
481
|
+
* Given an errno value (converted to a ruby Fixnum), return the corresponding
|
|
482
|
+
* Errno::* constant. If none is found, return StandardError instead.
|
|
443
483
|
*/
|
|
444
|
-
static
|
|
484
|
+
static VALUE
|
|
485
|
+
prot_exception_for_errno(VALUE err)
|
|
486
|
+
{
|
|
487
|
+
if (err != INT2FIX(0)) {
|
|
488
|
+
VALUE mErrno = rb_const_get(rb_cObject, rb_intern("Errno"));
|
|
489
|
+
VALUE constants = rb_funcall(mErrno, rb_intern("constants"), 0);
|
|
490
|
+
VALUE which = rb_funcall(constants, rb_intern("[]"), 1, err);
|
|
491
|
+
return rb_funcall(mErrno, rb_intern("const_get"), 1, which);
|
|
492
|
+
}
|
|
493
|
+
return rb_eStandardError;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
/* Read contents from an fd, whose contents are asserted to be +size+ bytes
|
|
498
|
+
* long, into a buffer */
|
|
499
|
+
static ssize_t
|
|
445
500
|
bs_read_contents(int fd, size_t size, char ** contents)
|
|
446
501
|
{
|
|
447
502
|
*contents = ALLOC_N(char, size);
|
|
448
503
|
return read(fd, *contents, size);
|
|
449
504
|
}
|
|
450
505
|
|
|
451
|
-
|
|
452
|
-
|
|
506
|
+
/*
|
|
507
|
+
* This is the meat of the extension. bs_fetch is
|
|
508
|
+
* Bootsnap::CompileCache::Native.fetch.
|
|
509
|
+
*
|
|
510
|
+
* There are three "formats" in use here:
|
|
511
|
+
* 1. "input" fomat, which is what we load from the source file;
|
|
512
|
+
* 2. "storage" format, which we write to the cache;
|
|
513
|
+
* 3. "output" format, which is what we return.
|
|
514
|
+
*
|
|
515
|
+
* E.g., For ISeq compilation:
|
|
516
|
+
* input: ruby source, as text
|
|
517
|
+
* storage: binary string (RubyVM::InstructionSequence#to_binary)
|
|
518
|
+
* output: Instance of RubyVM::InstructionSequence
|
|
519
|
+
*
|
|
520
|
+
* And for YAML:
|
|
521
|
+
* input: yaml as text
|
|
522
|
+
* storage: MessagePack or Marshal text
|
|
523
|
+
* output: ruby object, loaded from yaml/messagepack/marshal
|
|
524
|
+
*
|
|
525
|
+
* The handler passed in must support three messages:
|
|
526
|
+
* * storage_to_output(s) -> o
|
|
527
|
+
* * input_to_output(i) -> o
|
|
528
|
+
* * input_to_storage(i) -> s
|
|
529
|
+
* (input_to_storage may raise Bootsnap::CompileCache::Uncompilable, which
|
|
530
|
+
* will prevent caching and cause output to be generated with
|
|
531
|
+
* input_to_output)
|
|
532
|
+
*
|
|
533
|
+
* The semantics of this function are basically:
|
|
534
|
+
*
|
|
535
|
+
* return storage_to_output(cache[path]) if cache[path]
|
|
536
|
+
* storage = input_to_storage(input)
|
|
537
|
+
* cache[path] = storage
|
|
538
|
+
* return storage_to_output(storage)
|
|
539
|
+
*
|
|
540
|
+
* Or expanded a bit:
|
|
541
|
+
*
|
|
542
|
+
* - Check if the cache file exists and is up to date.
|
|
543
|
+
* - If it is, load this data to storage_data.
|
|
544
|
+
* - return storage_to_output(storage_data)
|
|
545
|
+
* - Read the file to input_data
|
|
546
|
+
* - Generate storage_data using input_to_storage(input_data)
|
|
547
|
+
* - Write storage_data data, with a cache key, to the cache file.
|
|
548
|
+
* - Return storage_to_output(storage_data)
|
|
549
|
+
*/
|
|
550
|
+
static VALUE
|
|
551
|
+
bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
453
552
|
{
|
|
454
|
-
struct
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
553
|
+
struct bs_cache_key cached_key, current_key;
|
|
554
|
+
char * contents = NULL;
|
|
555
|
+
int cache_fd = -1, current_fd = -1;
|
|
556
|
+
int res, valid_cache, exception_tag = 0;
|
|
557
|
+
|
|
558
|
+
VALUE input_data; /* data read from source file, e.g. YAML or ruby source */
|
|
559
|
+
VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
|
|
560
|
+
VALUE output_data; /* return data, e.g. ruby hash or loaded iseq */
|
|
561
|
+
|
|
562
|
+
VALUE exception; /* ruby exception object to raise instead of returning */
|
|
563
|
+
|
|
564
|
+
/* Open the source file and generate a cache key for it */
|
|
565
|
+
current_fd = open_current_file(path, ¤t_key);
|
|
566
|
+
if (current_fd < 0) goto fail_errno;
|
|
567
|
+
|
|
568
|
+
/* Open the cache key if it exists, and read its cache key in */
|
|
569
|
+
cache_fd = open_cache_file(cache_path, &cached_key);
|
|
570
|
+
if (cache_fd < 0 && cache_fd != CACHE_MISSING_OR_INVALID) goto fail_errno;
|
|
571
|
+
|
|
572
|
+
/* True if the cache existed and no invalidating changes have occurred since
|
|
573
|
+
* it was generated. */
|
|
574
|
+
valid_cache = cache_key_equal(¤t_key, &cached_key);
|
|
575
|
+
|
|
576
|
+
if (valid_cache) {
|
|
577
|
+
/* Fetch the cache data and return it if we're able to load it successfully */
|
|
578
|
+
res = fetch_cached_data(cache_fd, (ssize_t)cached_key.data_size, handler, &output_data, &exception_tag);
|
|
579
|
+
if (exception_tag != 0) goto raise;
|
|
580
|
+
else if (res == CACHE_MISSING_OR_INVALID) valid_cache = 0;
|
|
581
|
+
else if (res == ERROR_WITH_ERRNO) goto fail_errno;
|
|
582
|
+
else if (!NIL_P(output_data)) goto succeed; /* fast-path, goal */
|
|
460
583
|
}
|
|
461
|
-
|
|
462
|
-
|
|
584
|
+
close(cache_fd);
|
|
585
|
+
cache_fd = -1;
|
|
586
|
+
/* Cache is stale, invalid, or missing. Regenerate and write it out. */
|
|
587
|
+
|
|
588
|
+
/* Read the contents of the source file into a buffer */
|
|
589
|
+
if (bs_read_contents(current_fd, current_key.size, &contents) < 0) goto fail_errno;
|
|
590
|
+
input_data = rb_str_new_static(contents, current_key.size);
|
|
591
|
+
|
|
592
|
+
/* Try to compile the input_data using input_to_storage(input_data) */
|
|
593
|
+
exception_tag = bs_input_to_storage(handler, input_data, path_v, &storage_data);
|
|
594
|
+
if (exception_tag != 0) goto raise;
|
|
595
|
+
/* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
|
|
596
|
+
* to cache anything; just return input_to_output(input_data) */
|
|
597
|
+
if (storage_data == uncompilable) {
|
|
598
|
+
bs_input_to_output(handler, input_data, &output_data, &exception_tag);
|
|
599
|
+
if (exception_tag != 0) goto raise;
|
|
600
|
+
goto succeed;
|
|
601
|
+
}
|
|
602
|
+
/* If storage_data isn't a string, we can't cache it */
|
|
603
|
+
if (!RB_TYPE_P(storage_data, T_STRING)) goto invalid_type_storage_data;
|
|
604
|
+
|
|
605
|
+
/* Write the cache key and storage_data to the cache directory */
|
|
606
|
+
res = atomic_write_cache_file(cache_path, ¤t_key, storage_data);
|
|
607
|
+
if (res < 0) goto fail_errno;
|
|
608
|
+
|
|
609
|
+
/* Having written the cache, now convert storage_data to output_data */
|
|
610
|
+
exception_tag = bs_storage_to_output(handler, storage_data, &output_data);
|
|
611
|
+
if (exception_tag != 0) goto raise;
|
|
612
|
+
|
|
613
|
+
/* If output_data is nil, delete the cache entry and generate the output
|
|
614
|
+
* using input_to_output */
|
|
615
|
+
if (NIL_P(output_data)) {
|
|
616
|
+
if (unlink(cache_path) < 0) goto fail_errno;
|
|
617
|
+
bs_input_to_output(handler, input_data, &output_data, &exception_tag);
|
|
618
|
+
if (exception_tag != 0) goto raise;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
goto succeed; /* output_data is now the correct return. */
|
|
622
|
+
|
|
623
|
+
#define CLEANUP \
|
|
624
|
+
if (contents != NULL) xfree(contents); \
|
|
625
|
+
if (current_fd >= 0) close(current_fd); \
|
|
626
|
+
if (cache_fd >= 0) close(cache_fd);
|
|
627
|
+
|
|
628
|
+
succeed:
|
|
629
|
+
CLEANUP;
|
|
630
|
+
return output_data;
|
|
631
|
+
fail_errno:
|
|
632
|
+
CLEANUP;
|
|
633
|
+
exception = rb_protect(prot_exception_for_errno, INT2FIX(errno), &res);
|
|
634
|
+
if (res) exception = rb_eStandardError;
|
|
635
|
+
rb_exc_raise(exception);
|
|
636
|
+
__builtin_unreachable();
|
|
637
|
+
raise:
|
|
638
|
+
CLEANUP;
|
|
639
|
+
rb_jump_tag(exception_tag);
|
|
640
|
+
__builtin_unreachable();
|
|
641
|
+
invalid_type_storage_data:
|
|
642
|
+
CLEANUP;
|
|
643
|
+
Check_Type(storage_data, T_STRING);
|
|
644
|
+
__builtin_unreachable();
|
|
645
|
+
|
|
646
|
+
#undef CLEANUP
|
|
463
647
|
}
|
|
464
648
|
|
|
649
|
+
/*****************************************************************************/
|
|
650
|
+
/********************* Handler Wrappers **************************************/
|
|
651
|
+
/*****************************************************************************
|
|
652
|
+
* Everything after this point in the file is just wrappers to deal with ruby's
|
|
653
|
+
* clunky method of handling exceptions from ruby methods invoked from C.
|
|
654
|
+
*/
|
|
655
|
+
|
|
656
|
+
struct s2o_data {
|
|
657
|
+
VALUE handler;
|
|
658
|
+
VALUE storage_data;
|
|
659
|
+
};
|
|
660
|
+
|
|
661
|
+
struct i2o_data {
|
|
662
|
+
VALUE handler;
|
|
663
|
+
VALUE input_data;
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
struct i2s_data {
|
|
667
|
+
VALUE handler;
|
|
668
|
+
VALUE input_data;
|
|
669
|
+
VALUE pathval;
|
|
670
|
+
};
|
|
671
|
+
|
|
465
672
|
static VALUE
|
|
466
|
-
|
|
673
|
+
prot_storage_to_output(VALUE arg)
|
|
467
674
|
{
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
VALUE constants = rb_funcall(mErrno, rb_intern("constants"), 0);
|
|
471
|
-
VALUE which = rb_funcall(constants, rb_intern("[]"), 1, err);
|
|
472
|
-
return rb_funcall(mErrno, rb_intern("const_get"), 1, which);
|
|
473
|
-
}
|
|
474
|
-
return rb_eStandardError;
|
|
675
|
+
struct s2o_data * data = (struct s2o_data *)arg;
|
|
676
|
+
return rb_funcall(data->handler, rb_intern("storage_to_output"), 1, data->storage_data);
|
|
475
677
|
}
|
|
476
678
|
|
|
477
|
-
static
|
|
478
|
-
|
|
679
|
+
static int
|
|
680
|
+
bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data)
|
|
479
681
|
{
|
|
480
|
-
|
|
481
|
-
|
|
682
|
+
int state;
|
|
683
|
+
struct s2o_data s2o_data = {
|
|
684
|
+
.handler = handler,
|
|
685
|
+
.storage_data = storage_data,
|
|
686
|
+
};
|
|
687
|
+
*output_data = rb_protect(prot_storage_to_output, (VALUE)&s2o_data, &state);
|
|
688
|
+
return state;
|
|
482
689
|
}
|
|
483
690
|
|
|
484
691
|
static void
|
|
@@ -491,6 +698,13 @@ bs_input_to_output(VALUE handler, VALUE input_data, VALUE * output_data, int * e
|
|
|
491
698
|
*output_data = rb_protect(prot_input_to_output, (VALUE)&i2o_data, exception_tag);
|
|
492
699
|
}
|
|
493
700
|
|
|
701
|
+
static VALUE
|
|
702
|
+
prot_input_to_output(VALUE arg)
|
|
703
|
+
{
|
|
704
|
+
struct i2o_data * data = (struct i2o_data *)arg;
|
|
705
|
+
return rb_funcall(data->handler, rb_intern("input_to_output"), 1, data->input_data);
|
|
706
|
+
}
|
|
707
|
+
|
|
494
708
|
static VALUE
|
|
495
709
|
try_input_to_storage(VALUE arg)
|
|
496
710
|
{
|
|
@@ -526,36 +740,3 @@ bs_input_to_storage(VALUE handler, VALUE input_data, VALUE pathval, VALUE * stor
|
|
|
526
740
|
*storage_data = rb_protect(prot_input_to_storage, (VALUE)&i2s_data, &state);
|
|
527
741
|
return state;
|
|
528
742
|
}
|
|
529
|
-
|
|
530
|
-
static VALUE
|
|
531
|
-
prot_storage_to_output(VALUE arg)
|
|
532
|
-
{
|
|
533
|
-
struct s2o_data * data = (struct s2o_data *)arg;
|
|
534
|
-
return rb_funcall(data->handler, rb_intern("storage_to_output"), 1, data->storage_data);
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
static int
|
|
538
|
-
bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data)
|
|
539
|
-
{
|
|
540
|
-
int state;
|
|
541
|
-
struct s2o_data s2o_data = {
|
|
542
|
-
.handler = handler,
|
|
543
|
-
.storage_data = storage_data,
|
|
544
|
-
};
|
|
545
|
-
*output_data = rb_protect(prot_storage_to_output, (VALUE)&s2o_data, &state);
|
|
546
|
-
return state;
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
/* default no if empty, yes if present, no if "0" */
|
|
550
|
-
static int
|
|
551
|
-
logging_enabled()
|
|
552
|
-
{
|
|
553
|
-
char * log = getenv("OPT_AOT_LOG");
|
|
554
|
-
if (log == 0) {
|
|
555
|
-
return 0;
|
|
556
|
-
} else if (log[0] == '0') {
|
|
557
|
-
return 0;
|
|
558
|
-
} else {
|
|
559
|
-
return 1;
|
|
560
|
-
}
|
|
561
|
-
}
|