leptris 1.9.311.1-arm-linux → 1.9.311.2-arm-linux

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: df4785eab905b23b3ababb440d2c0f47051bf0f7acde2c6631216828d4fa10d1
4
- data.tar.gz: 4f8488030ff6e479afc2ebdc59075cd3c92e5f7c17d986731195df6aad13621a
3
+ metadata.gz: fc322e484037235b28d726c88ac8e036f5321bc17c95561c60ca90b3aa9b4d4a
4
+ data.tar.gz: 6388d7a7b8715785ac6bcdb734b82848c4e1683f72f5f81c7e6556fd255ba5da
5
5
  SHA512:
6
- metadata.gz: 7fe50d8bc07f07ce47a09539df0def21e0e75716ee96afb471b2f82c5d6687f0e73544bd5afc185ae8767e7576b8e664f27b2e50cb2df7763e616668f28ba51f
7
- data.tar.gz: 3646cd773bcace3e4f0023b8f5fc80680969062228b458ff2ff296b44bf88f206261caebedb185871eca81a97a583df5579040d6cceb960f67a2d21e3ed3adf3
6
+ metadata.gz: 486358f50f20bb8633919fab3159648e916efc984b5cc6813d641fb89c0cb463cab47e5c7c50dd5832ce6f993d28055901a2b1fe050d496c3a33531f8e54d9e9
7
+ data.tar.gz: ef13ab80d77dbae815f3cad97223a69a0574095df2def3b28ef1e846b538ad4bcf3c76dc70ccfc778690ccb0420f7fdbdc3fdc712bae4a18d672cf5c378d7b83
data/CHANGELOG.md CHANGED
@@ -1,3 +1,20 @@
1
+ ## [1.9.311.2] - 2026-10-06
2
+
3
+ ### Fixed
4
+
5
+ - **Absorbed sources alive at exit no longer double-free the pool
6
+ (#386)**: `Document#absorb`'s handle-only contract was enforced
7
+ only by the explicit `#free` — the C lifetime handle's dfree
8
+ and the no-native Ruby finalizer still called
9
+ `leptris_document_free`, destroying a pool already owned (and
10
+ released) by the absorber. Libraries that pin adopted-source
11
+ documents (moxml's lifetime pins) aborted at exit-time
12
+ finalization (`leptris_pool_destroy` → `free_small_botch`). The
13
+ absorbed flag now lives where every release path reads it: the
14
+ shared `Freed` struct (Ruby finalizer) and the C handle
15
+ (`doc_handle_mark_absorbed`; dfree is handle-only when the pool
16
+ moved). Both paths pinned by subprocess regression specs.
17
+
1
18
  ## [1.9.311.1] - 2026-10-06
2
19
 
3
20
  ### Fixed
@@ -2682,15 +2682,21 @@ static VALUE nf_fast_inner_xml(VALUE self, VALUE addr)
2682
2682
  * Ruby-finalizer path (which stays for LEPTRIS_NO_NATIVE). */
2683
2683
  struct doc_handle {
2684
2684
  void *doc;
2685
+ /* Pool ownership transferred to an absorber (engine #1548):
2686
+ * the destination's free performs the real release, so this
2687
+ * handle's dfree is HANDLE-ONLY (#386: malloc_zone_error /
2688
+ * free_small_botch when an absorbed source survived to
2689
+ * exit-time finalization after its destination's release). */
2690
+ int absorbed;
2685
2691
  };
2686
2692
 
2687
2693
  static void dh_free(void *p)
2688
2694
  {
2689
2695
  struct doc_handle *h = p;
2690
- if (h->doc) {
2696
+ if (h->doc && !h->absorbed) {
2691
2697
  f_doc_free(h->doc);
2692
- h->doc = NULL;
2693
2698
  }
2699
+ h->doc = NULL;
2694
2700
  }
2695
2701
 
2696
2702
  static size_t dh_size(const void *p)
@@ -2888,6 +2894,22 @@ static VALUE nf_doc_handle_release(VALUE self, VALUE document)
2888
2894
  return Qnil;
2889
2895
  }
2890
2896
 
2897
+ /* Absorption bookkeeping (#386, engine #1548): Document#absorb
2898
+ * calls this on the SOURCE right after the engine transfers pool
2899
+ * ownership — the handle's dfree becomes handle-only, matching
2900
+ * the Ruby-side Freed flag the no-native finalizer reads. */
2901
+ static VALUE nf_doc_handle_mark_absorbed(VALUE self, VALUE document)
2902
+ {
2903
+ VALUE handle = rb_ivar_get(document, id_iv_doc_handle);
2904
+ (void)self;
2905
+ if (handle != Qnil) {
2906
+ struct doc_handle *h;
2907
+ TypedData_Get_Struct(handle, struct doc_handle, &dh_type, h);
2908
+ h->absorbed = 1;
2909
+ }
2910
+ return Qnil;
2911
+ }
2912
+
2891
2913
  /* The full Document.create in one dispatch: engine create,
2892
2914
  * binding wrapper (ivar-seeded, bypassing initialize), and the
2893
2915
  * lifetime handle. Returns Qnil when the engine refuses. */
@@ -3158,6 +3180,8 @@ LEPTRIS_INIT_EXPORT void Init_native(void)
3158
3180
  nf_doc_handle_attach, 1);
3159
3181
  rb_define_module_function(m_native, "doc_handle_release",
3160
3182
  nf_doc_handle_release, 1);
3183
+ rb_define_module_function(m_native, "doc_handle_mark_absorbed",
3184
+ nf_doc_handle_mark_absorbed, 1);
3161
3185
  rb_define_module_function(m_native, "create_binding_document",
3162
3186
  nf_create_binding_document, 0);
3163
3187
  c_doc_handle = rb_define_class_under(m_xml, "DocHandle", rb_cObject);
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leptris
4
- VERSION = "1.9.311.1"
4
+ VERSION = "1.9.311.2"
5
5
  end
@@ -11,7 +11,9 @@ class Leptris::XML::Document
11
11
  # the explicit `free` path and the finalizer. This eliminates the
12
12
  # double-free that FFI::AutoPointer's release proc caused when
13
13
  # `Document#free` was called explicitly and then GC ran.
14
- Freed = Struct.new(:state) # state: :alive | :freed
14
+ # +absorbed+ (engine #1548, #386): pool ownership moved to an
15
+ # absorber — the finalizer must never call the engine free.
16
+ Freed = Struct.new(:state, :absorbed) # state: :alive | :freed
15
17
 
16
18
  # Mutation version: advanced by every data mutation (via
17
19
  # Node#ensure_writable!, root=, add_pi). Node memos stamp the
@@ -286,7 +288,7 @@ class Leptris::XML::Document
286
288
 
287
289
  def self.finalizer(address, freed)
288
290
  proc do
289
- next if freed.state == :freed
291
+ next if freed.state == :freed || freed.absorbed
290
292
  freed.state = :freed
291
293
  Leptris::XML::FFI.leptris_document_free(::FFI::Pointer.new(address))
292
294
  end
@@ -591,8 +593,16 @@ class Leptris::XML::Document
591
593
  # objects by hand): the absorbed document's #free skips the
592
594
  # engine release, and splice seams skip source-side removals
593
595
  # because absorbed splices MOVE the node (nothing to remove).
596
+ # The flag is mirrored into every release path (#386): the
597
+ # shared Freed struct (the no-native Ruby finalizer reads it)
598
+ # and the C lifetime handle (dh_free must be handle-only when
599
+ # the pool belongs to the absorber).
594
600
  def mark_absorbed_into(destination)
595
601
  @absorbed_into = destination
602
+ @freed.absorbed = true
603
+ if defined?(Leptris::XML::NATIVE_FAST)
604
+ Leptris::XML::Native.doc_handle_mark_absorbed(self)
605
+ end
596
606
  end
597
607
 
598
608
  def absorbed_into?(destination)
Binary file
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.311.1
4
+ version: 1.9.311.2
5
5
  platform: arm-linux
6
6
  authors:
7
7
  - Ribose Inc.