leptris 1.9.12 → 1.9.13

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: 7c286a2c462a249a214a59fa8ca2e9c85f0945e997dcf04b587412d4b10a85b6
4
- data.tar.gz: 9c2b7efb4e42f2cd2433cc11f925eacf81ee67ff0f5f6faed8b9bb74dfbc4feb
3
+ metadata.gz: 590285b33ed6aac05def5ff85268822fc6d5abf87cfd5b262d51b72868f70ac3
4
+ data.tar.gz: 8dc09c12f495c12177a5dd15ff8892340dc090d678eb342738571bfb0e142228
5
5
  SHA512:
6
- metadata.gz: 3265cfca1c7858c657c9bb58524b289f2224348614febf54c63c7f91f210a0500d889834a0ea55c950c788c5ae24dadba45a7677794c32afb431c929fd791ae6
7
- data.tar.gz: 44a202c1c0edb009089f652c527401b5d9b52c5194e5546c9013c1100e4a37fc015ee7868a7aa3638e4e1798b7e838f73939e645f973e51e288061fdf49da870
6
+ metadata.gz: 2781b4465ff07a85ea7ecc91824070da8412aa933bc96273a4b9159860c95a0e227e06630967685e66db9fd696a6e5648ec9474217ecae769b8163096adf18ed
7
+ data.tar.gz: 9d7173b435723be6f068700e27e05f796246143dcf22dd1319079c5770a71f3f933fdd7d517a71ee47cba58f1305ed2d2e270ffaee43c422c43d693dbb77f4f7
data/CHANGELOG.md CHANGED
@@ -5,6 +5,25 @@ All notable changes to Leptris will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.9.13] - 2026-08-27
9
+
10
+ ### Changed
11
+
12
+ - **Cached-true readonly guard**: readonly is one-way, so a node
13
+ caches TRUE once observed (FALSE stays uncached — the document
14
+ may still flip). Per-read guards drop from a three-call document
15
+ round-trip to one ivar check. Harness attr loop 0.061 -> 0.048 s.
16
+ - **Lazy wrapper-cache allocation**: Documents no longer allocate
17
+ the wrapper-identity Hash up front; parse-heavy loops that free
18
+ before re-reading stop paying it (~9% on the tiny-doc
19
+ parse-query-serialize loop). Identity semantics unchanged.
20
+
21
+ ### Measured dead
22
+
23
+ - Further readonly-[] trimming: the isolated read is at the Ruby
24
+ call-chain floor (~227 ns across ~7 calls); inlining the
25
+ attributes build would duplicate memo logic for ~10%.
26
+
8
27
  ## [1.9.12] - 2026-08-27
9
28
 
10
29
  ### Changed
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leptris
4
- VERSION = "1.9.12"
4
+ VERSION = "1.9.13"
5
5
  end
@@ -3,7 +3,7 @@
3
3
  require "ffi"
4
4
 
5
5
  class Leptris::XML::Document
6
- attr_reader :c_ptr, :wrapper_cache
6
+ attr_reader :c_ptr
7
7
 
8
8
  # @api private
9
9
  # Internal flag container shared between the Document instance and its
@@ -31,7 +31,13 @@ class Leptris::XML::Document
31
31
  # the weak map — any GC sweep evicted it and the second call built
32
32
  # a fresh object. A strong cache costs at most one wrapper per node
33
33
  # actually visited, held until the document dies.
34
- @wrapper_cache = {}
34
+ #
35
+ # Allocated lazily: parse-heavy loops stop paying one Hash per
36
+ # document for trees that are freed before any wrap.
37
+ end
38
+
39
+ def wrapper_cache
40
+ @wrapper_cache ||= {}
35
41
  end
36
42
 
37
43
  def self.parse(xml_or_io, options: nil, readonly: false, recover: false)
@@ -230,7 +236,7 @@ class Leptris::XML::Document
230
236
  @freed.state = :freed
231
237
  Leptris::XML::FFI.leptris_document_free(@c_ptr) unless @c_ptr.nil?
232
238
  @c_ptr = nil
233
- @wrapper_cache.clear
239
+ @wrapper_cache&.clear
234
240
  end
235
241
 
236
242
  # Enable the first-party EXSLT-style extension pack on this
@@ -36,7 +36,7 @@ class Leptris::XML::Element < Leptris::XML::Node
36
36
  # materializes on demand — repeated reads become hash lookups,
37
37
  # with no dispatch and no lifetime guard (a hash read cannot
38
38
  # use-after-free).
39
- if @document&.readonly?
39
+ if readonly_document?
40
40
  cached = attributes[key.to_s]
41
41
  return cached.nil? ? nil : cached.value
42
42
  end
@@ -104,12 +104,22 @@ class Leptris::XML::Node
104
104
  # UseAfterFreeError when it was freed.
105
105
  def ensure_writable!
106
106
  ensure_alive!
107
- if @document&.readonly?
107
+ if readonly_document?
108
108
  raise Leptris::XML::ReadOnlyError,
109
109
  "document is readonly — mutation attempted on #{inspect}"
110
110
  end
111
111
  end
112
112
 
113
+ # Readonly is one-way, so caching TRUE is sound: once observed,
114
+ # the document is readonly forever. FALSE stays uncached (the
115
+ # document may still flip). Saves the document round-trip on
116
+ # per-read guards.
117
+ def readonly_document?
118
+ return true if instance_variable_defined?(:@readonly_document)
119
+ return false unless @document&.readonly?
120
+ @readonly_document = true
121
+ end
122
+
113
123
  def line
114
124
  ensure_alive!
115
125
  Leptris::XML::FFI.leptris_node_line(@c_ptr)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: leptris
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.12
4
+ version: 1.9.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.