lutaml-model 0.8.51 → 0.8.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bd76b1c0377a6e86d547f5ca29fe5d164d390bc7f1d88ba7051a5930b119911f
4
- data.tar.gz: 22e2dea3ad97c502647a49ec748113e939234b9136c02634b99c722ce443df10
3
+ metadata.gz: 92e0193fa58094112522b5a7717aed2f568d77096eb0ae3bc1a246be28c05bf0
4
+ data.tar.gz: 20dcd124c22c695cc0a8fca65d04422ac84f468515ef153d909e5a6dcf5eac67
5
5
  SHA512:
6
- metadata.gz: '0883bebaf58efeeda222ddc83f74661f396184786944f48f1a9ae2ecb5cbb4c15059cb5dd93df791d101acd734ca597e849b5c0d4488b104c59b7afa01f05e40'
7
- data.tar.gz: 46e13698b95bff244e8d31627bbd3c1fd2a9fc667b3741a9707bac2a85256be426a9606c3c38ac848722ae668c488da39643154f5387aa6a60ee006dd57056b3
6
+ metadata.gz: 0cc3e71dceb6ae87db00453efbd905de28dc10d528cd38426b29c2c0da1a67d64d41a59095e45a9da1daa97888a4d001c8211b0ad64a3a011ef46949e71206a6
7
+ data.tar.gz: c444800a986c0e34bc1502fd671f53d7ff4776377d89b62225b857c923f4b97058042094145caf0d0a54db37576bedd546a733e114a444118ff2e13241092d83
@@ -57,9 +57,31 @@ RUBY_YJIT_ENABLE=1 ruby app.rb
57
57
  RubyVM::YJIT.enable
58
58
  ----
59
59
 
60
- The opt-in XML plan fast path (see below) moves work out of Ruby and
61
- into the engine, which shrinks the YJIT win — enabling both is still
62
- net faster than either alone.
60
+ The XML plan fast path moves work out of Ruby and into the engine,
61
+ which shrinks the YJIT win — enabling both is still net faster than
62
+ either alone.
63
+
64
+ == The XML plan fast path (default on)
65
+
66
+ When the XML adapter resolves to leptris, XML (de)serialization
67
+ compiles the model's mapping into a leptris descriptor plan and runs
68
+ the whole document through one native pass — measured 6.7x parse and
69
+ 4x serialize with -76% / -80% allocations on the plan-path corpus.
70
+ Models the plan cannot express exactly (same-name discriminator
71
+ partitions, namespace-qualified models with deferred-rule shapes) and
72
+ shapes with custom semantics (custom methods, polymorphism, unions,
73
+ ordered children) hydrate through the interpretive pipeline or a
74
+ per-subtree fragment of it, unchanged.
75
+
76
+ The path is on by default and is a no-op without leptris (standard
77
+ adapters keep the classic pipeline). Opt out for the whole process:
78
+
79
+ [source,ruby]
80
+ ----
81
+ Lutaml::Model::Config.configure do |config|
82
+ config.xml_plan_fast_path = false
83
+ end
84
+ ----
63
85
 
64
86
  == Selecting adapters explicitly
65
87
 
@@ -25,7 +25,7 @@ module Lutaml
25
25
  @default_register = :default
26
26
  @conversion_cache = nil
27
27
  @configured = false
28
- @xml_plan_fast_path = false
28
+ @xml_plan_fast_path = true
29
29
  end
30
30
 
31
31
  def configure
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.8.51"
5
+ VERSION = "0.8.52"
6
6
  end
7
7
  end
@@ -39,7 +39,21 @@ module Lutaml
39
39
  key = [model_class, register]
40
40
  return PLAN_CACHE[key] if PLAN_CACHE.key?(key)
41
41
 
42
- PLAN_CACHE[key] = build(model_class, register)
42
+ # Cycle guard: a self-referential model (JATS sec-in-sec) must
43
+ # resolve to nil — the interpretive pipeline owns it. The guard
44
+ # is THREAD-LOCAL (a shared in-progress set races: a concurrent
45
+ # same-key compile would cache false permanently — the #828
46
+ # lesson); the nil at the cycle point is NOT cached — the
47
+ # outermost build completes and caches the real verdict.
48
+ stack = (Thread.current[:plan_compiler_stack] ||= [])
49
+ return nil if stack.include?(key)
50
+
51
+ stack.push(key)
52
+ begin
53
+ PLAN_CACHE[key] = build(model_class, register)
54
+ ensure
55
+ stack.pop
56
+ end
43
57
  end
44
58
 
45
59
  private
@@ -38,8 +38,42 @@ RSpec.describe "XML plan fast path" do
38
38
  Lutaml::Model::Config.instance.xml_plan_fast_path = old
39
39
  end
40
40
 
41
- it "is opt-in and off by default" do
42
- expect(Lutaml::Model::Configuration.new.xml_plan_fast_path).to be(false)
41
+ # Real-corpus crash (niso-jats): sec-in-sec nesting is a
42
+ # self-referential model — compile must resolve it to nil instead of
43
+ # recursing to SystemStackError.
44
+ it "opts self-referential models out instead of recursing" do
45
+ node = Class.new(Lutaml::Model::Serializable) do
46
+ attribute :text, :string
47
+ xml do
48
+ element "n"
49
+ map_content to: :text
50
+ end
51
+ end
52
+ node.attribute :child, node
53
+ node.xml do
54
+ element "n"
55
+ map_content to: :text
56
+ map_element "child", to: :child
57
+ end
58
+ stub_const("PlanFastPath::SelfRef", node)
59
+
60
+ expect(Lutaml::Xml::PlanCompiler.compile(node, :default)).to be_nil
61
+
62
+ xml = "<n>#{'<n>t</n>' * 200}</n>"
63
+ parsed = node.from_xml(xml)
64
+ expect(parsed).to be_a(node)
65
+ end
66
+
67
+ it "is on by default and can be disabled" do
68
+ expect(Lutaml::Model::Configuration.new.xml_plan_fast_path).to be(true)
69
+
70
+ # Opt-out restores the interpretive pipeline for the whole process.
71
+ Lutaml::Model::Config.instance.xml_plan_fast_path = false
72
+ item = item_class.new(id: 1, name: "a", tags: %w[x])
73
+ parsed = root_class.from_xml(root_class.new(item: [item]).to_xml)
74
+ expect(parsed.item.first.id).to eq(1)
75
+ ensure
76
+ Lutaml::Model::Config.instance.xml_plan_fast_path = true
43
77
  end
44
78
 
45
79
  it "hydrates equal to the interpretive path" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lutaml-model
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.51
4
+ version: 0.8.52
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.