ox 2.14.6 → 2.14.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8cef7183a7bea9cd7baba3313afd0e008861d159ae8d99ef229dca9cb34ba1fd
4
- data.tar.gz: 4929590f1b84ac3e5350c2657a3374eb49a2a258668bbf13d2ee5624110402c5
3
+ metadata.gz: d34e2f569a6f870bc46c1ed8bf2e39e860f63ee8da1ad8aec0897e11904d8444
4
+ data.tar.gz: 90049302c1ba8e443a28aa1e69115db636ae86517695c6aab278fc8f7dcc8590
5
5
  SHA512:
6
- metadata.gz: fb7fb21b9be8f9e046b22e3dde36bb2469256daab163e17e082c9c15865926e21d2793052f544ebadaa958fb6589d30e9a5b7c6fed018f1fa6e32782271a2e8c
7
- data.tar.gz: f259be386f39855faf5f871119cd60c16ee6c23872baf2239754f17512355fd19c2dbfa8c254746e1f44b0819f32b3e63700a5bfe3f6f0ff831c5f9c107f9df1
6
+ metadata.gz: 2f5f3e9ef3f7172a7c25b43b00a92c57858e052e592ec7265077dabf1ab8280888953639232ca06e088e4524287e4f264ac3ddfe017dfde9d5c5b8dc5bd847a8
7
+ data.tar.gz: 35ebe783817b228b82eaacfcbdc7805de7327ad77fcf34f605baf7fab53103b79f5bd893c13b9a1a8f74f2e529b241ae805a21f487993db229e03b817f78aabf
data/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  All changes to the Ox gem are documented here. Releases follow semantic versioning.
4
4
 
5
+ ## [2.14.7] - 2022-02-03
6
+
7
+ ### Fixed
8
+
9
+ - All classes and symbols are now registered to avoid issues with GC compaction movement.
10
+ - Parsing of any size processing instruction is now allowed. There is no 1024 limit.
11
+ - Fixed the `\r` replacement with `\n` according to https://www.w3.org/TR/2008/REC-xml-20081126/#sec-line-ends.
12
+
13
+ ### Changed
14
+
15
+ - Symbol and string caching changed but should have no impact on use
16
+ other than being slightly faster and handles large numbers of cached
17
+ items more efficiently.
18
+
5
19
  ## [2.14.6] - 2021-11-03
6
20
 
7
21
  ### Fixed
data/ext/ox/builder.c CHANGED
@@ -9,9 +9,7 @@
9
9
  #include <string.h>
10
10
 
11
11
  #include "ruby.h"
12
- #if HAVE_RB_ENC_ASSOCIATE
13
12
  #include "ruby/encoding.h"
14
- #endif
15
13
  #include "ox.h"
16
14
  #include "buf.h"
17
15
  #include "err.h"
@@ -335,9 +333,7 @@ to_s(Builder b) {
335
333
  rstr = rb_str_new(b->buf.head, buf_len(&b->buf));
336
334
 
337
335
  if ('\0' != *b->encoding) {
338
- #if HAVE_RB_ENC_ASSOCIATE
339
336
  rb_enc_associate(rstr, rb_enc_find(b->encoding));
340
- #endif
341
337
  }
342
338
  return rstr;
343
339
  }
data/ext/ox/cache.c CHANGED
@@ -1,160 +1,349 @@
1
- /* cache.c
2
- * Copyright (c) 2011, Peter Ohler
3
- * All rights reserved.
4
- */
1
+ // Copyright (c) 2011, 2021 Peter Ohler. All rights reserved.
2
+ // Licensed under the MIT License. See LICENSE file in the project root for license details.
5
3
 
4
+ #if HAVE_PTHREAD_MUTEX_INIT
5
+ #include <pthread.h>
6
+ #endif
6
7
  #include <stdlib.h>
7
- #include <errno.h>
8
- #include <stdio.h>
9
- #include <string.h>
10
- #include <strings.h>
11
- #include <stdarg.h>
12
- #include <stdint.h>
13
8
 
14
9
  #include "cache.h"
15
10
 
16
- struct _cache {
17
- /* The key is a length byte followed by the key as a string. If the key is longer than 254 characters then the
18
- length is 255. The key can be for a premature value and in that case the length byte is greater than the length
19
- of the key. */
20
- char *key;
21
- VALUE value;
22
- struct _cache *slots[16];
23
- };
11
+ // The stdlib calloc, realloc, and free are used instead of the Ruby ALLOC,
12
+ // ALLOC_N, REALLOC, and xfree since the later could trigger a GC which will
13
+ // either corrupt memory or if the mark function locks will deadlock.
24
14
 
25
- static void slot_print(Cache cache, unsigned int depth);
15
+ #define REHASH_LIMIT 4
16
+ #define MIN_SHIFT 8
17
+ #define REUSE_MAX 8192
26
18
 
27
- static char* form_key(const char *s) {
28
- size_t len = strlen(s);
29
- char *d = ALLOC_N(char, len + 2);
19
+ #if HAVE_PTHREAD_MUTEX_INIT
20
+ #define CACHE_LOCK(c) pthread_mutex_lock(&((c)->mutex))
21
+ #define CACHE_UNLOCK(c) pthread_mutex_unlock(&((c)->mutex))
22
+ #else
23
+ #define CACHE_LOCK(c) rb_mutex_lock((c)->mutex)
24
+ #define CACHE_UNLOCK(c) rb_mutex_unlock((c)->mutex)
25
+ #endif
30
26
 
31
- *(uint8_t*)d = (255 <= len) ? 255 : len;
32
- memcpy(d + 1, s, len + 1);
27
+ // almost the Murmur hash algorithm
28
+ #define M 0x5bd1e995
33
29
 
34
- return d;
30
+ typedef struct _slot {
31
+ struct _slot * next;
32
+ VALUE val;
33
+ uint64_t hash;
34
+ volatile uint32_t use_cnt;
35
+ uint8_t klen;
36
+ char key[CACHE_MAX_KEY];
37
+ } * Slot;
38
+
39
+ typedef struct _cache {
40
+ volatile Slot * slots;
41
+ volatile size_t cnt;
42
+ VALUE (*form)(const char *str, size_t len);
43
+ uint64_t size;
44
+ uint64_t mask;
45
+ VALUE (*intern)(struct _cache *c, const char *key, size_t len, const char **keyp);
46
+ volatile Slot reuse;
47
+ size_t rcnt;
48
+ #if HAVE_PTHREAD_MUTEX_INIT
49
+ pthread_mutex_t mutex;
50
+ #else
51
+ VALUE mutex;
52
+ #endif
53
+ uint8_t xrate;
54
+ bool mark;
55
+ } * Cache;
56
+
57
+ void cache_set_form(Cache c, VALUE (*form)(const char *str, size_t len)) {
58
+ c->form = form;
35
59
  }
36
60
 
37
- void
38
- ox_cache_new(Cache *cache) {
39
- *cache = ALLOC(struct _cache);
40
- (*cache)->key = 0;
41
- (*cache)->value = Qundef;
42
- memset((*cache)->slots, 0, sizeof((*cache)->slots));
61
+ static uint64_t hash_calc(const uint8_t *key, size_t len) {
62
+ const uint8_t *end = key + len;
63
+ const uint8_t *endless = key + (len & 0xFFFFFFFC);
64
+ uint64_t h = (uint64_t)len;
65
+ uint64_t k;
66
+
67
+ while (key < endless) {
68
+ k = (uint64_t)*key++;
69
+ k |= (uint64_t)*key++ << 8;
70
+ k |= (uint64_t)*key++ << 16;
71
+ k |= (uint64_t)*key++ << 24;
72
+
73
+ k *= M;
74
+ k ^= k >> 24;
75
+ h *= M;
76
+ h ^= k * M;
77
+ }
78
+ if (1 < end - key) {
79
+ uint16_t k16 = (uint16_t)*key++;
80
+
81
+ k16 |= (uint16_t)*key++ << 8;
82
+ h ^= k16 << 8;
83
+ }
84
+ if (key < end) {
85
+ h ^= *key;
86
+ }
87
+ h *= M;
88
+ h ^= h >> 13;
89
+ h *= M;
90
+ h ^= h >> 15;
91
+
92
+ return h;
43
93
  }
44
94
 
45
- VALUE
46
- ox_cache_get(Cache cache, const char *key, VALUE **slot, const char **keyp) {
47
- unsigned char *k = (unsigned char*)key;
48
- Cache *cp;
49
-
50
- for (; '\0' != *k; k++) {
51
- cp = cache->slots + (unsigned int)(*k >> 4); /* upper 4 bits */
52
- if (0 == *cp) {
53
- ox_cache_new(cp);
95
+ static void rehash(Cache c) {
96
+ uint64_t osize;
97
+ Slot * end;
98
+ Slot * sp;
99
+
100
+ osize = c->size;
101
+ c->size = osize * 4;
102
+ c->mask = c->size - 1;
103
+ c->slots = realloc((void *)c->slots, sizeof(Slot) * c->size);
104
+ memset((Slot *)c->slots + osize, 0, sizeof(Slot) * osize * 3);
105
+ end = (Slot *)c->slots + osize;
106
+ for (sp = (Slot *)c->slots; sp < end; sp++) {
107
+ Slot s = *sp;
108
+ Slot next = NULL;
109
+
110
+ *sp = NULL;
111
+ for (; NULL != s; s = next) {
112
+ uint64_t h = s->hash & c->mask;
113
+ Slot * bucket = (Slot *)c->slots + h;
114
+
115
+ next = s->next;
116
+ s->next = *bucket;
117
+ *bucket = s;
118
+ }
119
+ }
120
+ }
121
+
122
+ static VALUE lockless_intern(Cache c, const char *key, size_t len, const char **keyp) {
123
+ uint64_t h = hash_calc((const uint8_t *)key, len);
124
+ Slot * bucket = (Slot *)c->slots + (h & c->mask);
125
+ Slot b;
126
+ volatile VALUE rkey;
127
+
128
+ while (REUSE_MAX < c->rcnt) {
129
+ if (NULL != (b = c->reuse)) {
130
+ c->reuse = b->next;
131
+ free(b);
132
+ c->rcnt--;
133
+ } else {
134
+ // An accounting error occured somewhere so correct it.
135
+ c->rcnt = 0;
136
+ }
137
+ }
138
+ for (b = *bucket; NULL != b; b = b->next) {
139
+ if ((uint8_t)len == b->klen && 0 == strncmp(b->key, key, len)) {
140
+ b->use_cnt += 16;
141
+ if (NULL != keyp) {
142
+ *keyp = b->key;
143
+ }
144
+ return b->val;
145
+ }
146
+ }
147
+ rkey = c->form(key, len);
148
+ if (NULL == (b = c->reuse)) {
149
+ b = calloc(1, sizeof(struct _slot));
150
+ } else {
151
+ c->reuse = b->next;
152
+ c->rcnt--;
153
+ }
154
+ b->hash = h;
155
+ memcpy(b->key, key, len);
156
+ b->klen = (uint8_t)len;
157
+ b->key[len] = '\0';
158
+ b->val = rkey;
159
+ b->use_cnt = 4;
160
+ b->next = *bucket;
161
+ *bucket = b;
162
+ c->cnt++; // Don't worry about wrapping. Worse case is the entry is removed and recreated.
163
+ if (NULL != keyp) {
164
+ *keyp = b->key;
165
+ }
166
+ if (REHASH_LIMIT < c->cnt / c->size) {
167
+ rehash(c);
168
+ }
169
+ return rkey;
170
+ }
171
+
172
+ static VALUE locking_intern(Cache c, const char *key, size_t len, const char **keyp) {
173
+ uint64_t h;
174
+ Slot * bucket;
175
+ Slot b;
176
+ uint64_t old_size;
177
+ volatile VALUE rkey;
178
+
179
+ CACHE_LOCK(c);
180
+ while (REUSE_MAX < c->rcnt) {
181
+ if (NULL != (b = c->reuse)) {
182
+ c->reuse = b->next;
183
+ free(b);
184
+ c->rcnt--;
185
+ } else {
186
+ // An accounting error occured somewhere so correct it.
187
+ c->rcnt = 0;
54
188
  }
55
- cache = *cp;
56
- cp = cache->slots + (unsigned int)(*k & 0x0F); /* lower 4 bits */
57
- if (0 == *cp) { /* nothing on this tree so set key and value as a premature key/value pair */
58
- ox_cache_new(cp);
59
- cache = *cp;
60
- cache->key = form_key(key);
61
- break;
62
- } else {
63
- int depth = (int)(k - (unsigned char*)key + 1);
64
-
65
- cache = *cp;
66
-
67
- if ('\0' == *(k + 1)) { /* exact match */
68
- if (0 == cache->key) { /* nothing in this spot so take it */
69
- cache->key = form_key(key);
70
- break;
71
- } else if ((depth == *cache->key || 255 < depth) && 0 == strcmp(key, cache->key + 1)) { /* match */
72
- break;
73
- } else { /* have to move the current premature key/value deeper */
74
- unsigned char *ck = (unsigned char*)(cache->key + depth + 1);
75
- Cache orig = *cp;
76
-
77
- cp = (*cp)->slots + (*ck >> 4);
78
- ox_cache_new(cp);
79
- cp = (*cp)->slots + (*ck & 0x0F);
80
- ox_cache_new(cp);
81
- (*cp)->key = cache->key;
82
- (*cp)->value = cache->value;
83
- orig->key = form_key(key);
84
- orig->value = Qundef;
85
- }
86
- } else { /* not exact match but on the path */
87
- if (0 != cache->key) { /* there is a key/value here already */
88
- if (depth == *cache->key || (255 <= depth && 0 == strncmp(cache->key, key, depth) && '\0' == cache->key[depth])) { /* key belongs here */
89
- continue;
90
- } else {
91
- unsigned char *ck = (unsigned char*)(cache->key + depth + 1);
92
- Cache orig = *cp;
93
-
94
- cp = (*cp)->slots + (*ck >> 4);
95
- ox_cache_new(cp);
96
- cp = (*cp)->slots + (*ck & 0x0F);
97
- ox_cache_new(cp);
98
- (*cp)->key = cache->key;
99
- (*cp)->value = cache->value;
100
- orig->key = 0;
101
- orig->value = Qundef;
102
- }
103
- }
104
- }
189
+ }
190
+ h = hash_calc((const uint8_t *)key, len);
191
+ bucket = (Slot *)c->slots + (h & c->mask);
192
+ for (b = *bucket; NULL != b; b = b->next) {
193
+ if ((uint8_t)len == b->klen && 0 == strncmp(b->key, key, len)) {
194
+ b->use_cnt += 4;
195
+ if (NULL != keyp) {
196
+ *keyp = b->key;
197
+ }
198
+ CACHE_UNLOCK(c);
199
+
200
+ return b->val;
105
201
  }
106
202
  }
107
- *slot = &cache->value;
108
- if (0 != keyp) {
109
- if (0 == cache->key) {
110
- printf("*** Error: failed to set the key for '%s'\n", key);
111
- *keyp = 0;
112
- } else {
113
- *keyp = cache->key + 1;
114
- }
203
+ old_size = c->size;
204
+ // The creation of a new value may trigger a GC which be a problem if the
205
+ // cache is locked so make sure it is unlocked for the key value creation.
206
+ if (NULL != (b = c->reuse)) {
207
+ c->reuse = b->next;
208
+ c->rcnt--;
209
+ }
210
+ CACHE_UNLOCK(c);
211
+ if (NULL == b) {
212
+ b = calloc(1, sizeof(struct _slot));
213
+ }
214
+ rkey = c->form(key, len);
215
+ b->hash = h;
216
+ memcpy(b->key, key, len);
217
+ b->klen = (uint8_t)len;
218
+ b->key[len] = '\0';
219
+ b->val = rkey;
220
+ b->use_cnt = 16;
221
+
222
+ // Lock again to add the new entry.
223
+ CACHE_LOCK(c);
224
+ if (old_size != c->size) {
225
+ h = hash_calc((const uint8_t *)key, len);
226
+ bucket = (Slot *)c->slots + (h & c->mask);
227
+ }
228
+ b->next = *bucket;
229
+ *bucket = b;
230
+ c->cnt++; // Don't worry about wrapping. Worse case is the entry is removed and recreated.
231
+ if (NULL != keyp) {
232
+ *keyp = b->key;
233
+ }
234
+ if (REHASH_LIMIT < c->cnt / c->size) {
235
+ rehash(c);
236
+ }
237
+ CACHE_UNLOCK(c);
238
+
239
+ return rkey;
240
+ }
241
+
242
+ Cache cache_create(size_t size,
243
+ VALUE (*form)(const char *str, size_t len),
244
+ bool mark,
245
+ bool locking) {
246
+ Cache c = calloc(1, sizeof(struct _cache));
247
+ int shift = 0;
248
+
249
+ for (; REHASH_LIMIT < size; size /= 2, shift++) {
115
250
  }
116
- return cache->value;
251
+ if (shift < MIN_SHIFT) {
252
+ shift = MIN_SHIFT;
253
+ }
254
+ #if HAVE_PTHREAD_MUTEX_INIT
255
+ pthread_mutex_init(&c->mutex, NULL);
256
+ #else
257
+ c->mutex = rb_mutex_new();
258
+ #endif
259
+ c->size = 1 << shift;
260
+ c->mask = c->size - 1;
261
+ c->slots = calloc(c->size, sizeof(Slot));
262
+ c->form = form;
263
+ c->xrate = 1; // low
264
+ c->mark = mark;
265
+ if (locking) {
266
+ c->intern = locking_intern;
267
+ } else {
268
+ c->intern = lockless_intern;
269
+ }
270
+ return c;
271
+ }
272
+
273
+ void cache_set_expunge_rate(Cache c, int rate) {
274
+ c->xrate = (uint8_t)rate;
117
275
  }
118
276
 
119
- void
120
- ox_cache_print(Cache cache) {
121
- /*printf("-------------------------------------------\n");*/
122
- slot_print(cache, 0);
277
+ void cache_free(Cache c) {
278
+ uint64_t i;
279
+
280
+ for (i = 0; i < c->size; i++) {
281
+ Slot next;
282
+ Slot s;
283
+
284
+ for (s = c->slots[i]; NULL != s; s = next) {
285
+ next = s->next;
286
+ free(s);
287
+ }
288
+ }
289
+ free((void *)c->slots);
290
+ free(c);
123
291
  }
124
292
 
125
- static void
126
- slot_print(Cache c, unsigned int depth) {
127
- char indent[256];
128
- Cache *cp;
129
- unsigned int i;
293
+ void cache_mark(Cache c) {
294
+ uint64_t i;
130
295
 
131
- if (sizeof(indent) - 1 < depth) {
132
- depth = ((int)sizeof(indent) - 1);
296
+ #if !HAVE_PTHREAD_MUTEX_INIT
297
+ rb_gc_mark(c->mutex);
298
+ #endif
299
+ if (0 == c->cnt) {
300
+ return;
133
301
  }
134
- memset(indent, ' ', depth);
135
- indent[depth] = '\0';
136
- for (i = 0, cp = c->slots; i < 16; i++, cp++) {
137
- if (0 == *cp) {
138
- /*printf("%s%02u:\n", indent, i);*/
139
- } else {
140
- if (0 == (*cp)->key && Qundef == (*cp)->value) {
141
- printf("%s%02u:\n", indent, i);
142
- } else {
143
- const char *vs;
144
- const char *clas;
145
-
146
- if (Qundef == (*cp)->value) {
147
- vs = "undefined";
148
- clas = "";
149
- } else {
150
- VALUE rs = rb_funcall2((*cp)->value, rb_intern("to_s"), 0, 0);
302
+ for (i = 0; i < c->size; i++) {
303
+ Slot s;
304
+ Slot prev = NULL;
305
+ Slot next;
151
306
 
152
- vs = StringValuePtr(rs);
153
- clas = rb_class2name(rb_obj_class((*cp)->value));
307
+ for (s = c->slots[i]; NULL != s; s = next) {
308
+ next = s->next;
309
+ if (0 == s->use_cnt) {
310
+ if (NULL == prev) {
311
+ c->slots[i] = next;
312
+ } else {
313
+ prev->next = next;
154
314
  }
155
- printf("%s%02u: %s = %s (%s)\n", indent, i, (*cp)->key, vs, clas);
315
+ c->cnt--;
316
+ s->next = c->reuse;
317
+ c->reuse = s;
318
+ c->rcnt++;
319
+ continue;
320
+ }
321
+ switch (c->xrate) {
322
+ case 0: break;
323
+ case 2: s->use_cnt -= 2; break;
324
+ case 3: s->use_cnt /= 2; break;
325
+ default: s->use_cnt--; break;
326
+ }
327
+ if (c->mark) {
328
+ rb_gc_mark(s->val);
329
+ }
330
+ prev = s;
331
+ }
332
+ }
333
+ }
334
+
335
+ VALUE
336
+ cache_intern(Cache c, const char *key, size_t len, const char **keyp) {
337
+ if (CACHE_MAX_KEY <= len) {
338
+ if (NULL != keyp) {
339
+ volatile VALUE rkey = c->form(key, len);
340
+
341
+ if (SYMBOL_P(rkey)) {
342
+ *keyp = rb_id2name(rb_sym2id(rkey));
156
343
  }
157
- slot_print(*cp, depth + 2);
344
+ return rkey;
158
345
  }
346
+ return c->form(key, len);
159
347
  }
348
+ return c->intern(c, key, len, keyp);
160
349
  }
data/ext/ox/cache.h CHANGED
@@ -1,19 +1,21 @@
1
- /* cache.h
2
- * Copyright (c) 2011, Peter Ohler
3
- * All rights reserved.
4
- */
1
+ // Copyright (c) 2021 Peter Ohler. All rights reserved.
2
+ // Licensed under the MIT License. See LICENSE file in the project root for license details.
5
3
 
6
- #ifndef OX_CACHE_H
7
- #define OX_CACHE_H
4
+ #ifndef CACHE_H
5
+ #define CACHE_H
8
6
 
9
- #include "ruby.h"
7
+ #include <ruby.h>
8
+ #include <stdbool.h>
10
9
 
11
- typedef struct _cache *Cache;
10
+ #define CACHE_MAX_KEY 35
12
11
 
13
- extern void ox_cache_new(Cache *cache);
12
+ struct _cache;
14
13
 
15
- extern VALUE ox_cache_get(Cache cache, const char *key, VALUE **slot, const char **keyp);
14
+ extern struct _cache *cache_create(size_t size, VALUE (*form)(const char *str, size_t len), bool mark, bool locking);
15
+ extern void cache_free(struct _cache *c);
16
+ extern void cache_mark(struct _cache *c);
17
+ extern void cache_set_form(struct _cache *c, VALUE (*form)(const char *str, size_t len));
18
+ extern VALUE cache_intern(struct _cache *c, const char *key, size_t len, const char **keyp);
19
+ extern void cache_set_expunge_rate(struct _cache *c, int rate);
16
20
 
17
- extern void ox_cache_print(Cache cache);
18
-
19
- #endif /* OX_CACHE_H */
21
+ #endif /* CACHE_H */
data/ext/ox/dump.c CHANGED
@@ -511,7 +511,7 @@ dump_date(Out out, VALUE obj) {
511
511
  static void
512
512
  dump_time_xsd(Out out, VALUE obj) {
513
513
  struct tm *tm;
514
- #if HAS_RB_TIME_TIMESPEC
514
+ #if HAVE_RB_TIME_TIMESPEC
515
515
  struct timespec ts = rb_time_timespec(obj);
516
516
  time_t sec = ts.tv_sec;
517
517
  long nsec = ts.tv_nsec;
@@ -528,7 +528,7 @@ dump_time_xsd(Out out, VALUE obj) {
528
528
  }
529
529
  /* 2010-07-09T10:47:45.895826+09:00 */
530
530
  tm = localtime(&sec);
531
- #if HAS_TM_GMTOFF
531
+ #if HAVE_ST_TM_GMTOFF
532
532
  if (0 > tm->tm_gmtoff) {
533
533
  tzsign = '-';
534
534
  tzhour = (int)(tm->tm_gmtoff / -3600);
data/ext/ox/extconf.rb CHANGED
@@ -33,12 +33,14 @@ CONFIG['warnflags'].slice!(/ -Wdeclaration-after-statement/)
33
33
  CONFIG['warnflags'].slice!(/ -Wmissing-noreturn/)
34
34
 
35
35
  have_func('rb_time_timespec')
36
- have_func('rb_enc_associate')
37
- have_func('rb_enc_find')
38
36
  have_func('rb_struct_alloc_noinit')
39
37
  have_func('rb_obj_encoding')
40
38
  have_func('rb_ivar_foreach')
41
39
  have_func('rb_ext_ractor_safe', 'ruby.h')
40
+ have_func('pthread_mutex_init')
41
+ have_func('rb_enc_interned_str')
42
+ have_func('rb_time_nano_new')
43
+ have_func('index')
42
44
 
43
45
  have_header('ruby/st.h')
44
46
  have_header('sys/uio.h')