bootsnap 1.4.6 → 1.7.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +68 -0
- data/README.md +45 -14
- data/exe/bootsnap +5 -0
- data/ext/bootsnap/bootsnap.c +224 -61
- data/ext/bootsnap/extconf.rb +19 -14
- data/lib/bootsnap.rb +88 -15
- data/lib/bootsnap/cli.rb +246 -0
- data/lib/bootsnap/cli/worker_pool.rb +131 -0
- data/lib/bootsnap/compile_cache.rb +2 -2
- data/lib/bootsnap/compile_cache/iseq.rb +21 -7
- data/lib/bootsnap/compile_cache/yaml.rb +109 -40
- data/lib/bootsnap/load_path_cache.rb +3 -16
- data/lib/bootsnap/load_path_cache/cache.rb +23 -6
- data/lib/bootsnap/load_path_cache/change_observer.rb +1 -1
- data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +16 -4
- data/lib/bootsnap/load_path_cache/loaded_features_index.rb +3 -3
- data/lib/bootsnap/load_path_cache/path.rb +2 -2
- data/lib/bootsnap/load_path_cache/path_scanner.rb +50 -26
- data/lib/bootsnap/load_path_cache/realpath_cache.rb +5 -5
- data/lib/bootsnap/load_path_cache/store.rb +16 -9
- data/lib/bootsnap/setup.rb +1 -36
- data/lib/bootsnap/version.rb +1 -1
- metadata +14 -28
- data/.github/CODEOWNERS +0 -2
- data/.github/probots.yml +0 -2
- data/.gitignore +0 -17
- data/.rubocop.yml +0 -20
- data/.travis.yml +0 -21
- data/CODE_OF_CONDUCT.md +0 -74
- data/CONTRIBUTING.md +0 -21
- data/Gemfile +0 -9
- data/README.jp.md +0 -231
- data/Rakefile +0 -13
- data/bin/ci +0 -10
- data/bin/console +0 -15
- data/bin/setup +0 -8
- data/bin/test-minimal-support +0 -7
- data/bin/testunit +0 -8
- data/bootsnap.gemspec +0 -46
- data/dev.yml +0 -10
- data/lib/bootsnap/load_path_cache/core_ext/active_support.rb +0 -107
- data/shipit.rubygems.yml +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7b27cdcd835cd67efea9340d2e09899a3fa553ded267992672183d3bce9cd7de
|
4
|
+
data.tar.gz: 73d228cbe15a69fb1a26d76f6375fe564f4de4db6e7f111e6a4d0ec31e09b0a1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e967771cc3387e0297d1654a824f7406e2db1395766dc6df5d2069b78129a71737a497e4681c8988f62ce308e646f967137929266004dbc0e3fbf212da53d3f0
|
7
|
+
data.tar.gz: ba6e782a7c4da7d8762cea5501359077b66b68f6b0cbb00e8533557cfd5194ef71bc5016c2273a8ba6ad0aa8e5f434f449c3d0cc8252fde215032807cfd75a9f
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,71 @@
|
|
1
|
+
# Unreleased
|
2
|
+
|
3
|
+
# 1.7.3
|
4
|
+
|
5
|
+
* Disable YAML precompilation when encountering YAML tags. (#351)
|
6
|
+
|
7
|
+
# 1.7.2
|
8
|
+
|
9
|
+
* Fix compatibility with msgpack < 1. (#349)
|
10
|
+
|
11
|
+
# 1.7.1
|
12
|
+
|
13
|
+
* Warn Ruby 2.5 users if they turn ISeq caching on. (#327, #244)
|
14
|
+
* Disable ISeq caching for the whole 2.5.x series again.
|
15
|
+
* Better handle hashing of Ruby strings. (#318)
|
16
|
+
|
17
|
+
# 1.7.0
|
18
|
+
|
19
|
+
* Fix detection of YAML files in gems.
|
20
|
+
* Adds an instrumentation API to monitor cache misses.
|
21
|
+
* Allow to control the behavior of `require 'bootsnap/setup'` using environment variables.
|
22
|
+
* Deprecate the `disable_trace` option.
|
23
|
+
* Deprecate the `ActiveSupport::Dependencies` (AKA Classic autoloader) integration. (#344)
|
24
|
+
|
25
|
+
# 1.6.0
|
26
|
+
|
27
|
+
* Fix a Ruby 2.7/3.0 issue with `YAML.load_file` keyword arguments. (#342)
|
28
|
+
* `bootsnap precompile` CLI use multiple processes to complete faster. (#341)
|
29
|
+
* `bootsnap precompile` CLI also precompile YAML files. (#340)
|
30
|
+
* Changed the load path cache directory from `$BOOTSNAP_CACHE_DIR/bootsnap-load-path-cache` to `$BOOTSNAP_CACHE_DIR/bootsnap/load-path-cache` for ease of use. (#334)
|
31
|
+
* Changed the compile cache directory from `$BOOTSNAP_CACHE_DIR/bootsnap-compile-cache` to `$BOOTSNAP_CACHE_DIR/bootsnap/compile-cache` for ease of use. (#334)
|
32
|
+
|
33
|
+
# 1.5.1
|
34
|
+
|
35
|
+
* Workaround a Ruby bug in InstructionSequence.compile_file. (#332)
|
36
|
+
|
37
|
+
# 1.5.0
|
38
|
+
|
39
|
+
* Add a command line to statically precompile the ISeq cache. (#326)
|
40
|
+
|
41
|
+
# 1.4.9
|
42
|
+
|
43
|
+
* [Windows support](https://github.com/Shopify/bootsnap/pull/319)
|
44
|
+
* [Fix potential crash](https://github.com/Shopify/bootsnap/pull/322)
|
45
|
+
|
46
|
+
# 1.4.8
|
47
|
+
|
48
|
+
* [Prevent FallbackScan from polluting exception cause](https://github.com/Shopify/bootsnap/pull/314)
|
49
|
+
|
50
|
+
# 1.4.7
|
51
|
+
|
52
|
+
* Various performance enhancements
|
53
|
+
* Fix race condition in heavy concurrent load scenarios that would cause bootsnap to raise
|
54
|
+
|
55
|
+
# 1.4.6
|
56
|
+
|
57
|
+
* Fix bug that was erroneously considering that files containing `.` in the names were being
|
58
|
+
required if a different file with the same name was already being required
|
59
|
+
|
60
|
+
Example:
|
61
|
+
|
62
|
+
require 'foo'
|
63
|
+
require 'foo.en'
|
64
|
+
|
65
|
+
Before bootsnap was considering `foo.en` to be the same file as `foo`
|
66
|
+
|
67
|
+
* Use glibc as part of the ruby_platform cache key
|
68
|
+
|
1
69
|
# 1.4.5
|
2
70
|
|
3
71
|
* MRI 2.7 support
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
# Bootsnap [![
|
1
|
+
# Bootsnap [![Actions Status](https://github.com/Shopify/bootsnap/workflows/ci/badge.svg)](https://github.com/Shopify/bootsnap/actions)
|
2
2
|
|
3
|
-
Bootsnap is a library that plugs into Ruby, with optional support for `
|
3
|
+
Bootsnap is a library that plugs into Ruby, with optional support for `YAML`,
|
4
4
|
to optimize and cache expensive computations. See [How Does This Work](#how-does-this-work).
|
5
5
|
|
6
6
|
#### Performance
|
@@ -11,7 +11,7 @@ to optimize and cache expensive computations. See [How Does This Work](#how-does
|
|
11
11
|
- The core Shopify platform -- a rather large monolithic application -- boots about 75% faster,
|
12
12
|
dropping from around 25s to 6.5s.
|
13
13
|
* In Shopify core (a large app), about 25% of this gain can be attributed to `compile_cache_*`
|
14
|
-
features; 75% to path caching
|
14
|
+
features; 75% to path caching. This is fairly representative.
|
15
15
|
|
16
16
|
## Usage
|
17
17
|
|
@@ -29,7 +29,8 @@ If you are using Rails, add this to `config/boot.rb` immediately after `require
|
|
29
29
|
require 'bootsnap/setup'
|
30
30
|
```
|
31
31
|
|
32
|
-
Note that bootsnap writes to `tmp/cache
|
32
|
+
Note that bootsnap writes to `tmp/cache` (or the path specified by `ENV['BOOTSNAP_CACHE_DIR']`),
|
33
|
+
and that directory *must* be writable. Rails will fail to
|
33
34
|
boot if it is not. If this is unacceptable (e.g. you are running in a read-only container and
|
34
35
|
unwilling to mount in a writable tmpdir), you should remove this line or wrap it in a conditional.
|
35
36
|
|
@@ -53,15 +54,11 @@ Bootsnap.setup(
|
|
53
54
|
cache_dir: 'tmp/cache', # Path to your cache
|
54
55
|
development_mode: env == 'development', # Current working environment, e.g. RACK_ENV, RAILS_ENV, etc
|
55
56
|
load_path_cache: true, # Optimize the LOAD_PATH with a cache
|
56
|
-
autoload_paths_cache: true, # Optimize ActiveSupport autoloads with cache
|
57
|
-
disable_trace: true, # Set `RubyVM::InstructionSequence.compile_option = { trace_instruction: false }`
|
58
57
|
compile_cache_iseq: true, # Compile Ruby code into ISeq cache, breaks coverage reporting.
|
59
58
|
compile_cache_yaml: true # Compile YAML into a cache
|
60
59
|
)
|
61
60
|
```
|
62
61
|
|
63
|
-
**Note that `disable_trace` will break debuggers and tracing.**
|
64
|
-
|
65
62
|
**Protip:** You can replace `require 'bootsnap'` with `BootLib::Require.from_gem('bootsnap',
|
66
63
|
'bootsnap')` using [this trick](https://github.com/Shopify/bootsnap/wiki/Bootlib::Require). This
|
67
64
|
will help optimize boot time further if you have an extremely large `$LOAD_PATH`.
|
@@ -71,12 +68,39 @@ speeds up the loading of individual source files, Spring keeps a copy of a pre-b
|
|
71
68
|
on hand to completely skip parts of the boot process the next time it's needed. The two tools work
|
72
69
|
well together, and are both included in a newly-generated Rails applications by default.
|
73
70
|
|
71
|
+
### Environment variables
|
72
|
+
|
73
|
+
`require 'bootsnap/setup'` behavior can be changed using environment variables:
|
74
|
+
|
75
|
+
- `BOOTSNAP_CACHE_DIR` allows to define the cache location.
|
76
|
+
- `DISABLE_BOOTSNAP` allows to entirely disable bootsnap.
|
77
|
+
- `DISABLE_BOOTSNAP_LOAD_PATH_CACHE` allows to disable load path caching.
|
78
|
+
- `DISABLE_BOOTSNAP_COMPILE_CACHE` allows to disable ISeq and YAML caches.
|
79
|
+
- `BOOTSNAP_LOG` configure bootsnap to log all caches misses to STDERR.
|
80
|
+
|
74
81
|
### Environments
|
75
82
|
|
76
83
|
All Bootsnap features are enabled in development, test, production, and all other environments according to the configuration in the setup. At Shopify, we use this gem safely in all environments without issue.
|
77
84
|
|
78
85
|
If you would like to disable any feature for a certain environment, we suggest changing the configuration to take into account the appropriate ENV var or configuration according to your needs.
|
79
86
|
|
87
|
+
### Instrumentation
|
88
|
+
|
89
|
+
Bootsnap cache misses can be monitored though a callback:
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
Bootsnap.instrumentation = ->(event, path) { puts "#{event} #{path}" }
|
93
|
+
```
|
94
|
+
|
95
|
+
`event` is either `:miss` or `:stale`. You can also call `Bootsnap.log!` as a shortcut to
|
96
|
+
log all events to STDERR.
|
97
|
+
|
98
|
+
To turn instrumentation back off you can set it to nil:
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
Bootsnap.instrumentation = nil
|
102
|
+
```
|
103
|
+
|
80
104
|
## How does this work?
|
81
105
|
|
82
106
|
Bootsnap optimizes methods to cache results of expensive computations, and can be grouped
|
@@ -84,8 +108,6 @@ into two broad categories:
|
|
84
108
|
|
85
109
|
* [Path Pre-Scanning](#path-pre-scanning)
|
86
110
|
* `Kernel#require` and `Kernel#load` are modified to eliminate `$LOAD_PATH` scans.
|
87
|
-
* `ActiveSupport::Dependencies.{autoloadable_module?,load_missing_constant,depend_on}` are
|
88
|
-
overridden to eliminate scans of `ActiveSupport::Dependencies.autoload_paths`.
|
89
111
|
* [Compilation caching](#compilation-caching)
|
90
112
|
* `RubyVM::InstructionSequence.load_iseq` is implemented to cache the result of ruby bytecode
|
91
113
|
compilation.
|
@@ -124,10 +146,6 @@ open y/foo.rb
|
|
124
146
|
...
|
125
147
|
```
|
126
148
|
|
127
|
-
Exactly the same strategy is employed for methods that traverse
|
128
|
-
`ActiveSupport::Dependencies.autoload_paths` if the `autoload_paths_cache` option is given to
|
129
|
-
`Bootsnap.setup`.
|
130
|
-
|
131
149
|
The following diagram flowcharts the overrides that make the `*_path_cache` features work.
|
132
150
|
|
133
151
|
![Flowchart explaining
|
@@ -294,6 +312,19 @@ open /c/nope.bundle -> -1
|
|
294
312
|
# (nothing!)
|
295
313
|
```
|
296
314
|
|
315
|
+
## Precompilation
|
316
|
+
|
317
|
+
In development environments the bootsnap compilation cache is generated on the fly when source files are loaded.
|
318
|
+
But in production environments, such as docker images, you might need to precompile the cache.
|
319
|
+
|
320
|
+
To do so you can use the `bootsnap precompile` command.
|
321
|
+
|
322
|
+
Example:
|
323
|
+
|
324
|
+
```bash
|
325
|
+
$ bundle exec bootsnap precompile --gemfile app/ lib/
|
326
|
+
```
|
327
|
+
|
297
328
|
## When not to use Bootsnap
|
298
329
|
|
299
330
|
*Alternative engines*: Bootsnap is pretty reliant on MRI features, and parts are disabled entirely on alternative ruby
|
data/exe/bootsnap
ADDED
data/ext/bootsnap/bootsnap.c
CHANGED
@@ -14,6 +14,7 @@
|
|
14
14
|
#include "bootsnap.h"
|
15
15
|
#include "ruby.h"
|
16
16
|
#include <stdint.h>
|
17
|
+
#include <stdbool.h>
|
17
18
|
#include <sys/types.h>
|
18
19
|
#include <errno.h>
|
19
20
|
#include <fcntl.h>
|
@@ -32,6 +33,12 @@
|
|
32
33
|
|
33
34
|
#define KEY_SIZE 64
|
34
35
|
|
36
|
+
#define MAX_CREATE_TEMPFILE_ATTEMPT 3
|
37
|
+
|
38
|
+
#ifndef RB_UNLIKELY
|
39
|
+
#define RB_UNLIKELY(x) (x)
|
40
|
+
#endif
|
41
|
+
|
35
42
|
/*
|
36
43
|
* An instance of this key is written as the first 64 bytes of each cache file.
|
37
44
|
* The mtime and size members track whether the file contents have changed, and
|
@@ -68,7 +75,7 @@ struct bs_cache_key {
|
|
68
75
|
STATIC_ASSERT(sizeof(struct bs_cache_key) == KEY_SIZE);
|
69
76
|
|
70
77
|
/* Effectively a schema version. Bumping invalidates all previous caches */
|
71
|
-
static const uint32_t current_version =
|
78
|
+
static const uint32_t current_version = 3;
|
72
79
|
|
73
80
|
/* hash of e.g. "x86_64-darwin17", invalidating when ruby is recompiled on a
|
74
81
|
* new OS ABI, etc. */
|
@@ -86,19 +93,25 @@ static VALUE rb_mBootsnap_CompileCache;
|
|
86
93
|
static VALUE rb_mBootsnap_CompileCache_Native;
|
87
94
|
static VALUE rb_eBootsnap_CompileCache_Uncompilable;
|
88
95
|
static ID uncompilable;
|
96
|
+
static ID instrumentation_method;
|
97
|
+
static VALUE sym_miss;
|
98
|
+
static VALUE sym_stale;
|
99
|
+
static bool instrumentation_enabled = false;
|
89
100
|
|
90
101
|
/* Functions exposed as module functions on Bootsnap::CompileCache::Native */
|
102
|
+
static VALUE bs_instrumentation_enabled_set(VALUE self, VALUE enabled);
|
91
103
|
static VALUE bs_compile_option_crc32_set(VALUE self, VALUE crc32_v);
|
92
|
-
static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler);
|
104
|
+
static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args);
|
105
|
+
static VALUE bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler);
|
93
106
|
|
94
107
|
/* Helpers */
|
95
|
-
static
|
96
|
-
static void bs_cache_path(const char * cachedir, const char * path, char (* cache_path)[MAX_CACHEPATH_SIZE]);
|
108
|
+
static void bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_CACHEPATH_SIZE]);
|
97
109
|
static int bs_read_key(int fd, struct bs_cache_key * key);
|
98
110
|
static int cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2);
|
99
|
-
static VALUE bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler);
|
111
|
+
static VALUE bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args);
|
112
|
+
static VALUE bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler);
|
100
113
|
static int open_current_file(char * path, struct bs_cache_key * key, const char ** errno_provenance);
|
101
|
-
static int fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag, const char ** errno_provenance);
|
114
|
+
static int fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE * output_data, int * exception_tag, const char ** errno_provenance);
|
102
115
|
static uint32_t get_ruby_revision(void);
|
103
116
|
static uint32_t get_ruby_platform(void);
|
104
117
|
|
@@ -106,12 +119,12 @@ static uint32_t get_ruby_platform(void);
|
|
106
119
|
* Helper functions to call ruby methods on handler object without crashing on
|
107
120
|
* exception.
|
108
121
|
*/
|
109
|
-
static int bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data);
|
122
|
+
static int bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * output_data);
|
110
123
|
static VALUE prot_storage_to_output(VALUE arg);
|
111
124
|
static VALUE prot_input_to_output(VALUE arg);
|
112
|
-
static void bs_input_to_output(VALUE handler, VALUE input_data, VALUE * output_data, int * exception_tag);
|
125
|
+
static void bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag);
|
113
126
|
static VALUE prot_input_to_storage(VALUE arg);
|
114
|
-
static int bs_input_to_storage(VALUE handler, VALUE input_data, VALUE pathval, VALUE * storage_data);
|
127
|
+
static int bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data);
|
115
128
|
struct s2o_data;
|
116
129
|
struct i2o_data;
|
117
130
|
struct i2s_data;
|
@@ -144,15 +157,31 @@ Init_bootsnap(void)
|
|
144
157
|
current_ruby_platform = get_ruby_platform();
|
145
158
|
|
146
159
|
uncompilable = rb_intern("__bootsnap_uncompilable__");
|
160
|
+
instrumentation_method = rb_intern("_instrument");
|
161
|
+
|
162
|
+
sym_miss = ID2SYM(rb_intern("miss"));
|
163
|
+
rb_global_variable(&sym_miss);
|
147
164
|
|
165
|
+
sym_stale = ID2SYM(rb_intern("stale"));
|
166
|
+
rb_global_variable(&sym_stale);
|
167
|
+
|
168
|
+
rb_define_module_function(rb_mBootsnap, "instrumentation_enabled=", bs_instrumentation_enabled_set, 1);
|
148
169
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "coverage_running?", bs_rb_coverage_running, 0);
|
149
|
-
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch,
|
170
|
+
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch, 4);
|
171
|
+
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "precompile", bs_rb_precompile, 3);
|
150
172
|
rb_define_module_function(rb_mBootsnap_CompileCache_Native, "compile_option_crc32=", bs_compile_option_crc32_set, 1);
|
151
173
|
|
152
174
|
current_umask = umask(0777);
|
153
175
|
umask(current_umask);
|
154
176
|
}
|
155
177
|
|
178
|
+
static VALUE
|
179
|
+
bs_instrumentation_enabled_set(VALUE self, VALUE enabled)
|
180
|
+
{
|
181
|
+
instrumentation_enabled = RTEST(enabled);
|
182
|
+
return enabled;
|
183
|
+
}
|
184
|
+
|
156
185
|
/*
|
157
186
|
* Bootsnap's ruby code registers a hook that notifies us via this function
|
158
187
|
* when compile_option changes. These changes invalidate all existing caches.
|
@@ -181,7 +210,7 @@ bs_compile_option_crc32_set(VALUE self, VALUE crc32_v)
|
|
181
210
|
* - 32 bits doesn't feel collision-resistant enough; 64 is nice.
|
182
211
|
*/
|
183
212
|
static uint64_t
|
184
|
-
|
213
|
+
fnv1a_64_iter_cstr(uint64_t h, const char *str)
|
185
214
|
{
|
186
215
|
unsigned char *s = (unsigned char *)str;
|
187
216
|
|
@@ -194,7 +223,21 @@ fnv1a_64_iter(uint64_t h, const char *str)
|
|
194
223
|
}
|
195
224
|
|
196
225
|
static uint64_t
|
197
|
-
|
226
|
+
fnv1a_64_iter(uint64_t h, const VALUE str)
|
227
|
+
{
|
228
|
+
unsigned char *s = (unsigned char *)RSTRING_PTR(str);
|
229
|
+
unsigned char *str_end = (unsigned char *)RSTRING_PTR(str) + RSTRING_LEN(str);
|
230
|
+
|
231
|
+
while (s < str_end) {
|
232
|
+
h ^= (uint64_t)*s++;
|
233
|
+
h += (h << 1) + (h << 4) + (h << 5) + (h << 7) + (h << 8) + (h << 40);
|
234
|
+
}
|
235
|
+
|
236
|
+
return h;
|
237
|
+
}
|
238
|
+
|
239
|
+
static uint64_t
|
240
|
+
fnv1a_64(const VALUE str)
|
198
241
|
{
|
199
242
|
uint64_t h = (uint64_t)0xcbf29ce484222325ULL;
|
200
243
|
return fnv1a_64_iter(h, str);
|
@@ -215,7 +258,7 @@ get_ruby_revision(void)
|
|
215
258
|
} else {
|
216
259
|
uint64_t hash;
|
217
260
|
|
218
|
-
hash = fnv1a_64(
|
261
|
+
hash = fnv1a_64(ruby_revision);
|
219
262
|
return (uint32_t)(hash >> 32);
|
220
263
|
}
|
221
264
|
}
|
@@ -235,19 +278,19 @@ get_ruby_platform(void)
|
|
235
278
|
VALUE ruby_platform;
|
236
279
|
|
237
280
|
ruby_platform = rb_const_get(rb_cObject, rb_intern("RUBY_PLATFORM"));
|
238
|
-
hash = fnv1a_64(
|
281
|
+
hash = fnv1a_64(ruby_platform);
|
239
282
|
|
240
283
|
#ifdef _WIN32
|
241
284
|
return (uint32_t)(hash >> 32) ^ (uint32_t)GetVersion();
|
242
285
|
#elif defined(__GLIBC__)
|
243
|
-
hash =
|
286
|
+
hash = fnv1a_64_iter_cstr(hash, gnu_get_libc_version());
|
244
287
|
return (uint32_t)(hash >> 32);
|
245
288
|
#else
|
246
289
|
struct utsname utsname;
|
247
290
|
|
248
291
|
/* Not worth crashing if this fails; lose extra cache invalidation potential */
|
249
292
|
if (uname(&utsname) >= 0) {
|
250
|
-
hash =
|
293
|
+
hash = fnv1a_64_iter_cstr(hash, utsname.version);
|
251
294
|
}
|
252
295
|
|
253
296
|
return (uint32_t)(hash >> 32);
|
@@ -262,14 +305,13 @@ get_ruby_platform(void)
|
|
262
305
|
* The path will look something like: <cachedir>/12/34567890abcdef
|
263
306
|
*/
|
264
307
|
static void
|
265
|
-
bs_cache_path(const char * cachedir, const
|
308
|
+
bs_cache_path(const char * cachedir, const VALUE path, char (* cache_path)[MAX_CACHEPATH_SIZE])
|
266
309
|
{
|
267
310
|
uint64_t hash = fnv1a_64(path);
|
268
|
-
|
269
311
|
uint8_t first_byte = (hash >> (64 - 8));
|
270
312
|
uint64_t remainder = hash & 0x00ffffffffffffff;
|
271
313
|
|
272
|
-
sprintf(*cache_path, "%s/%
|
314
|
+
sprintf(*cache_path, "%s/%02"PRIx8"/%014"PRIx64, cachedir, first_byte, remainder);
|
273
315
|
}
|
274
316
|
|
275
317
|
/*
|
@@ -299,7 +341,7 @@ cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2)
|
|
299
341
|
* conversions on the ruby VALUE arguments before passing them along.
|
300
342
|
*/
|
301
343
|
static VALUE
|
302
|
-
bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
|
344
|
+
bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler, VALUE args)
|
303
345
|
{
|
304
346
|
FilePathValue(path_v);
|
305
347
|
|
@@ -315,11 +357,37 @@ bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
|
|
315
357
|
char cache_path[MAX_CACHEPATH_SIZE];
|
316
358
|
|
317
359
|
/* generate cache path to cache_path */
|
318
|
-
bs_cache_path(cachedir,
|
360
|
+
bs_cache_path(cachedir, path_v, &cache_path);
|
319
361
|
|
320
|
-
return bs_fetch(path, path_v, cache_path, handler);
|
362
|
+
return bs_fetch(path, path_v, cache_path, handler, args);
|
321
363
|
}
|
322
364
|
|
365
|
+
/*
|
366
|
+
* Entrypoint for Bootsnap::CompileCache::Native.precompile.
|
367
|
+
* Similar to fetch, but it only generate the cache if missing
|
368
|
+
* and doesn't return the content.
|
369
|
+
*/
|
370
|
+
static VALUE
|
371
|
+
bs_rb_precompile(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler)
|
372
|
+
{
|
373
|
+
FilePathValue(path_v);
|
374
|
+
|
375
|
+
Check_Type(cachedir_v, T_STRING);
|
376
|
+
Check_Type(path_v, T_STRING);
|
377
|
+
|
378
|
+
if (RSTRING_LEN(cachedir_v) > MAX_CACHEDIR_SIZE) {
|
379
|
+
rb_raise(rb_eArgError, "cachedir too long");
|
380
|
+
}
|
381
|
+
|
382
|
+
char * cachedir = RSTRING_PTR(cachedir_v);
|
383
|
+
char * path = RSTRING_PTR(path_v);
|
384
|
+
char cache_path[MAX_CACHEPATH_SIZE];
|
385
|
+
|
386
|
+
/* generate cache path to cache_path */
|
387
|
+
bs_cache_path(cachedir, path_v, &cache_path);
|
388
|
+
|
389
|
+
return bs_precompile(path, path_v, cache_path, handler);
|
390
|
+
}
|
323
391
|
/*
|
324
392
|
* Open the file we want to load/cache and generate a cache key for it if it
|
325
393
|
* was loaded.
|
@@ -356,7 +424,8 @@ open_current_file(char * path, struct bs_cache_key * key, const char ** errno_pr
|
|
356
424
|
}
|
357
425
|
|
358
426
|
#define ERROR_WITH_ERRNO -1
|
359
|
-
#define
|
427
|
+
#define CACHE_MISS -2
|
428
|
+
#define CACHE_STALE -3
|
360
429
|
|
361
430
|
/*
|
362
431
|
* Read the cache key from the given fd, which must have position 0 (e.g.
|
@@ -364,15 +433,16 @@ open_current_file(char * path, struct bs_cache_key * key, const char ** errno_pr
|
|
364
433
|
*
|
365
434
|
* Possible return values:
|
366
435
|
* - 0 (OK, key was loaded)
|
367
|
-
* - CACHE_MISSING_OR_INVALID (-2)
|
368
436
|
* - ERROR_WITH_ERRNO (-1, errno is set)
|
437
|
+
* - CACHE_MISS (-2)
|
438
|
+
* - CACHE_STALE (-3)
|
369
439
|
*/
|
370
440
|
static int
|
371
441
|
bs_read_key(int fd, struct bs_cache_key * key)
|
372
442
|
{
|
373
443
|
ssize_t nread = read(fd, key, KEY_SIZE);
|
374
444
|
if (nread < 0) return ERROR_WITH_ERRNO;
|
375
|
-
if (nread < KEY_SIZE) return
|
445
|
+
if (nread < KEY_SIZE) return CACHE_STALE;
|
376
446
|
return 0;
|
377
447
|
}
|
378
448
|
|
@@ -382,7 +452,8 @@ bs_read_key(int fd, struct bs_cache_key * key)
|
|
382
452
|
*
|
383
453
|
* Possible return values:
|
384
454
|
* - 0 (OK, key was loaded)
|
385
|
-
* -
|
455
|
+
* - CACHE_MISS (-2)
|
456
|
+
* - CACHE_STALE (-3)
|
386
457
|
* - ERROR_WITH_ERRNO (-1, errno is set)
|
387
458
|
*/
|
388
459
|
static int
|
@@ -393,7 +464,7 @@ open_cache_file(const char * path, struct bs_cache_key * key, const char ** errn
|
|
393
464
|
fd = open(path, O_RDONLY);
|
394
465
|
if (fd < 0) {
|
395
466
|
*errno_provenance = "bs_fetch:open_cache_file:open";
|
396
|
-
if (errno == ENOENT) return
|
467
|
+
if (errno == ENOENT) return CACHE_MISS;
|
397
468
|
return ERROR_WITH_ERRNO;
|
398
469
|
}
|
399
470
|
#ifdef _WIN32
|
@@ -426,7 +497,7 @@ open_cache_file(const char * path, struct bs_cache_key * key, const char ** errn
|
|
426
497
|
* or exception, will be the final data returnable to the user.
|
427
498
|
*/
|
428
499
|
static int
|
429
|
-
fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag, const char ** errno_provenance)
|
500
|
+
fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE args, VALUE * output_data, int * exception_tag, const char ** errno_provenance)
|
430
501
|
{
|
431
502
|
char * data = NULL;
|
432
503
|
ssize_t nread;
|
@@ -448,13 +519,13 @@ fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data,
|
|
448
519
|
goto done;
|
449
520
|
}
|
450
521
|
if (nread != data_size) {
|
451
|
-
ret =
|
522
|
+
ret = CACHE_STALE;
|
452
523
|
goto done;
|
453
524
|
}
|
454
525
|
|
455
|
-
storage_data =
|
526
|
+
storage_data = rb_str_new(data, data_size);
|
456
527
|
|
457
|
-
*exception_tag = bs_storage_to_output(handler, storage_data, output_data);
|
528
|
+
*exception_tag = bs_storage_to_output(handler, args, storage_data, output_data);
|
458
529
|
ret = 0;
|
459
530
|
done:
|
460
531
|
if (data != NULL) xfree(data);
|
@@ -499,25 +570,32 @@ atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, cons
|
|
499
570
|
{
|
500
571
|
char template[MAX_CACHEPATH_SIZE + 20];
|
501
572
|
char * tmp_path;
|
502
|
-
int fd, ret;
|
573
|
+
int fd, ret, attempt;
|
503
574
|
ssize_t nwrite;
|
504
575
|
|
505
|
-
|
506
|
-
|
576
|
+
for (attempt = 0; attempt < MAX_CREATE_TEMPFILE_ATTEMPT; ++attempt) {
|
577
|
+
tmp_path = strncpy(template, path, MAX_CACHEPATH_SIZE);
|
578
|
+
strcat(tmp_path, ".tmp.XXXXXX");
|
507
579
|
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
580
|
+
// mkstemp modifies the template to be the actual created path
|
581
|
+
fd = mkstemp(tmp_path);
|
582
|
+
if (fd > 0) break;
|
583
|
+
|
584
|
+
if (attempt == 0 && mkpath(tmp_path, 0775) < 0) {
|
512
585
|
*errno_provenance = "bs_fetch:atomic_write_cache_file:mkpath";
|
513
586
|
return -1;
|
514
587
|
}
|
515
|
-
fd = open(tmp_path, O_WRONLY | O_CREAT, 0664);
|
516
|
-
if (fd < 0) {
|
517
|
-
*errno_provenance = "bs_fetch:atomic_write_cache_file:open";
|
518
|
-
return -1;
|
519
|
-
}
|
520
588
|
}
|
589
|
+
if (fd < 0) {
|
590
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:mkstemp";
|
591
|
+
return -1;
|
592
|
+
}
|
593
|
+
|
594
|
+
if (chmod(tmp_path, 0644) < 0) {
|
595
|
+
*errno_provenance = "bs_fetch:atomic_write_cache_file:chmod";
|
596
|
+
return -1;
|
597
|
+
}
|
598
|
+
|
521
599
|
#ifdef _WIN32
|
522
600
|
setmode(fd, O_BINARY);
|
523
601
|
#endif
|
@@ -615,7 +693,7 @@ bs_read_contents(int fd, size_t size, char ** contents, const char ** errno_prov
|
|
615
693
|
* - Return storage_to_output(storage_data)
|
616
694
|
*/
|
617
695
|
static VALUE
|
618
|
-
bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
696
|
+
bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler, VALUE args)
|
619
697
|
{
|
620
698
|
struct bs_cache_key cached_key, current_key;
|
621
699
|
char * contents = NULL;
|
@@ -635,26 +713,34 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
635
713
|
|
636
714
|
/* Open the cache key if it exists, and read its cache key in */
|
637
715
|
cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance);
|
638
|
-
if (cache_fd ==
|
716
|
+
if (cache_fd == CACHE_MISS || cache_fd == CACHE_STALE) {
|
639
717
|
/* This is ok: valid_cache remains false, we re-populate it. */
|
718
|
+
if (RB_UNLIKELY(instrumentation_enabled)) {
|
719
|
+
rb_funcall(rb_mBootsnap, instrumentation_method, 2, cache_fd == CACHE_MISS ? sym_miss : sym_stale, path_v);
|
720
|
+
}
|
640
721
|
} else if (cache_fd < 0) {
|
641
722
|
goto fail_errno;
|
642
723
|
} else {
|
643
724
|
/* True if the cache existed and no invalidating changes have occurred since
|
644
725
|
* it was generated. */
|
645
726
|
valid_cache = cache_key_equal(¤t_key, &cached_key);
|
727
|
+
if (RB_UNLIKELY(instrumentation_enabled)) {
|
728
|
+
if (!valid_cache) {
|
729
|
+
rb_funcall(rb_mBootsnap, instrumentation_method, 2, sym_stale, path_v);
|
730
|
+
}
|
731
|
+
}
|
646
732
|
}
|
647
733
|
|
648
734
|
if (valid_cache) {
|
649
735
|
/* Fetch the cache data and return it if we're able to load it successfully */
|
650
736
|
res = fetch_cached_data(
|
651
|
-
cache_fd, (ssize_t)cached_key.data_size, handler,
|
737
|
+
cache_fd, (ssize_t)cached_key.data_size, handler, args,
|
652
738
|
&output_data, &exception_tag, &errno_provenance
|
653
739
|
);
|
654
|
-
if (exception_tag != 0)
|
655
|
-
else if (res ==
|
656
|
-
else if (res == ERROR_WITH_ERRNO)
|
657
|
-
else if (!NIL_P(output_data))
|
740
|
+
if (exception_tag != 0) goto raise;
|
741
|
+
else if (res == CACHE_MISS || res == CACHE_STALE) valid_cache = 0;
|
742
|
+
else if (res == ERROR_WITH_ERRNO) goto fail_errno;
|
743
|
+
else if (!NIL_P(output_data)) goto succeed; /* fast-path, goal */
|
658
744
|
}
|
659
745
|
close(cache_fd);
|
660
746
|
cache_fd = -1;
|
@@ -662,15 +748,15 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
662
748
|
|
663
749
|
/* Read the contents of the source file into a buffer */
|
664
750
|
if (bs_read_contents(current_fd, current_key.size, &contents, &errno_provenance) < 0) goto fail_errno;
|
665
|
-
input_data =
|
751
|
+
input_data = rb_str_new(contents, current_key.size);
|
666
752
|
|
667
753
|
/* Try to compile the input_data using input_to_storage(input_data) */
|
668
|
-
exception_tag = bs_input_to_storage(handler, input_data, path_v, &storage_data);
|
754
|
+
exception_tag = bs_input_to_storage(handler, args, input_data, path_v, &storage_data);
|
669
755
|
if (exception_tag != 0) goto raise;
|
670
756
|
/* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
|
671
757
|
* to cache anything; just return input_to_output(input_data) */
|
672
758
|
if (storage_data == uncompilable) {
|
673
|
-
bs_input_to_output(handler, input_data, &output_data, &exception_tag);
|
759
|
+
bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
|
674
760
|
if (exception_tag != 0) goto raise;
|
675
761
|
goto succeed;
|
676
762
|
}
|
@@ -682,7 +768,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
682
768
|
if (res < 0) goto fail_errno;
|
683
769
|
|
684
770
|
/* Having written the cache, now convert storage_data to output_data */
|
685
|
-
exception_tag = bs_storage_to_output(handler, storage_data, &output_data);
|
771
|
+
exception_tag = bs_storage_to_output(handler, args, storage_data, &output_data);
|
686
772
|
if (exception_tag != 0) goto raise;
|
687
773
|
|
688
774
|
/* If output_data is nil, delete the cache entry and generate the output
|
@@ -692,7 +778,7 @@ bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
|
692
778
|
errno_provenance = "bs_fetch:unlink";
|
693
779
|
goto fail_errno;
|
694
780
|
}
|
695
|
-
bs_input_to_output(handler, input_data, &output_data, &exception_tag);
|
781
|
+
bs_input_to_output(handler, args, input_data, &output_data, &exception_tag);
|
696
782
|
if (exception_tag != 0) goto raise;
|
697
783
|
}
|
698
784
|
|
@@ -723,6 +809,79 @@ invalid_type_storage_data:
|
|
723
809
|
#undef CLEANUP
|
724
810
|
}
|
725
811
|
|
812
|
+
static VALUE
|
813
|
+
bs_precompile(char * path, VALUE path_v, char * cache_path, VALUE handler)
|
814
|
+
{
|
815
|
+
struct bs_cache_key cached_key, current_key;
|
816
|
+
char * contents = NULL;
|
817
|
+
int cache_fd = -1, current_fd = -1;
|
818
|
+
int res, valid_cache = 0, exception_tag = 0;
|
819
|
+
const char * errno_provenance = NULL;
|
820
|
+
|
821
|
+
VALUE input_data; /* data read from source file, e.g. YAML or ruby source */
|
822
|
+
VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */
|
823
|
+
|
824
|
+
/* Open the source file and generate a cache key for it */
|
825
|
+
current_fd = open_current_file(path, ¤t_key, &errno_provenance);
|
826
|
+
if (current_fd < 0) goto fail;
|
827
|
+
|
828
|
+
/* Open the cache key if it exists, and read its cache key in */
|
829
|
+
cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance);
|
830
|
+
if (cache_fd == CACHE_MISS || cache_fd == CACHE_STALE) {
|
831
|
+
/* This is ok: valid_cache remains false, we re-populate it. */
|
832
|
+
} else if (cache_fd < 0) {
|
833
|
+
goto fail;
|
834
|
+
} else {
|
835
|
+
/* True if the cache existed and no invalidating changes have occurred since
|
836
|
+
* it was generated. */
|
837
|
+
valid_cache = cache_key_equal(¤t_key, &cached_key);
|
838
|
+
}
|
839
|
+
|
840
|
+
if (valid_cache) {
|
841
|
+
goto succeed;
|
842
|
+
}
|
843
|
+
|
844
|
+
close(cache_fd);
|
845
|
+
cache_fd = -1;
|
846
|
+
/* Cache is stale, invalid, or missing. Regenerate and write it out. */
|
847
|
+
|
848
|
+
/* Read the contents of the source file into a buffer */
|
849
|
+
if (bs_read_contents(current_fd, current_key.size, &contents, &errno_provenance) < 0) goto fail;
|
850
|
+
input_data = rb_str_new(contents, current_key.size);
|
851
|
+
|
852
|
+
/* Try to compile the input_data using input_to_storage(input_data) */
|
853
|
+
exception_tag = bs_input_to_storage(handler, Qnil, input_data, path_v, &storage_data);
|
854
|
+
if (exception_tag != 0) goto fail;
|
855
|
+
|
856
|
+
/* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try
|
857
|
+
* to cache anything; just return false */
|
858
|
+
if (storage_data == uncompilable) {
|
859
|
+
goto fail;
|
860
|
+
}
|
861
|
+
/* If storage_data isn't a string, we can't cache it */
|
862
|
+
if (!RB_TYPE_P(storage_data, T_STRING)) goto fail;
|
863
|
+
|
864
|
+
/* Write the cache key and storage_data to the cache directory */
|
865
|
+
res = atomic_write_cache_file(cache_path, ¤t_key, storage_data, &errno_provenance);
|
866
|
+
if (res < 0) goto fail;
|
867
|
+
|
868
|
+
goto succeed;
|
869
|
+
|
870
|
+
#define CLEANUP \
|
871
|
+
if (contents != NULL) xfree(contents); \
|
872
|
+
if (current_fd >= 0) close(current_fd); \
|
873
|
+
if (cache_fd >= 0) close(cache_fd);
|
874
|
+
|
875
|
+
succeed:
|
876
|
+
CLEANUP;
|
877
|
+
return Qtrue;
|
878
|
+
fail:
|
879
|
+
CLEANUP;
|
880
|
+
return Qfalse;
|
881
|
+
#undef CLEANUP
|
882
|
+
}
|
883
|
+
|
884
|
+
|
726
885
|
/*****************************************************************************/
|
727
886
|
/********************* Handler Wrappers **************************************/
|
728
887
|
/*****************************************************************************
|
@@ -742,11 +901,13 @@ invalid_type_storage_data:
|
|
742
901
|
|
743
902
|
struct s2o_data {
|
744
903
|
VALUE handler;
|
904
|
+
VALUE args;
|
745
905
|
VALUE storage_data;
|
746
906
|
};
|
747
907
|
|
748
908
|
struct i2o_data {
|
749
909
|
VALUE handler;
|
910
|
+
VALUE args;
|
750
911
|
VALUE input_data;
|
751
912
|
};
|
752
913
|
|
@@ -760,15 +921,16 @@ static VALUE
|
|
760
921
|
prot_storage_to_output(VALUE arg)
|
761
922
|
{
|
762
923
|
struct s2o_data * data = (struct s2o_data *)arg;
|
763
|
-
return rb_funcall(data->handler, rb_intern("storage_to_output"),
|
924
|
+
return rb_funcall(data->handler, rb_intern("storage_to_output"), 2, data->storage_data, data->args);
|
764
925
|
}
|
765
926
|
|
766
927
|
static int
|
767
|
-
bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data)
|
928
|
+
bs_storage_to_output(VALUE handler, VALUE args, VALUE storage_data, VALUE * output_data)
|
768
929
|
{
|
769
930
|
int state;
|
770
931
|
struct s2o_data s2o_data = {
|
771
932
|
.handler = handler,
|
933
|
+
.args = args,
|
772
934
|
.storage_data = storage_data,
|
773
935
|
};
|
774
936
|
*output_data = rb_protect(prot_storage_to_output, (VALUE)&s2o_data, &state);
|
@@ -776,10 +938,11 @@ bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data)
|
|
776
938
|
}
|
777
939
|
|
778
940
|
static void
|
779
|
-
bs_input_to_output(VALUE handler, VALUE input_data, VALUE * output_data, int * exception_tag)
|
941
|
+
bs_input_to_output(VALUE handler, VALUE args, VALUE input_data, VALUE * output_data, int * exception_tag)
|
780
942
|
{
|
781
943
|
struct i2o_data i2o_data = {
|
782
944
|
.handler = handler,
|
945
|
+
.args = args,
|
783
946
|
.input_data = input_data,
|
784
947
|
};
|
785
948
|
*output_data = rb_protect(prot_input_to_output, (VALUE)&i2o_data, exception_tag);
|
@@ -789,7 +952,7 @@ static VALUE
|
|
789
952
|
prot_input_to_output(VALUE arg)
|
790
953
|
{
|
791
954
|
struct i2o_data * data = (struct i2o_data *)arg;
|
792
|
-
return rb_funcall(data->handler, rb_intern("input_to_output"),
|
955
|
+
return rb_funcall(data->handler, rb_intern("input_to_output"), 2, data->input_data, data->args);
|
793
956
|
}
|
794
957
|
|
795
958
|
static VALUE
|
@@ -800,7 +963,7 @@ try_input_to_storage(VALUE arg)
|
|
800
963
|
}
|
801
964
|
|
802
965
|
static VALUE
|
803
|
-
rescue_input_to_storage(VALUE arg)
|
966
|
+
rescue_input_to_storage(VALUE arg, VALUE e)
|
804
967
|
{
|
805
968
|
return uncompilable;
|
806
969
|
}
|
@@ -816,7 +979,7 @@ prot_input_to_storage(VALUE arg)
|
|
816
979
|
}
|
817
980
|
|
818
981
|
static int
|
819
|
-
bs_input_to_storage(VALUE handler, VALUE input_data, VALUE pathval, VALUE * storage_data)
|
982
|
+
bs_input_to_storage(VALUE handler, VALUE args, VALUE input_data, VALUE pathval, VALUE * storage_data)
|
820
983
|
{
|
821
984
|
int state;
|
822
985
|
struct i2s_data i2s_data = {
|