expanse 0.9.2 → 0.10.0

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.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/expanse.gemspec +1 -1
  3. data/lib/expanse.rb +88 -31
  4. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8043aef053fe8b06e269408d9b9e18cf50a673fda11f6b031161ad14ce91f830
4
- data.tar.gz: d8e4f54caa0b83b4db60f058aa5e2c39da3a6c113fa2cf4943465daf9e4ce8e0
3
+ metadata.gz: 5300ecd5bd652ecf1fd4b4ab8f360e8a92ad5243574573d61c6134b1093e33c5
4
+ data.tar.gz: 6a88dc9770aeb6d81165e7c0a4b57e1fc8dcb8e1901a2f8ece2972e1228438fd
5
5
  SHA512:
6
- metadata.gz: aa4dbf0eab25c2d9588bc4de063de6f5b9f28f40d9d95b4f255599ae72ad13e96249b5c50130ece50b546c6f1e00b158b3fd590f4475f06cd72ef9c66e09622a
7
- data.tar.gz: 462b88f941e48466183f849948f096ebe0338471e0b40ef9dd039904aca83d14b745435a59ad026bf4a6e39bc04e90dd9592603b3e13ee14eaf8a9c4f38324c2
6
+ metadata.gz: 1257f1f3412d1b855333a16bcd6e3eb9fb2438c278cf3b45c4167a091a70620841917b47cdce88f0791ac3f4db77e13b82c5dfd9318124795c61966aced57406
7
+ data.tar.gz: 3e54aa15d0a7c86a5ea1eca61a71b7f176dacadaeef87c887fd7548773d1f3a1acc752dd74e24f780ce7619c71abef49ece0d5e281a3ead78d87bf384a3bac51
data/expanse.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = "expanse"
3
- spec.version = "0.9.2"
3
+ spec.version = "0.10.0"
4
4
  spec.authors = ["Nicolas Brousse"]
5
5
  spec.email = ["nicolas@brousse.info"]
6
6
  spec.summary = "Expanse: clean-room, pure-Rust Judy arrays"
data/lib/expanse.rb CHANGED
@@ -4,7 +4,7 @@ require "fiddle"
4
4
  require "fiddle/import"
5
5
 
6
6
  module Expanse
7
- VERSION = "0.9.2"
7
+ VERSION = "0.10.0"
8
8
 
9
9
  module Native
10
10
  extend Fiddle::Importer
@@ -35,50 +35,58 @@ module Expanse
35
35
 
36
36
  raise "Could not load libexpanse native library" unless loaded
37
37
 
38
+ # Every function whose C return type is `bool` is imported as
39
+ # `unsigned char`, never `int`. The System V x86-64 and AArch64 ABIs define
40
+ # only the low 8 bits of a `bool` return; a 32-bit read can see non-zero
41
+ # upper bits for `false`. Fiddle converts an `unsigned char` return through
42
+ # an `(unsigned char)` cast, so the call sites' `!= 0` reads exactly the
43
+ # byte the callee defined. `unsigned char` is used rather than Fiddle's
44
+ # `bool` type so the declaration does not depend on the Fiddle version.
45
+
38
46
  # Library metadata
39
47
  extern "const char* expanse_version(void)"
40
48
 
41
49
  # Set
42
50
  extern "void* expanse_set_new(void)"
43
51
  extern "void expanse_set_free(void*)"
44
- extern "int expanse_set_insert(void*, unsigned long long)"
45
- extern "int expanse_set_remove(void*, unsigned long long)"
46
- extern "int expanse_set_contains(void*, unsigned long long)"
52
+ extern "unsigned char expanse_set_insert(void*, unsigned long long)"
53
+ extern "unsigned char expanse_set_remove(void*, unsigned long long)"
54
+ extern "unsigned char expanse_set_contains(void*, unsigned long long)"
47
55
  extern "unsigned long long expanse_set_len(void*)"
48
56
  extern "size_t expanse_set_mem_used(void*)"
49
57
  extern "size_t expanse_set_mem_held(void*)"
50
58
  extern "size_t expanse_set_shrink_to_fit(void*)"
51
59
  extern "void expanse_set_clear(void*)"
52
- extern "int expanse_set_first(void*, void*)"
53
- extern "int expanse_set_last(void*, void*)"
54
- extern "int expanse_set_next_after(void*, unsigned long long, void*)"
55
- extern "int expanse_set_prev_before(void*, unsigned long long, void*)"
60
+ extern "unsigned char expanse_set_first(void*, void*)"
61
+ extern "unsigned char expanse_set_last(void*, void*)"
62
+ extern "unsigned char expanse_set_next_after(void*, unsigned long long, void*)"
63
+ extern "unsigned char expanse_set_prev_before(void*, unsigned long long, void*)"
56
64
  extern "unsigned long long expanse_set_count_below(void*, unsigned long long)"
57
65
  extern "unsigned long long expanse_set_count_range(void*, unsigned long long, unsigned long long)"
58
- extern "int expanse_set_by_count(void*, unsigned long long, void*)"
66
+ extern "unsigned char expanse_set_by_count(void*, unsigned long long, void*)"
59
67
 
60
68
  # Map
61
69
  extern "void* expanse_map_new(void)"
62
70
  extern "void expanse_map_free(void*)"
63
- extern "int expanse_map_insert(void*, unsigned long long, unsigned long long, void*)"
64
- extern "int expanse_map_get(void*, unsigned long long, void*)"
65
- extern "int expanse_map_remove(void*, unsigned long long, void*)"
71
+ extern "unsigned char expanse_map_insert(void*, unsigned long long, unsigned long long, void*)"
72
+ extern "unsigned char expanse_map_get(void*, unsigned long long, void*)"
73
+ extern "unsigned char expanse_map_remove(void*, unsigned long long, void*)"
66
74
  extern "unsigned long long expanse_map_len(void*)"
67
75
  extern "size_t expanse_map_mem_used(void*)"
68
76
  extern "size_t expanse_map_mem_held(void*)"
69
77
  extern "size_t expanse_map_shrink_to_fit(void*)"
70
78
  extern "void expanse_map_clear(void*)"
71
- extern "int expanse_map_first(void*, void*, void*)"
72
- extern "int expanse_map_last(void*, void*, void*)"
73
- extern "int expanse_map_next_after(void*, unsigned long long, void*, void*)"
74
- extern "int expanse_map_prev_before(void*, unsigned long long, void*, void*)"
79
+ extern "unsigned char expanse_map_first(void*, void*, void*)"
80
+ extern "unsigned char expanse_map_last(void*, void*, void*)"
81
+ extern "unsigned char expanse_map_next_after(void*, unsigned long long, void*, void*)"
82
+ extern "unsigned char expanse_map_prev_before(void*, unsigned long long, void*, void*)"
75
83
 
76
84
  # StrMap
77
85
  extern "void* expanse_strmap_new(void)"
78
86
  extern "void expanse_strmap_free(void*)"
79
- extern "int expanse_strmap_insert(void*, const char*, unsigned long long, void*)"
80
- extern "int expanse_strmap_get(void*, const char*, void*)"
81
- extern "int expanse_strmap_remove(void*, const char*, void*)"
87
+ extern "unsigned char expanse_strmap_insert(void*, const char*, unsigned long long, void*)"
88
+ extern "unsigned char expanse_strmap_get(void*, const char*, void*)"
89
+ extern "unsigned char expanse_strmap_remove(void*, const char*, void*)"
82
90
  extern "unsigned long long expanse_strmap_len(void*)"
83
91
  extern "void expanse_strmap_clear(void*)"
84
92
  extern "size_t expanse_strmap_mem_held(void*)"
@@ -87,21 +95,25 @@ module Expanse
87
95
  # BytesMap
88
96
  extern "void* expanse_bytesmap_new(void)"
89
97
  extern "void expanse_bytesmap_free(void*)"
90
- extern "int expanse_bytesmap_insert(void*, const void*, size_t, unsigned long long, void*)"
91
- extern "int expanse_bytesmap_get(void*, const void*, size_t, void*)"
92
- extern "int expanse_bytesmap_remove(void*, const void*, size_t, void*)"
98
+ extern "unsigned char expanse_bytesmap_insert(void*, const void*, size_t, unsigned long long, void*)"
99
+ extern "unsigned char expanse_bytesmap_get(void*, const void*, size_t, void*)"
100
+ extern "unsigned char expanse_bytesmap_remove(void*, const void*, size_t, void*)"
93
101
  extern "unsigned long long expanse_bytesmap_len(void*)"
94
102
  extern "void expanse_bytesmap_clear(void*)"
95
103
 
96
104
  # BlobMap
97
105
  extern "void* expanse_blob_map_new(size_t)"
106
+ extern "void* expanse_blob_map_new_with_capacity(size_t, size_t)"
107
+ extern "void expanse_blob_map_set_reclaim_at_cap(void*, unsigned char)"
108
+ extern "int expanse_blob_map_insert_ex(void*, unsigned long long, const void*, size_t, unsigned int)"
109
+ extern "int expanse_blob_map_arena_stats(void*, void*, size_t)"
98
110
  extern "void expanse_blob_map_free(void*)"
99
- extern "int expanse_blob_map_insert(void*, unsigned long long, const void*, size_t, unsigned int)"
100
- extern "int expanse_blob_map_get(void*, unsigned long long, void*)"
101
- extern "int expanse_blob_map_remove(void*, unsigned long long)"
111
+ extern "unsigned char expanse_blob_map_insert(void*, unsigned long long, const void*, size_t, unsigned int)"
112
+ extern "unsigned char expanse_blob_map_get(void*, unsigned long long, void*)"
113
+ extern "unsigned char expanse_blob_map_remove(void*, unsigned long long)"
102
114
  extern "unsigned long long expanse_blob_map_len(void*)"
103
115
  extern "void expanse_blob_map_clear(void*)"
104
- extern "int expanse_blob_map_contains_key(void*, unsigned long long)"
116
+ extern "unsigned char expanse_blob_map_contains_key(void*, unsigned long long)"
105
117
  end
106
118
 
107
119
  def self.version
@@ -337,14 +349,14 @@ module Expanse
337
349
  end
338
350
 
339
351
  def []=(key, val)
340
- s = key.to_s
352
+ s = c_key(key)
341
353
  Native.expanse_strmap_insert(@ptr, s, val, nil)
342
354
  val
343
355
  end
344
356
  alias set []=
345
357
 
346
358
  def [](key)
347
- s = key.to_s
359
+ s = c_key(key)
348
360
  buf = Fiddle::Pointer.malloc(8)
349
361
  if Native.expanse_strmap_get(@ptr, s, buf) != 0
350
362
  buf.to_str(8).unpack1("Q<")
@@ -353,7 +365,7 @@ module Expanse
353
365
  alias get []
354
366
 
355
367
  def delete(key)
356
- s = key.to_s
368
+ s = c_key(key)
357
369
  buf = Fiddle::Pointer.malloc(8)
358
370
  if Native.expanse_strmap_remove(@ptr, s, buf) != 0
359
371
  buf.to_str(8).unpack1("Q<")
@@ -372,6 +384,20 @@ module Expanse
372
384
  Native.expanse_strmap_clear(@ptr)
373
385
  self
374
386
  end
387
+
388
+ private
389
+
390
+ # The key as the C ABI will read it. Every expanse_strmap_* call takes the
391
+ # key as a NUL-terminated const char*, so a Ruby String carrying "\0" would
392
+ # reach the engine truncated and "a\0b" would address the entry for "a".
393
+ # ExpanseStrMap keys are NUL-free; reject the key instead, as the PHP
394
+ # binding does.
395
+ def c_key(key)
396
+ s = key.to_s
397
+ raise ArgumentError, 'NUL bytes ("\0") are not allowed in StrMap keys' if s.include?("\0")
398
+
399
+ s
400
+ end
375
401
  end
376
402
 
377
403
  class BytesMap
@@ -423,8 +449,16 @@ module Expanse
423
449
  end
424
450
 
425
451
  class BlobMap
426
- def initialize(chunk_size: 0)
427
- @ptr = Native.expanse_blob_map_new(chunk_size)
452
+ # expanse_blob_status_t values, as set_status returns them.
453
+ STATUS = {
454
+ 0 => :ok, 16 => :meta_overflow, 17 => :allocation_failed,
455
+ 18 => :cap_refused, 19 => :arena_full, 32 => :invalid_argument
456
+ }.freeze
457
+
458
+ # max_capacity caps the arena's allocated chunk bytes (0: the default
459
+ # 1 GiB), clamped to [chunk_size, 64 GiB]; chunk_size 0 is the 2 MiB default.
460
+ def initialize(chunk_size: 0, max_capacity: 0)
461
+ @ptr = Native.expanse_blob_map_new_with_capacity(chunk_size, max_capacity)
428
462
  ObjectSpace.define_finalizer(self, self.class.finalize(@ptr))
429
463
  end
430
464
 
@@ -437,6 +471,29 @@ module Expanse
437
471
  Native.expanse_blob_map_insert(@ptr, key, b, b.bytesize, hot_meta) != 0
438
472
  end
439
473
 
474
+ # Like set, but returns why a refused insert was refused: :cap_refused (the
475
+ # capacity cap refused a chunk and nothing was compacted) or :arena_full
476
+ # (this insert compacted and the record still does not fit), among others.
477
+ def set_status(key, payload, hot_meta: 0)
478
+ b = payload.b
479
+ STATUS.fetch(Native.expanse_blob_map_insert_ex(@ptr, key, b, b.bytesize, hot_meta), :error)
480
+ end
481
+
482
+ # Turns the reclaim at the capacity cap on (the default) or off.
483
+ def reclaim_at_cap=(on)
484
+ Native.expanse_blob_map_set_reclaim_at_cap(@ptr, on ? 1 : 0)
485
+ end
486
+
487
+ # Arena accounting: live bytes (payloads plus 8-byte headers), allocated
488
+ # chunk bytes (what the cap counts), the cap, the chunk size, the switch.
489
+ def arena_stats
490
+ buf = Fiddle::Pointer.malloc(40)
491
+ Native.expanse_blob_map_arena_stats(@ptr, buf, 40)
492
+ live, allocated, cap, chunk, reclaim = buf[0, 40].unpack("Q<5")
493
+ { live_bytes: live, allocated_bytes: allocated, max_capacity: cap,
494
+ chunk_size: chunk, reclaim_at_cap: reclaim == 1 }
495
+ end
496
+
440
497
  def get(key)
441
498
  # ExpanseBlobView: ptr (8B), len (8B), hot_meta (4B), is_inline (1B) + padding (3B) = 24 bytes
442
499
  view_buf = Fiddle::Pointer.malloc(24)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: expanse
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.2
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nicolas Brousse
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-09-29 00:00:00.000000000 Z
11
+ date: 2026-10-01 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Ruby bindings for the expanse library.
14
14
  email: