teptris 0.2.51 → 0.2.52
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/ext/teptris_ext/materialize.c +124 -1
- data/lib/teptris/toml.rb +46 -2
- data/lib/teptris/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2537428f82b791a55389c0be4bb2eaa2115f118238839705a913e51da11e6af1
|
|
4
|
+
data.tar.gz: a95d1d2b3f582803ef44c0bacbc1ec9172a9cf044c30cf56a488963f575c4007
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2a8d04664b9cb79842b466a7bc4e877e167ff61aaa58258c2e2419363d601d75ad759913f439c3f722c0b3ceccee603548414dcbcf828aa169acd00793fad92c
|
|
7
|
+
data.tar.gz: 27633d7ecf115edbb86be8b669552b8bdd4ccc427aacd1c775dea017a4a143aa7df267a4dfb0ffa59de3f1e827f0056f6e6989f16e022ee808d277ee2e32bcc8
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#include <ruby.h>
|
|
2
|
+
#include <ruby/thread.h>
|
|
2
3
|
#include <ruby/encoding.h>
|
|
3
4
|
#include "teptris/teptris.h"
|
|
4
5
|
#include "teptris/plan.h"
|
|
@@ -237,9 +238,36 @@ static VALUE obj_from_node(const teptris_node *n, unsigned flags,
|
|
|
237
238
|
}}
|
|
238
239
|
}
|
|
239
240
|
|
|
241
|
+
/* the parse is pure C over the buffer with no VM access: run it
|
|
242
|
+
* GVL-free so other threads progress during long parses (and parallel
|
|
243
|
+
* batch loads overlap). The string is locked (pinned) across the
|
|
244
|
+
* release so a concurrent compacting GC cannot relocate the buffer. */
|
|
245
|
+
struct gvl_parse_arg {
|
|
246
|
+
const char *data;
|
|
247
|
+
size_t len;
|
|
248
|
+
teptris_document **doc;
|
|
249
|
+
teptris_status st;
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
static void *parse_without_gvl(void *p)
|
|
253
|
+
{
|
|
254
|
+
struct gvl_parse_arg *a = (struct gvl_parse_arg *)p;
|
|
255
|
+
a->st = teptris_parse(a->data, a->len, NULL, a->doc);
|
|
256
|
+
return NULL;
|
|
257
|
+
}
|
|
258
|
+
|
|
240
259
|
static teptris_document *parse_or_raise(VALUE str) {
|
|
241
260
|
teptris_document *doc = NULL;
|
|
242
|
-
|
|
261
|
+
/* frozen strings cannot take the tmp lock (ruby 4.0 raises
|
|
262
|
+
* FrozenError - chilled literals); a dup is movable-page-safe the
|
|
263
|
+
* same way, at one input copy for frozen callers only */
|
|
264
|
+
volatile VALUE pin = OBJ_FROZEN(str) ? rb_str_dup(str) : str;
|
|
265
|
+
rb_str_locktmp(pin);
|
|
266
|
+
struct gvl_parse_arg arg = {RSTRING_PTR(pin), (size_t)RSTRING_LEN(pin),
|
|
267
|
+
&doc, TEPTRIS_OK};
|
|
268
|
+
rb_thread_call_without_gvl(parse_without_gvl, &arg, RUBY_UBF_IO, NULL);
|
|
269
|
+
rb_str_unlocktmp(pin);
|
|
270
|
+
teptris_status st = arg.st;
|
|
243
271
|
if (st != TEPTRIS_OK) {
|
|
244
272
|
const teptris_error *e = teptris_document_error(doc);
|
|
245
273
|
VALUE ex = rb_exc_new(eParseError, e->message, (long)strlen(e->message));
|
|
@@ -995,6 +1023,100 @@ static VALUE ext_load_lazy_batch(VALUE self, VALUE strings) {
|
|
|
995
1023
|
* as ext_load. opts is parsed once; safe_load semantics apply uniformly.
|
|
996
1024
|
* The caller's input Array is read-only; its strings stay referenced
|
|
997
1025
|
* for the duration of the C call (no separate keeper Array). */
|
|
1026
|
+
/* slice-level GVL release: one thread parses K documents under a
|
|
1027
|
+
* single release (no per-document GVL ping-pong), then materializes
|
|
1028
|
+
* them under the GVL with one shared key cache (batch semantics).
|
|
1029
|
+
* All inputs are pinned (dup'd when frozen) across the release. */
|
|
1030
|
+
struct slice_parse_arg {
|
|
1031
|
+
long n;
|
|
1032
|
+
const char **data;
|
|
1033
|
+
size_t *lens;
|
|
1034
|
+
teptris_document **docs;
|
|
1035
|
+
teptris_status *stats;
|
|
1036
|
+
};
|
|
1037
|
+
|
|
1038
|
+
static void *slice_parse_without_gvl(void *p)
|
|
1039
|
+
{
|
|
1040
|
+
struct slice_parse_arg *a = (struct slice_parse_arg *)p;
|
|
1041
|
+
for (long i = 0; i < a->n; i++) {
|
|
1042
|
+
a->docs[i] = NULL;
|
|
1043
|
+
a->stats[i] = teptris_parse(a->data[i], a->lens[i], NULL, &a->docs[i]);
|
|
1044
|
+
}
|
|
1045
|
+
return NULL;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
static VALUE ext_load_slice(int argc, VALUE *argv, VALUE self) {
|
|
1049
|
+
VALUE strs, opts;
|
|
1050
|
+
rb_scan_args(argc, argv, "11", &strs, &opts);
|
|
1051
|
+
Check_Type(strs, T_ARRAY);
|
|
1052
|
+
long n = RARRAY_LEN(strs);
|
|
1053
|
+
if (n == 0) return rb_ary_new();
|
|
1054
|
+
unsigned flags = flags_from_opts(opts);
|
|
1055
|
+
|
|
1056
|
+
volatile VALUE *pins = ALLOC_N(VALUE, n);
|
|
1057
|
+
const char **data = ALLOC_N(const char *, n);
|
|
1058
|
+
size_t *lens = ALLOC_N(size_t, n);
|
|
1059
|
+
teptris_document **docs = ALLOC_N(teptris_document *, n);
|
|
1060
|
+
teptris_status *stats = ALLOC_N(teptris_status, n);
|
|
1061
|
+
/* the tmp lock is one-per-string: repeated objects in the slice
|
|
1062
|
+
* get a dup before pinning (frozen inputs dup for the same reason
|
|
1063
|
+
* - they cannot take the lock, yet compaction can move them) */
|
|
1064
|
+
VALUE seen = rb_hash_new();
|
|
1065
|
+
long i;
|
|
1066
|
+
for (i = 0; i < n; i++) {
|
|
1067
|
+
VALUE str = rb_ary_entry(strs, i);
|
|
1068
|
+
StringValue(str);
|
|
1069
|
+
VALUE id = rb_obj_id(str);
|
|
1070
|
+
if (!NIL_P(rb_hash_aref(seen, id))) {
|
|
1071
|
+
str = rb_str_dup(str);
|
|
1072
|
+
} else {
|
|
1073
|
+
rb_hash_aset(seen, id, Qtrue);
|
|
1074
|
+
if (OBJ_FROZEN(str)) str = rb_str_dup(str);
|
|
1075
|
+
}
|
|
1076
|
+
pins[i] = str;
|
|
1077
|
+
rb_str_locktmp(pins[i]);
|
|
1078
|
+
data[i] = RSTRING_PTR(pins[i]);
|
|
1079
|
+
lens[i] = (size_t)RSTRING_LEN(pins[i]);
|
|
1080
|
+
}
|
|
1081
|
+
struct slice_parse_arg arg = {n, data, lens, docs, stats};
|
|
1082
|
+
rb_thread_call_without_gvl(slice_parse_without_gvl, &arg,
|
|
1083
|
+
RUBY_UBF_IO, NULL);
|
|
1084
|
+
for (i = 0; i < n; i++) rb_str_unlocktmp(pins[i]);
|
|
1085
|
+
|
|
1086
|
+
VALUE out = rb_ary_new_capa(n);
|
|
1087
|
+
keycache kc;
|
|
1088
|
+
kc_init(&kc);
|
|
1089
|
+
for (i = 0; i < n; i++) {
|
|
1090
|
+
if (stats[i] != TEPTRIS_OK && docs[i] == NULL) {
|
|
1091
|
+
const char *msg = "parse failed (out of memory)";
|
|
1092
|
+
VALUE ex = rb_exc_new(eParseError, msg, (long)strlen(msg));
|
|
1093
|
+
kc_free(&kc);
|
|
1094
|
+
xfree(pins); xfree(data); xfree(lens); xfree(docs); xfree(stats);
|
|
1095
|
+
rb_exc_raise(ex);
|
|
1096
|
+
}
|
|
1097
|
+
const teptris_error *e = teptris_document_error(docs[i]);
|
|
1098
|
+
if (stats[i] != TEPTRIS_OK) {
|
|
1099
|
+
VALUE ex = rb_exc_new(eParseError, e->message,
|
|
1100
|
+
(long)strlen(e->message));
|
|
1101
|
+
rb_iv_set(ex, "@line", SIZET2NUM(e->line));
|
|
1102
|
+
rb_iv_set(ex, "@column", SIZET2NUM(e->column));
|
|
1103
|
+
long j;
|
|
1104
|
+
for (j = i; j < n; j++) teptris_document_free(docs[j]);
|
|
1105
|
+
kc_free(&kc);
|
|
1106
|
+
xfree(pins); xfree(data); xfree(lens); xfree(docs); xfree(stats);
|
|
1107
|
+
rb_exc_raise(ex);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
for (i = 0; i < n; i++) {
|
|
1111
|
+
rb_ary_store(out, i,
|
|
1112
|
+
obj_from_node(teptris_document_root(docs[i]), flags, &kc));
|
|
1113
|
+
teptris_document_free(docs[i]);
|
|
1114
|
+
}
|
|
1115
|
+
kc_free(&kc);
|
|
1116
|
+
xfree(pins); xfree(data); xfree(lens); xfree(docs); xfree(stats);
|
|
1117
|
+
return out;
|
|
1118
|
+
}
|
|
1119
|
+
|
|
998
1120
|
static VALUE ext_load_batch(int argc, VALUE *argv, VALUE self) {
|
|
999
1121
|
VALUE strings, opts;
|
|
1000
1122
|
rb_scan_args(argc, argv, "11", &strings, &opts);
|
|
@@ -1052,6 +1174,7 @@ void Init_teptris_ext(void) {
|
|
|
1052
1174
|
rb_define_module_function(m, "load", ext_load, -1);
|
|
1053
1175
|
rb_define_module_function(m, "load_lazy", ext_load_lazy, 1);
|
|
1054
1176
|
rb_define_module_function(m, "load_batch", ext_load_batch, -1);
|
|
1177
|
+
rb_define_module_function(m, "load_slice", ext_load_slice, -1);
|
|
1055
1178
|
rb_define_module_function(m, "load_lazy_batch", ext_load_lazy_batch, 1);
|
|
1056
1179
|
cLazyOwner = rb_define_class_under(m, "LazyOwner", rb_cObject);
|
|
1057
1180
|
cLazyValue = rb_define_class_under(m, "LazyValue", rb_cObject);
|
data/lib/teptris/toml.rb
CHANGED
|
@@ -51,9 +51,53 @@ module Teptris
|
|
|
51
51
|
# struct across the batch, return one eager Hash per document.
|
|
52
52
|
# Same datetime contract as load. A failing document raises
|
|
53
53
|
# Teptris::ParseError carrying the first failure's line/column.
|
|
54
|
-
def load_batch(tomls, safe_load: nil, datetime_policy: :native
|
|
54
|
+
def load_batch(tomls, safe_load: nil, datetime_policy: :native,
|
|
55
|
+
threads: 1)
|
|
55
56
|
_validate_batch!(tomls, safe_load, datetime_policy)
|
|
56
|
-
|
|
57
|
+
opts = _safe_load_opts(safe_load, datetime_policy)
|
|
58
|
+
return TeptrisExt.load_batch(tomls, opts) if threads <= 1
|
|
59
|
+
|
|
60
|
+
# threads > 1: slice across Ruby threads. The C parse releases
|
|
61
|
+
# the GVL, so parses overlap; materialization serializes on the
|
|
62
|
+
# GVL. Contiguous slices keep error order deterministic: the
|
|
63
|
+
# lowest-index failure raises after all threads join.
|
|
64
|
+
n = tomls.size
|
|
65
|
+
threads = [threads, n].min
|
|
66
|
+
return TeptrisExt.load_batch(tomls, opts) if threads <= 1
|
|
67
|
+
# load_slice releases the GVL once per slice (no per-document
|
|
68
|
+
# ping-pong) and shares one key cache across its documents.
|
|
69
|
+
# Repeated objects are dup'd once each HERE: a repeat landing in
|
|
70
|
+
# two slices would double-lock across threads (the slice-level
|
|
71
|
+
# guard cannot see across slices); frozen dup'ing is C-side.
|
|
72
|
+
seen = {}
|
|
73
|
+
tomls = tomls.map do |s|
|
|
74
|
+
next s.dup if seen[s.object_id]
|
|
75
|
+
seen[s.object_id] = true
|
|
76
|
+
s
|
|
77
|
+
end
|
|
78
|
+
per = (n.to_f / threads).ceil
|
|
79
|
+
results = Array.new(n)
|
|
80
|
+
first_error = nil
|
|
81
|
+
mutex = Mutex.new
|
|
82
|
+
threads.times.map do |ti|
|
|
83
|
+
Thread.new(ti) do |i|
|
|
84
|
+
base = i * per
|
|
85
|
+
slice = tomls[base, per] || []
|
|
86
|
+
begin
|
|
87
|
+
loaded = TeptrisExt.load_slice(slice, opts)
|
|
88
|
+
slice.each_with_index { |_, j| results[base + j] = loaded[j] }
|
|
89
|
+
rescue StandardError => e
|
|
90
|
+
mutex.synchronize do
|
|
91
|
+
first_error ||= [base, e]
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end.each(&:join)
|
|
96
|
+
if first_error
|
|
97
|
+
_, err = first_error
|
|
98
|
+
raise err
|
|
99
|
+
end
|
|
100
|
+
results
|
|
57
101
|
end
|
|
58
102
|
|
|
59
103
|
# Lazy twin of load_batch: parse N inputs eagerly, hand back an
|
data/lib/teptris/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: teptris
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.52
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- teptris contributors
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-10-01 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: A native C extension over libteptris, a C99 TOML 1.1 parser/emitter.
|
|
14
14
|
API shape mirrors the tomlib gem.
|