lqft-python-engine 1.0.5__tar.gz → 1.0.6__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lqft-python-engine
3
- Version: 1.0.5
3
+ Version: 1.0.6
4
4
  Summary: LQFT Engine: 14.3M Ops/sec O(1) Time | O(Σ) Space Data Structure
5
5
  Home-page: https://github.com/ParjadM/Log-Quantum-Fractal-Tree-LQFT-
6
6
  Author: Parjad Minooei
@@ -134,7 +134,7 @@ static inline void fast_unlock(volatile long* lk) {
134
134
  // GLOBAL METRIC SHARDING (F-02)
135
135
  // ===================================================================
136
136
 
137
- #define MAX_TRACKED_THREADS 256
137
+ #define MAX_TRACKED_THREADS 4096
138
138
 
139
139
  typedef struct {
140
140
  int64_t phys_added;
@@ -146,6 +146,8 @@ typedef struct {
146
146
  static ALIGN_64 ThreadMetrics global_metrics_array[MAX_TRACKED_THREADS];
147
147
  static volatile long registered_threads_count = 0;
148
148
  static THREAD_LOCAL ThreadMetrics* my_metrics = NULL;
149
+ static volatile long global_arena_epoch = 1;
150
+ static THREAD_LOCAL long local_arena_epoch = 0;
149
151
 
150
152
  static inline ThreadMetrics* get_my_metrics() {
151
153
  if (my_metrics == NULL) {
@@ -157,7 +159,8 @@ static inline ThreadMetrics* get_my_metrics() {
157
159
  if (idx < MAX_TRACKED_THREADS) {
158
160
  my_metrics = &global_metrics_array[idx];
159
161
  } else {
160
- my_metrics = &global_metrics_array[0];
162
+ // Overflow bucket to avoid distorting thread-0 metrics.
163
+ my_metrics = &global_metrics_array[MAX_TRACKED_THREADS - 1];
161
164
  }
162
165
  }
163
166
  return my_metrics;
@@ -278,6 +281,66 @@ static THREAD_LOCAL LQFTNode*** local_ret_arr_head = NULL;
278
281
  static THREAD_LOCAL LQFTNode*** local_ret_arr_tail = NULL;
279
282
  static THREAD_LOCAL int local_ret_arr_count = 0;
280
283
 
284
+ static inline void reset_tls_state_if_needed(void) {
285
+ long ge = global_arena_epoch;
286
+ if (local_arena_epoch == ge) return;
287
+
288
+ // A global purge/free_all occurred. Drop stale per-thread pointers.
289
+ local_arena.current_node_chunk = NULL;
290
+ local_arena.node_chunk_idx = ARENA_CHUNK_SIZE;
291
+ local_arena.node_free_list = NULL;
292
+ local_arena.current_child_chunk = NULL;
293
+ local_arena.child_chunk_idx = ARENA_CHUNK_SIZE;
294
+ local_arena.array_free_list = NULL;
295
+
296
+ local_ret_node_head = NULL;
297
+ local_ret_node_tail = NULL;
298
+ local_ret_node_count = 0;
299
+ local_ret_arr_head = NULL;
300
+ local_ret_arr_tail = NULL;
301
+ local_ret_arr_count = 0;
302
+
303
+ local_arena_epoch = ge;
304
+ }
305
+
306
+ static inline NodeChunk* pop_pre_zeroed_node_chunk(void) {
307
+ NodeChunk* head;
308
+ for (;;) {
309
+ head = pre_zeroed_node_chunks;
310
+ if (!head) return NULL;
311
+ #ifdef _MSC_VER
312
+ if (_InterlockedCompareExchangePointer((void* volatile*)&pre_zeroed_node_chunks, (void*)head->next_global, (void*)head) == (void*)head) {
313
+ _InterlockedDecrement(&pre_node_count);
314
+ return head;
315
+ }
316
+ #else
317
+ if (__sync_bool_compare_and_swap(&pre_zeroed_node_chunks, head, head->next_global)) {
318
+ __sync_fetch_and_sub(&pre_node_count, 1);
319
+ return head;
320
+ }
321
+ #endif
322
+ }
323
+ }
324
+
325
+ static inline ChildChunk* pop_pre_zeroed_child_chunk(void) {
326
+ ChildChunk* head;
327
+ for (;;) {
328
+ head = pre_zeroed_child_chunks;
329
+ if (!head) return NULL;
330
+ #ifdef _MSC_VER
331
+ if (_InterlockedCompareExchangePointer((void* volatile*)&pre_zeroed_child_chunks, (void*)head->next_global, (void*)head) == (void*)head) {
332
+ _InterlockedDecrement(&pre_child_count);
333
+ return head;
334
+ }
335
+ #else
336
+ if (__sync_bool_compare_and_swap(&pre_zeroed_child_chunks, head, head->next_global)) {
337
+ __sync_fetch_and_sub(&pre_child_count, 1);
338
+ return head;
339
+ }
340
+ #endif
341
+ }
342
+ }
343
+
281
344
  static LQFTNode** registry = NULL;
282
345
 
283
346
  typedef struct {
@@ -382,6 +445,8 @@ void* background_alloc_thread(void* arg) {
382
445
  }
383
446
 
384
447
  LQFTNode* create_node(void* value, uint64_t key_hash, LQFTNode** children_src, uint64_t full_hash) {
448
+ reset_tls_state_if_needed();
449
+
385
450
  LQFTNode* node = NULL;
386
451
  if (!local_arena.node_free_list) {
387
452
  #ifdef _MSC_VER
@@ -405,20 +470,7 @@ LQFTNode* create_node(void* value, uint64_t key_hash, LQFTNode** children_src, u
405
470
  local_arena.node_free_list = (LQFTNode*)node->children;
406
471
  } else {
407
472
  if (local_arena.node_chunk_idx >= ARENA_CHUNK_SIZE) {
408
- NodeChunk* new_chunk = NULL;
409
- #ifdef _MSC_VER
410
- do {
411
- new_chunk = pre_zeroed_node_chunks;
412
- if (!new_chunk) break;
413
- } while (_InterlockedCompareExchangePointer((void* volatile*)&pre_zeroed_node_chunks, (void*)new_chunk->next_global, (void*)new_chunk) != (void*)new_chunk);
414
- if (new_chunk) _InterlockedDecrement(&pre_node_count);
415
- #else
416
- do {
417
- new_chunk = pre_zeroed_node_chunks;
418
- if (!new_chunk) break;
419
- } while (!__sync_bool_compare_and_swap(&pre_zeroed_node_chunks, new_chunk, new_chunk->next_global));
420
- if (new_chunk) __sync_fetch_and_sub(&pre_node_count, 1);
421
- #endif
473
+ NodeChunk* new_chunk = pop_pre_zeroed_node_chunk();
422
474
  if (!new_chunk) {
423
475
  new_chunk = alloc_node_chunk();
424
476
  }
@@ -461,20 +513,7 @@ LQFTNode* create_node(void* value, uint64_t key_hash, LQFTNode** children_src, u
461
513
  local_arena.array_free_list = (LQFTNode***)arr[0];
462
514
  } else {
463
515
  if (local_arena.child_chunk_idx >= ARENA_CHUNK_SIZE) {
464
- ChildChunk* new_chunk = NULL;
465
- #ifdef _MSC_VER
466
- do {
467
- new_chunk = pre_zeroed_child_chunks;
468
- if (!new_chunk) break;
469
- } while (_InterlockedCompareExchangePointer((void* volatile*)&pre_zeroed_child_chunks, (void*)new_chunk->next_global, (void*)new_chunk) != (void*)new_chunk);
470
- if (new_chunk) _InterlockedDecrement(&pre_child_count);
471
- #else
472
- do {
473
- new_chunk = pre_zeroed_child_chunks;
474
- if (!new_chunk) break;
475
- } while (!__sync_bool_compare_and_swap(&pre_zeroed_child_chunks, new_chunk, new_chunk->next_global));
476
- if (new_chunk) __sync_fetch_and_sub(&pre_child_count, 1);
477
- #endif
516
+ ChildChunk* new_chunk = pop_pre_zeroed_child_chunk();
478
517
  if (!new_chunk) {
479
518
  new_chunk = alloc_child_chunk();
480
519
  }
@@ -498,8 +537,10 @@ LQFTNode* create_node(void* value, uint64_t key_hash, LQFTNode** children_src, u
498
537
 
499
538
  void decref(LQFTNode* start_node) {
500
539
  if (!start_node || start_node == TOMBSTONE) return;
501
- LQFTNode* cleanup_stack[512];
540
+ int cap = 1024;
502
541
  int top = 0;
542
+ LQFTNode** cleanup_stack = (LQFTNode**)malloc((size_t)cap * sizeof(LQFTNode*));
543
+ if (!cleanup_stack) return;
503
544
  cleanup_stack[top++] = start_node;
504
545
  while (top > 0) {
505
546
  LQFTNode* node = cleanup_stack[--top];
@@ -512,7 +553,19 @@ void decref(LQFTNode* start_node) {
512
553
  fast_unlock(&stripe_locks[stripe].flag);
513
554
  if (node->children) {
514
555
  for (int i = 0; i < 32; i++) {
515
- if (node->children[i]) cleanup_stack[top++] = node->children[i];
556
+ if (node->children[i]) {
557
+ if (top >= cap) {
558
+ int next_cap = cap * 2;
559
+ LQFTNode** grown = (LQFTNode**)realloc(cleanup_stack, (size_t)next_cap * sizeof(LQFTNode*));
560
+ if (!grown) {
561
+ free(cleanup_stack);
562
+ return;
563
+ }
564
+ cleanup_stack = grown;
565
+ cap = next_cap;
566
+ }
567
+ cleanup_stack[top++] = node->children[i];
568
+ }
516
569
  }
517
570
  LQFTNode*** arr = (LQFTNode***)node->children;
518
571
  arr[0] = (LQFTNode**)local_ret_arr_head;
@@ -564,6 +617,23 @@ void decref(LQFTNode* start_node) {
564
617
  get_my_metrics()->phys_freed++;
565
618
  }
566
619
  }
620
+ free(cleanup_stack);
621
+ }
622
+
623
+ static inline int node_matches_signature(const LQFTNode* node, const char* value_ptr, uint64_t key_hash, LQFTNode** children) {
624
+ if (!node) return 0;
625
+ if (node->key_hash != key_hash) return 0;
626
+
627
+ if ((node->value == NULL) != (value_ptr == NULL)) return 0;
628
+ if (node->value && value_ptr && strcmp((const char*)node->value, value_ptr) != 0) return 0;
629
+
630
+ if ((node->children == NULL) != (children == NULL)) return 0;
631
+ if (node->children && children) {
632
+ for (int i = 0; i < 32; i++) {
633
+ if (node->children[i] != children[i]) return 0;
634
+ }
635
+ }
636
+ return 1;
567
637
  }
568
638
 
569
639
  LQFTNode* get_canonical_v2(const char* value_ptr, uint64_t key_hash, LQFTNode** children, uint64_t full_hash) {
@@ -571,20 +641,28 @@ LQFTNode* get_canonical_v2(const char* value_ptr, uint64_t key_hash, LQFTNode**
571
641
  uint32_t local_idx = (uint32_t)((full_hash ^ (full_hash >> 32)) & STRIPE_MASK);
572
642
  uint32_t global_idx = (stripe * STRIPE_SIZE) + local_idx;
573
643
  uint32_t start_idx = local_idx;
644
+
645
+ // Minimal stability hardening: protect canonical-registry probing with stripe lock
646
+ // to avoid racing against concurrent tombstoning/reclamation.
647
+ fast_lock_backoff(&stripe_locks[stripe].flag);
574
648
  for (;;) {
575
649
  LQFTNode* slot = registry[global_idx];
576
650
  if (slot == NULL) break;
577
- if (slot != TOMBSTONE && slot->full_hash_val == full_hash) {
578
- ATOMIC_INC(&slot->ref_count);
651
+ if (slot != TOMBSTONE && slot->full_hash_val == full_hash && node_matches_signature(slot, value_ptr, key_hash, children)) {
652
+ ATOMIC_INC(&slot->ref_count);
653
+ fast_unlock(&stripe_locks[stripe].flag);
579
654
  return slot;
580
655
  }
581
656
  local_idx = (local_idx + 1) & STRIPE_MASK;
582
657
  global_idx = (stripe * STRIPE_SIZE) + local_idx;
583
- if (local_idx == start_idx) break;
658
+ if (local_idx == start_idx) break;
584
659
  }
660
+ fast_unlock(&stripe_locks[stripe].flag);
661
+
585
662
  LQFTNode* new_node = create_node(value_ptr ? (void*)portable_strdup(value_ptr) : NULL, key_hash, children, full_hash);
586
663
  if (!new_node) return NULL;
587
- new_node->ref_count = 1;
664
+ new_node->ref_count = 1;
665
+
588
666
  fast_lock_backoff(&stripe_locks[stripe].flag);
589
667
  local_idx = (uint32_t)((full_hash ^ (full_hash >> 32)) & STRIPE_MASK);
590
668
  global_idx = (stripe * STRIPE_SIZE) + local_idx;
@@ -594,7 +672,7 @@ LQFTNode* get_canonical_v2(const char* value_ptr, uint64_t key_hash, LQFTNode**
594
672
  LQFTNode* slot = registry[global_idx];
595
673
  if (slot == NULL) break;
596
674
  if (slot == TOMBSTONE) { if (first_tombstone == -1) first_tombstone = (int)local_idx; }
597
- else if (slot->full_hash_val == full_hash) {
675
+ else if (slot->full_hash_val == full_hash && node_matches_signature(slot, value_ptr, key_hash, children)) {
598
676
  ATOMIC_INC(&slot->ref_count);
599
677
  fast_unlock(&stripe_locks[stripe].flag);
600
678
  if (new_node->value) free(new_node->value);
@@ -745,8 +823,18 @@ char* core_search(uint64_t h, LQFTNode* root) {
745
823
  }
746
824
 
747
825
  static void c_internal_insert_rw(uint64_t h, const char* val_str) {
748
- uint64_t pre = fnv1a_update(FNV_OFFSET_BASIS, "leaf:", 5);
749
- pre = fnv1a_update(pre, val_str, strlen(val_str));
826
+ // Small TLS cache avoids repeated value hashing for hot constants (e.g. "x", "active").
827
+ static THREAD_LOCAL const char* last_val_ptr = NULL;
828
+ static THREAD_LOCAL uint64_t last_pre = 0;
829
+ uint64_t pre;
830
+ if (val_str == last_val_ptr) {
831
+ pre = last_pre;
832
+ } else {
833
+ pre = fnv1a_update(FNV_OFFSET_BASIS, "leaf:", 5);
834
+ pre = fnv1a_update(pre, val_str, strlen(val_str));
835
+ last_val_ptr = val_str;
836
+ last_pre = pre;
837
+ }
750
838
  uint32_t shard = (uint32_t)((h >> 48) & ROOT_MASK);
751
839
  get_my_metrics()->logical_inserts++;
752
840
  int spin = 0;
@@ -976,6 +1064,13 @@ static PyObject* method_free_all(PyObject* self, PyObject* args) {
976
1064
  for (int i = 0; i < MAX_TRACKED_THREADS; i++) {
977
1065
  global_metrics_array[i].phys_added = 0; global_metrics_array[i].phys_freed = 0; global_metrics_array[i].logical_inserts = 0;
978
1066
  }
1067
+ #ifdef _MSC_VER
1068
+ _InterlockedExchange(&registered_threads_count, 0);
1069
+ _InterlockedIncrement(&global_arena_epoch);
1070
+ #else
1071
+ __sync_lock_test_and_set(&registered_threads_count, 0);
1072
+ __sync_add_and_fetch(&global_arena_epoch, 1);
1073
+ #endif
979
1074
  for(int i = NUM_STRIPES - 1; i >= 0; i--) fast_unlock(&stripe_locks[i].flag);
980
1075
  for(int i = NUM_ROOTS - 1; i >= 0; i--) {
981
1076
  global_roots[i].root = NULL;
@@ -1,7 +1,12 @@
1
- import hashlib
2
- import psutil
3
1
  import os
4
2
  import sys
3
+ import hashlib
4
+ import threading
5
+
6
+ try:
7
+ import psutil
8
+ except Exception:
9
+ psutil = None
5
10
 
6
11
  # ---------------------------------------------------------
7
12
  # STRICT NATIVE ENTERPRISE WRAPPER (v1.0.5)
@@ -18,14 +23,21 @@ except ImportError:
18
23
  sys.exit(1)
19
24
 
20
25
  class LQFT:
26
+ _instance_lock = threading.Lock()
27
+ _live_instances = 0
28
+
21
29
  # F-03 & F-04: Restored migration_threshold to sync API signatures across the suite
22
30
  def __init__(self, migration_threshold=50000):
23
31
  self.is_native = True
24
- self.auto_purge_enabled = True
25
- self.max_memory_mb = 1000.0
32
+ # Keep destructive purge opt-in; global C-engine state can be shared by multiple wrappers.
33
+ self.auto_purge_enabled = False
34
+ self.max_memory_mb = 1000.0
26
35
  self.total_ops = 0
27
- self.migration_threshold = migration_threshold
28
- self._process = psutil.Process(os.getpid())
36
+ self.migration_threshold = migration_threshold
37
+ self._process = psutil.Process(os.getpid()) if psutil else None
38
+ self._closed = False
39
+ with LQFT._instance_lock:
40
+ LQFT._live_instances += 1
29
41
 
30
42
  def _validate_type(self, key, value=None):
31
43
  if not isinstance(key, str):
@@ -34,13 +46,30 @@ class LQFT:
34
46
  raise TypeError(f"LQFT values must be strings. Received: {type(value).__name__}")
35
47
 
36
48
  def _get_64bit_hash(self, key):
37
- return int(hashlib.md5(str(key).encode()).hexdigest()[:16], 16)
49
+ # Deterministic 64-bit hash keeps key mapping stable across processes/runs.
50
+ return int.from_bytes(hashlib.blake2b(key.encode(), digest_size=8).digest(), "little")
51
+
52
+ def _current_memory_mb(self):
53
+ if self._process is None:
54
+ return 0.0
55
+ try:
56
+ return self._process.memory_info().rss / (1024 * 1024)
57
+ except Exception:
58
+ return 0.0
38
59
 
39
60
  def set_auto_purge_threshold(self, threshold: float):
40
61
  self.max_memory_mb = threshold
41
62
 
42
63
  def purge(self):
43
- current_mb = self._process.memory_info().rss / (1024 * 1024)
64
+ current_mb = self._current_memory_mb()
65
+ with LQFT._instance_lock:
66
+ live = LQFT._live_instances
67
+ if live > 1:
68
+ print(
69
+ f"\n[⚠️ CIRCUIT Breaker] Memory {current_mb:.1f} MB but purge skipped "
70
+ f"because {live} LQFT instances are active (shared global engine state)."
71
+ )
72
+ return
44
73
  print(f"\n[⚠️ CIRCUIT Breaker] Engine exceeded limit (Currently {current_mb:.1f} MB). Auto-Purging!")
45
74
  self.clear()
46
75
 
@@ -55,6 +84,7 @@ class LQFT:
55
84
  return stats.get('logical_inserts', 0)
56
85
 
57
86
  def clear(self):
87
+ # Global clear (shared native state). Keep explicit to avoid accidental data loss.
58
88
  return lqft_c_engine.free_all()
59
89
 
60
90
  def insert(self, key, value):
@@ -63,7 +93,7 @@ class LQFT:
63
93
 
64
94
  # Heuristic Circuit Breaker check
65
95
  if self.auto_purge_enabled and self.total_ops % 5000 == 0:
66
- current_mb = self._process.memory_info().rss / (1024 * 1024)
96
+ current_mb = self._current_memory_mb()
67
97
  if current_mb >= self.max_memory_mb:
68
98
  self.purge()
69
99
 
@@ -97,8 +127,13 @@ class LQFT:
97
127
  self.delete(key)
98
128
 
99
129
  def __del__(self):
100
- try: self.clear()
101
- except: pass
130
+ try:
131
+ if not self._closed:
132
+ with LQFT._instance_lock:
133
+ LQFT._live_instances = max(0, LQFT._live_instances - 1)
134
+ self._closed = True
135
+ except Exception:
136
+ pass
102
137
 
103
138
  def status(self):
104
139
  return {
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lqft-python-engine
3
- Version: 1.0.5
3
+ Version: 1.0.6
4
4
  Summary: LQFT Engine: 14.3M Ops/sec O(1) Time | O(Σ) Space Data Structure
5
5
  Home-page: https://github.com/ParjadM/Log-Quantum-Fractal-Tree-LQFT-
6
6
  Author: Parjad Minooei
@@ -3,7 +3,7 @@ import os
3
3
  import sys
4
4
 
5
5
  # ---------------------------------------------------------
6
- # LQFT BUILD SYSTEM - V1.0.5 (Apple Silicon M3 Hotfix)
6
+ # LQFT BUILD SYSTEM - V1.0.6 (Apple Silicon M3 Hotfix)
7
7
  # Architect: Parjad Minooei
8
8
  # ---------------------------------------------------------
9
9
 
@@ -40,7 +40,7 @@ lqft_extension = Extension(
40
40
 
41
41
  setup(
42
42
  name="lqft-python-engine",
43
- version="1.0.5",
43
+ version="1.0.6",
44
44
  description="LQFT Engine: 14.3M Ops/sec O(1) Time | O(Σ) Space Data Structure",
45
45
  long_description=long_description,
46
46
  long_description_content_type="text/markdown",