File Coverage

lib/Doubly.xs
Criterion Covered Total %
statement 686 965 71.0
branch 297 630 47.1
condition n/a
subroutine n/a
pod n/a
total 983 1595 61.6


line stmt bran cond sub pod time code
1             /*
2             * Doubly - Thread-safe doubly linked list
3             *
4             * Architecture:
5             * - Lists are stored in a global registry keyed by integer ID
6             * - Perl objects only hold the list ID, not raw pointers
7             * - When Perl clones an SV across threads, it just clones the ID
8             * - All operations look up the list by ID under mutex protection
9             * - This avoids the "cloned pointer to freed memory" problem
10             */
11              
12             #define PERL_NO_GET_CONTEXT
13             #include "EXTERN.h"
14             #include "perl.h"
15             #include "XSUB.h"
16              
17             /* Node structure - stores data as a raw C string for cross-thread safety */
18             typedef struct DoublyNode {
19             char* data; /* Serialized string data */
20             STRLEN data_len; /* Length of data */
21             int is_number; /* Flag if original was numeric */
22             int is_frozen; /* Flag if data was frozen with Storable */
23             NV num_value; /* Numeric value if applicable */
24             long node_id; /* Unique node ID for stable references */
25             struct DoublyNode* next;
26             struct DoublyNode* prev;
27             } DoublyNode;
28              
29             /* Global node ID counter */
30             static long next_node_id = 1;
31              
32             /* List header - tracks the list (position is stored per-object in Perl) */
33             typedef struct DoublyList {
34             DoublyNode* head; /* First node */
35             DoublyNode* tail; /* Last node */
36             int length;
37             int refcount; /* Number of Perl references */
38             int destroyed; /* Flag to mark as destroyed */
39             } DoublyList;
40              
41             /* Registry of all lists - these are truly global (not thread-local) */
42             #define MAX_LISTS 65536
43             static DoublyList* list_registry[MAX_LISTS] = {NULL};
44             static int next_list_id = 1;
45             static int registry_initialized = 0;
46              
47             #ifdef USE_ITHREADS
48             static perl_mutex shared_mutex;
49             static int mutex_initialized = 0;
50              
51             #define SHARED_LOCK() MUTEX_LOCK(&shared_mutex)
52             #define SHARED_UNLOCK() MUTEX_UNLOCK(&shared_mutex)
53             #else
54             #define SHARED_LOCK()
55             #define SHARED_UNLOCK()
56             #endif
57              
58             /* Initialize the mutex (called once per process, not per thread) */
59 41           static void _init_shared(pTHX) {
60             #ifdef USE_ITHREADS
61             /* Only initialize once across all threads */
62             if (!mutex_initialized) {
63             MUTEX_INIT(&shared_mutex);
64             mutex_initialized = 1;
65             registry_initialized = 1;
66             }
67             #else
68 41 50         if (!registry_initialized) {
69 41           registry_initialized = 1;
70             }
71             #endif
72             /* DON'T reinitialize registry - it's already zero-initialized */
73 41           }
74              
75             /* Allocate a new list ID */
76 2731           static int _alloc_list_id(void) {
77 2731           int id = next_list_id;
78 2731           int tries = 0;
79            
80 2731 50         while (list_registry[id % MAX_LISTS] != NULL && tries < MAX_LISTS) {
    0          
81 0           id++;
82 0           tries++;
83             }
84            
85 2731 50         if (tries >= MAX_LISTS) {
86 0           return -1; /* No free slots */
87             }
88            
89 2731           next_list_id = id + 1;
90 2731           return id % MAX_LISTS;
91             }
92              
93             /* Get list by ID - must be called with lock held */
94 124110           static DoublyList* _get_list(int id) {
95 124110 50         if (id < 0 || id >= MAX_LISTS) {
    50          
96 0           return NULL;
97             }
98 124110           return list_registry[id];
99             }
100              
101             /* Node structure now stores SV* directly - refs get shared_clone'd */
102             typedef struct DoublyNodeData {
103             SV* sv; /* The actual SV (shared if ref) */
104             } DoublyNodeData;
105              
106             /* Check if sharing is initialized (threads loaded) */
107 1029           static int _is_sharing_initialized(pTHX) {
108 1029           SV* init_sv = get_sv("Doubly::_sharing_initialized", 0);
109 1029 50         return (init_sv && SvTRUE(init_sv));
    50          
110             }
111              
112             /* Lock the ref storage array for thread safety */
113 0           static void _lock_ref_storage(pTHX) {
114 0           dSP;
115 0           AV* storage = get_av("Doubly::_ref_storage", 0);
116 0 0         if (!storage) return;
117            
118 0           ENTER;
119 0           SAVETMPS;
120 0 0         PUSHMARK(SP);
121 0 0         XPUSHs(sv_2mortal(newRV_inc((SV*)storage)));
122 0           PUTBACK;
123 0           call_pv("threads::shared::lock", G_DISCARD | G_EVAL);
124 0 0         FREETMPS;
125 0           LEAVE;
126             }
127              
128             /* Store a value into the shared storage array at a given index */
129 0           static void _store_in_shared_array(pTHX_ IV index, SV* value) {
130 0           dSP;
131            
132 0           ENTER;
133 0           SAVETMPS;
134 0 0         PUSHMARK(SP);
135 0 0         mXPUSHi(index);
136 0 0         XPUSHs(value);
137 0           PUTBACK;
138            
139             /* Call a Perl helper to do the assignment: $_ref_storage[$index] = $value */
140 0           call_pv("Doubly::_xs_store_ref", G_DISCARD | G_EVAL);
141            
142 0 0         FREETMPS;
143 0           LEAVE;
144 0           }
145              
146             /* Store a ref in Perl's shared storage, returns the ID */
147 1029           static IV _store_ref_in_perl(pTHX_ SV* sv) {
148 1029           dSP;
149             int count;
150 1029           IV id = -1;
151             SV* shared;
152             SV* id_sv;
153            
154             /* Check if sharing is initialized */
155 1029 50         if (!_is_sharing_initialized(aTHX)) {
156 1029           return -1;
157             }
158            
159             /* Call threads::shared::shared_clone to make the ref shared */
160 0           ENTER;
161 0           SAVETMPS;
162 0 0         PUSHMARK(SP);
163 0 0         XPUSHs(sv);
164 0           PUTBACK;
165            
166 0           count = call_pv("threads::shared::shared_clone", G_SCALAR | G_EVAL);
167            
168 0           SPAGAIN;
169            
170 0 0         if (count != 1 || SvTRUE(ERRSV)) {
    0          
    0          
171 0           PUTBACK;
172 0 0         FREETMPS;
173 0           LEAVE;
174 0           return -1;
175             }
176            
177 0           shared = POPs;
178 0           SvREFCNT_inc(shared);
179 0           PUTBACK;
180 0 0         FREETMPS;
181 0           LEAVE;
182            
183             /* Get ID counter */
184 0           id_sv = get_sv("Doubly::_ref_next_id", 0);
185 0 0         if (!id_sv) {
186 0           SvREFCNT_dec(shared);
187 0           return -1;
188             }
189            
190             /* Lock the storage */
191 0           _lock_ref_storage(aTHX);
192            
193             /* Get current ID and increment */
194 0           id = SvIV(id_sv);
195 0           sv_setiv(id_sv, id + 1);
196 0 0         if (SvMAGICAL(id_sv)) {
197 0           mg_set(id_sv);
198             }
199            
200             /* Store in the shared array via Perl helper */
201 0           _store_in_shared_array(aTHX_ id, shared);
202 0           SvREFCNT_dec(shared); /* Helper took a copy */
203            
204 0           return id;
205             }
206              
207             /* Retrieve a ref from Perl's shared storage by ID */
208 0           static SV* _get_ref_from_perl(pTHX_ IV id) {
209 0           dSP;
210             int count;
211 0           SV* result = &PL_sv_undef;
212            
213 0 0         if (id < 0 || !_is_sharing_initialized(aTHX)) {
    0          
214 0           return result;
215             }
216            
217 0           _lock_ref_storage(aTHX);
218            
219 0           ENTER;
220 0           SAVETMPS;
221 0 0         PUSHMARK(SP);
222 0 0         mXPUSHi(id);
223 0           PUTBACK;
224            
225 0           count = call_pv("Doubly::_xs_get_ref", G_SCALAR | G_EVAL);
226            
227 0           SPAGAIN;
228            
229 0 0         if (count == 1 && !SvTRUE(ERRSV)) {
    0          
    0          
230 0           SV* ret = POPs;
231 0 0         if (SvOK(ret)) {
232 0           result = newSVsv(ret);
233             }
234             }
235            
236 0           PUTBACK;
237 0 0         FREETMPS;
238 0           LEAVE;
239            
240 0           return result;
241             }
242              
243             /* Clear a ref from Perl's shared storage */
244 0           static void _clear_ref_in_perl(pTHX_ IV id) {
245 0           dSP;
246            
247 0 0         if (id < 0 || !_is_sharing_initialized(aTHX)) {
    0          
248 0           return;
249             }
250            
251 0           _lock_ref_storage(aTHX);
252            
253 0           ENTER;
254 0           SAVETMPS;
255 0 0         PUSHMARK(SP);
256 0 0         mXPUSHi(id);
257 0           PUTBACK;
258            
259 0           call_pv("Doubly::_xs_clear_ref", G_DISCARD | G_EVAL);
260            
261 0 0         FREETMPS;
262 0           LEAVE;
263             }
264              
265             /* Create a new node from SV */
266 111185           static DoublyNode* _new_node(pTHX_ SV* sv) {
267 111185           DoublyNode* node = (DoublyNode*)malloc(sizeof(DoublyNode));
268 111185           node->next = NULL;
269 111185           node->prev = NULL;
270 111185           node->data = NULL;
271 111185           node->data_len = 0;
272 111185           node->is_number = 0;
273 111185           node->num_value = 0.0;
274 111185           node->node_id = next_node_id++;
275            
276 111185 50         if (sv && SvOK(sv)) {
    100          
277 110479 100         if (SvROK(sv)) {
278             /* Reference - try shared storage first (for threaded perl) */
279 1017           IV ref_id = _store_ref_in_perl(aTHX_ sv);
280 1017 50         if (ref_id >= 0) {
281             /* Stored in Perl's shared storage - keep ID */
282 0           node->num_value = (NV)ref_id;
283 0           node->is_number = 2; /* 2 = reference ID in shared storage */
284             } else {
285             /* Not threaded - store SV* directly with refcount increment */
286 1017           SvREFCNT_inc(sv);
287 1017           node->data = (char*)sv; /* Store SV* as pointer */
288 1017           node->is_number = 3; /* 3 = direct SV* reference */
289             }
290 109462 100         } else if (SvNOK(sv) || SvIOK(sv)) {
    100          
291             /* Store as number */
292 109226           node->is_number = 1;
293 109226           node->num_value = SvNV(sv);
294             /* Also store string representation */
295             STRLEN len;
296 109226           const char* str = SvPV(sv, len);
297 109226           node->data = (char*)malloc(len + 1);
298 109226           Copy(str, node->data, len, char);
299 109226           node->data[len] = '\0';
300 109226           node->data_len = len;
301             } else {
302             /* String - store as C string */
303             STRLEN len;
304 236           const char* str = SvPV(sv, len);
305 236           node->data = (char*)malloc(len + 1);
306 236           Copy(str, node->data, len, char);
307 236           node->data[len] = '\0';
308 236           node->data_len = len;
309             }
310             }
311            
312 111185           return node;
313             }
314              
315             /* Free a node */
316 109308           static void _free_node(pTHX_ DoublyNode* node) {
317 109308 50         if (node) {
318 109308 50         if (node->is_number == 2) {
319             /* It's a reference ID - clear from Perl storage */
320 0 0         if (!PL_dirty) {
321 0           IV ref_id = (IV)node->num_value;
322 0           _clear_ref_in_perl(aTHX_ ref_id);
323             }
324 109308 100         } else if (node->is_number == 3) {
325             /* It's a direct SV* - decrement refcount */
326 1000 50         if (!PL_dirty && node->data) {
    50          
327 1000           SV* sv = (SV*)node->data;
328 1000           SvREFCNT_dec(sv);
329             }
330 108308 100         } else if (node->data) {
331 108305           free(node->data);
332             }
333 109308           free(node);
334             }
335 109308           }
336              
337             /* Convert node data back to SV */
338 836           static SV* _node_to_sv(pTHX_ DoublyNode* node) {
339 836 50         if (!node) {
340 0           return newSVsv(&PL_sv_undef);
341             }
342            
343 836 50         if (node->is_number == 2) {
344             /* It's a reference ID - retrieve from Perl storage */
345 0           IV ref_id = (IV)node->num_value;
346 0           return _get_ref_from_perl(aTHX_ ref_id);
347             }
348            
349 836 100         if (node->is_number == 3) {
350             /* It's a direct SV* reference - increment refcount and return it */
351 39           SV* sv = (SV*)node->data;
352 39           SvREFCNT_inc(sv);
353 39           return sv;
354             }
355            
356 797 100         if (node->is_number == 1) {
357 791           return newSVnv(node->num_value);
358             }
359            
360 6 100         if (!node->data) {
361 1           return newSVsv(&PL_sv_undef);
362             }
363            
364 5           return newSVpvn(node->data, node->data_len);
365             }
366              
367             /* Create a new list */
368 2731           static int _new_list(pTHX_ SV* data) {
369             DoublyList* list;
370             int id;
371              
372             SHARED_LOCK();
373              
374 2731           id = _alloc_list_id();
375 2731 50         if (id < 0) {
376             SHARED_UNLOCK();
377 0           croak("Too many shared lists");
378             }
379              
380 2731           list = (DoublyList*)malloc(sizeof(DoublyList));
381 2731           list->head = _new_node(aTHX_ data);
382 2731           list->tail = list->head;
383 2731           list->length = SvOK(data) ? 1 : 0;
384 2731           list->refcount = 1;
385 2731           list->destroyed = 0;
386              
387 2731           list_registry[id] = list;
388              
389             SHARED_UNLOCK();
390              
391 2731           return id;
392             }
393              
394             /* Increment reference count */
395 1740           static void _incref(int id) {
396             DoublyList* list;
397            
398             SHARED_LOCK();
399 1740           list = _get_list(id);
400 1740 50         if (list) {
401 1740           list->refcount++;
402             }
403             SHARED_UNLOCK();
404 1740           }
405              
406             /* Decrement reference count, free if zero */
407 4471           static void _decref(pTHX_ int id) {
408             DoublyList* list;
409             DoublyNode* node;
410             DoublyNode* next;
411             /* Collect SVs that need decrementing after we release the lock */
412 4471           SV** sv_to_dec = NULL;
413 4471           IV* ref_ids_to_clear = NULL;
414 4471           int sv_count = 0;
415 4471           int ref_count = 0;
416             int i;
417            
418             SHARED_LOCK();
419 4471           list = _get_list(id);
420 4471 50         if (list) {
421 4471           list->refcount--;
422 4471 100         if (list->refcount <= 0) {
423             /* First pass: count nodes that need special cleanup */
424 2731           int total_nodes = 0;
425 2731           node = list->head;
426 4608 100         while (node) {
427 1877 100         if (node->is_number == 3 && !PL_dirty && node->data) {
    50          
    50          
428 27           total_nodes++;
429 1850 50         } else if (node->is_number == 2 && !PL_dirty) {
    0          
430 0           total_nodes++;
431             }
432 1877           node = node->next;
433             }
434            
435             /* Allocate arrays if needed */
436 2731 100         if (total_nodes > 0) {
437 11           sv_to_dec = (SV**)malloc(total_nodes * sizeof(SV*));
438 11           ref_ids_to_clear = (IV*)malloc(total_nodes * sizeof(IV));
439             }
440            
441             /* Second pass: collect refs and free nodes */
442 2731           node = list->head;
443 4608 100         while (node) {
444 1877           next = node->next;
445 1877 50         if (node->is_number == 2 && !PL_dirty) {
    0          
446             /* Collect ref ID for later clearing */
447 0           ref_ids_to_clear[ref_count++] = (IV)node->num_value;
448 1877 100         } else if (node->is_number == 3 && !PL_dirty && node->data) {
    50          
    50          
449             /* Collect SV* for later decrement */
450 27           sv_to_dec[sv_count++] = (SV*)node->data;
451 27           node->data = NULL; /* Prevent _free_node from freeing it */
452 1850 50         } else if (node->is_number != 2 && node->is_number != 3 && node->data) {
    50          
    100          
453 1843           free(node->data);
454             }
455 1877           free(node);
456 1877           node = next;
457             }
458 2731           free(list);
459 2731           list_registry[id] = NULL;
460             }
461             }
462             SHARED_UNLOCK();
463            
464             /* Now safely decrement SVs without holding the lock */
465 4498 100         for (i = 0; i < sv_count; i++) {
466 27           SvREFCNT_dec(sv_to_dec[i]);
467             }
468 4471 100         if (sv_to_dec) free(sv_to_dec);
469            
470             /* And clear ref IDs */
471 4471 50         for (i = 0; i < ref_count; i++) {
472 0           _clear_ref_in_perl(aTHX_ ref_ids_to_clear[i]);
473             }
474 4471 100         if (ref_ids_to_clear) free(ref_ids_to_clear);
475 4471           }
476              
477             /* Get length */
478 124           static int _list_length(int id) {
479             DoublyList* list;
480 124           int len = 0;
481            
482             SHARED_LOCK();
483 124           list = _get_list(id);
484 124 50         if (list && !list->destroyed) {
    50          
485 124           len = list->length;
486             }
487             SHARED_UNLOCK();
488            
489 124           return len;
490             }
491              
492             /* Get node at position - must be called with lock held */
493 6           static DoublyNode* _get_node_at_pos(DoublyList* list, int pos) {
494             DoublyNode* node;
495             int i;
496              
497 6 50         if (!list || list->destroyed || !list->head) {
    50          
    50          
498 0           return NULL;
499             }
500              
501 6           node = list->head;
502 12 100         for (i = 0; i < pos && node && node->next; i++) {
    50          
    100          
503 6           node = node->next;
504             }
505              
506 6           return node;
507             }
508              
509             /* Get node by ID - must be called with lock held */
510 964           static DoublyNode* _get_node_by_id(DoublyList* list, long node_id) {
511             DoublyNode* node;
512              
513 964 50         if (!list || list->destroyed || !list->head) {
    50          
    50          
514 0           return NULL;
515             }
516              
517 964           node = list->head;
518 304254 100         while (node) {
519 304253 100         if (node->node_id == node_id) {
520 963           return node;
521             }
522 303290           node = node->next;
523             }
524              
525 1           return NULL;
526             }
527              
528             /* Get position of node by ID - must be called with lock held */
529 0           static int _get_pos_by_node_id(DoublyList* list, long node_id) {
530             DoublyNode* node;
531 0           int pos = 0;
532              
533 0 0         if (!list || list->destroyed || !list->head) {
    0          
    0          
534 0           return 0;
535             }
536              
537 0           node = list->head;
538 0 0         while (node) {
539 0 0         if (node->node_id == node_id) {
540 0           return pos;
541             }
542 0           node = node->next;
543 0           pos++;
544             }
545              
546 0           return 0;
547             }
548              
549             /* Get data at position */
550 0           static SV* _list_data_at_pos(pTHX_ int id, int pos) {
551             DoublyList* list;
552             DoublyNode* node;
553 0           SV* result = &PL_sv_undef;
554              
555             SHARED_LOCK();
556 0           list = _get_list(id);
557 0 0         if (list && !list->destroyed) {
    0          
558 0           node = _get_node_at_pos(list, pos);
559 0 0         if (node) {
560 0           result = _node_to_sv(aTHX_ node);
561             }
562             }
563             SHARED_UNLOCK();
564              
565 0           return result;
566             }
567              
568             /* Set data at position */
569 0           static void _list_set_data_at_pos(pTHX_ int id, int pos, SV* sv) {
570             DoublyList* list;
571             DoublyNode* node;
572              
573             SHARED_LOCK();
574 0           list = _get_list(id);
575 0 0         if (list && !list->destroyed) {
    0          
576 0           node = _get_node_at_pos(list, pos);
577 0 0         if (node) {
578             /* Free old data */
579 0 0         if (node->is_number == 2) {
580 0           IV old_id = (IV)node->num_value;
581 0           _clear_ref_in_perl(aTHX_ old_id);
582 0 0         } else if (node->is_number == 3) {
583 0 0         if (node->data) {
584 0           SV* old_sv = (SV*)node->data;
585 0           SvREFCNT_dec(old_sv);
586             }
587 0 0         } else if (node->data) {
588 0           free(node->data);
589             }
590 0           node->data = NULL;
591              
592             /* Store new data */
593 0           node->data_len = 0;
594 0           node->is_number = 0;
595 0           node->num_value = 0.0;
596              
597 0 0         if (sv && SvOK(sv)) {
    0          
598 0 0         if (SvROK(sv)) {
599 0           IV ref_id = _store_ref_in_perl(aTHX_ sv);
600 0 0         if (ref_id >= 0) {
601 0           node->num_value = (NV)ref_id;
602 0           node->is_number = 2;
603             } else {
604 0           SvREFCNT_inc(sv);
605 0           node->data = (char*)sv;
606 0           node->is_number = 3;
607             }
608 0 0         } else if (SvNOK(sv) || SvIOK(sv)) {
    0          
609 0           node->is_number = 1;
610 0           node->num_value = SvNV(sv);
611             STRLEN len;
612 0           const char* str = SvPV(sv, len);
613 0           node->data = (char*)malloc(len + 1);
614 0           Copy(str, node->data, len, char);
615 0           node->data[len] = '\0';
616 0           node->data_len = len;
617             } else {
618             STRLEN len;
619 0           const char* str = SvPV(sv, len);
620 0           node->data = (char*)malloc(len + 1);
621 0           Copy(str, node->data, len, char);
622 0           node->data[len] = '\0';
623 0           node->data_len = len;
624             }
625             }
626             }
627             }
628             SHARED_UNLOCK();
629 0           }
630              
631             /* Forward declarations */
632             static void _list_add(pTHX_ int id, SV* data);
633              
634             /* Get end position (length - 1, or 0 for empty) */
635 0           static int _list_end_pos(int id) {
636             DoublyList* list;
637 0           int pos = 0;
638              
639             SHARED_LOCK();
640 0           list = _get_list(id);
641 0 0         if (list && !list->destroyed && list->length > 0) {
    0          
    0          
642 0           pos = list->length - 1;
643             }
644             SHARED_UNLOCK();
645              
646 0           return pos;
647             }
648              
649             /* Get head node ID */
650 3919           static long _list_head_node_id(int id) {
651             DoublyList* list;
652 3919           long node_id = 0;
653              
654             SHARED_LOCK();
655 3919           list = _get_list(id);
656 3919 50         if (list && !list->destroyed && list->head) {
    50          
    50          
657 3919           node_id = list->head->node_id;
658             }
659             SHARED_UNLOCK();
660              
661 3919           return node_id;
662             }
663              
664             /* Get tail node ID */
665 748           static long _list_tail_node_id(int id) {
666             DoublyList* list;
667 748           long node_id = 0;
668              
669             SHARED_LOCK();
670 748           list = _get_list(id);
671 748 50         if (list && !list->destroyed && list->tail) {
    50          
    50          
672 748           node_id = list->tail->node_id;
673             }
674             SHARED_UNLOCK();
675              
676 748           return node_id;
677             }
678              
679             /* Get next node ID */
680 244           static long _list_next_node_id(int id, long current_node_id) {
681             DoublyList* list;
682             DoublyNode* node;
683 244           long next_id = 0;
684              
685             SHARED_LOCK();
686 244           list = _get_list(id);
687 244 50         if (list && !list->destroyed) {
    50          
688 244           node = _get_node_by_id(list, current_node_id);
689 244 50         if (node && node->next) {
    100          
690 243           next_id = node->next->node_id;
691             }
692             }
693             SHARED_UNLOCK();
694              
695 244           return next_id;
696             }
697              
698             /* Get prev node ID */
699 207           static long _list_prev_node_id(int id, long current_node_id) {
700             DoublyList* list;
701             DoublyNode* node;
702 207           long prev_id = 0;
703              
704             SHARED_LOCK();
705 207           list = _get_list(id);
706 207 50         if (list && !list->destroyed) {
    50          
707 207           node = _get_node_by_id(list, current_node_id);
708 207 50         if (node && node->prev) {
    50          
709 207           prev_id = node->prev->node_id;
710             }
711             }
712             SHARED_UNLOCK();
713              
714 207           return prev_id;
715             }
716              
717             /* Get data by node ID */
718 86           static SV* _list_data_by_node_id(pTHX_ int id, long node_id) {
719             DoublyList* list;
720             DoublyNode* node;
721 86           SV* result = &PL_sv_undef;
722              
723             SHARED_LOCK();
724 86           list = _get_list(id);
725 86 50         if (list && !list->destroyed) {
    100          
726 85           node = _get_node_by_id(list, node_id);
727 85 100         if (node) {
728 84           result = _node_to_sv(aTHX_ node);
729             }
730             }
731             SHARED_UNLOCK();
732              
733 86           return result;
734             }
735              
736             /* Set data by node ID */
737 4           static void _list_set_data_by_node_id(pTHX_ int id, long node_id, SV* sv) {
738             DoublyList* list;
739             DoublyNode* node;
740              
741             SHARED_LOCK();
742 4           list = _get_list(id);
743 4 50         if (list && !list->destroyed) {
    50          
744 4           node = _get_node_by_id(list, node_id);
745 4 50         if (node) {
746             /* Free old data */
747 4 50         if (node->is_number == 2) {
748 0           IV old_id = (IV)node->num_value;
749 0           _clear_ref_in_perl(aTHX_ old_id);
750 4 100         } else if (node->is_number == 3) {
751 2 50         if (node->data) {
752 2           SV* old_sv = (SV*)node->data;
753 2           SvREFCNT_dec(old_sv);
754             }
755 2 100         } else if (node->data) {
756 1           free(node->data);
757             }
758 4           node->data = NULL;
759              
760             /* Store new data */
761 4           node->data_len = 0;
762 4           node->is_number = 0;
763 4           node->num_value = 0.0;
764              
765 4 50         if (sv && SvOK(sv)) {
    50          
766 4 100         if (SvROK(sv)) {
767 3           IV ref_id = _store_ref_in_perl(aTHX_ sv);
768 3 50         if (ref_id >= 0) {
769 0           node->num_value = (NV)ref_id;
770 0           node->is_number = 2;
771             } else {
772 3           SvREFCNT_inc(sv);
773 3           node->data = (char*)sv;
774 3           node->is_number = 3;
775             }
776 1 50         } else if (SvNOK(sv) || SvIOK(sv)) {
    50          
777 1           node->is_number = 1;
778 1           node->num_value = SvNV(sv);
779             STRLEN len;
780 1           const char* str = SvPV(sv, len);
781 1           node->data = (char*)malloc(len + 1);
782 1           Copy(str, node->data, len, char);
783 1           node->data[len] = '\0';
784 1           node->data_len = len;
785             } else {
786             STRLEN len;
787 0           const char* str = SvPV(sv, len);
788 0           node->data = (char*)malloc(len + 1);
789 0           Copy(str, node->data, len, char);
790 0           node->data[len] = '\0';
791 0           node->data_len = len;
792             }
793             }
794             }
795             }
796             SHARED_UNLOCK();
797 4           }
798              
799             /* Check if node_id is at start */
800 6           static int _list_is_start_node(int id, long node_id) {
801             DoublyList* list;
802 6           int is = 0;
803              
804             SHARED_LOCK();
805 6           list = _get_list(id);
806 6 50         if (list && !list->destroyed && list->head) {
    50          
    50          
807 6           is = (list->head->node_id == node_id) ? 1 : 0;
808             }
809             SHARED_UNLOCK();
810              
811 6           return is;
812             }
813              
814             /* Check if node_id is at end */
815 4           static int _list_is_end_node(int id, long node_id) {
816             DoublyList* list;
817 4           int is = 0;
818              
819             SHARED_LOCK();
820 4           list = _get_list(id);
821 4 50         if (list && !list->destroyed && list->tail) {
    50          
    50          
822 4           is = (list->tail->node_id == node_id) ? 1 : 0;
823             }
824             SHARED_UNLOCK();
825              
826 4           return is;
827             }
828              
829             /* Insert before node ID - returns new node's ID */
830 103           static long _list_insert_before_node_id(pTHX_ int id, long node_id, SV* data) {
831             DoublyList* list;
832             DoublyNode* new_node;
833             DoublyNode* node;
834 103           long new_id = 0;
835              
836             SHARED_LOCK();
837 103           list = _get_list(id);
838 103 50         if (list && !list->destroyed) {
    50          
839 103 100         if (list->length == 0) {
840             SHARED_UNLOCK();
841 1           _list_add(aTHX_ id, data);
842 1           return _list_head_node_id(id);
843             }
844 102           node = _get_node_by_id(list, node_id);
845 102 50         if (node) {
846 102           new_node = _new_node(aTHX_ data);
847 102           new_id = new_node->node_id;
848              
849 102 50         if (node->prev) {
850 0           node->prev->next = new_node;
851 0           new_node->prev = node->prev;
852             } else {
853 102           list->head = new_node;
854             }
855 102           new_node->next = node;
856 102           node->prev = new_node;
857 102           list->length++;
858             }
859             }
860             SHARED_UNLOCK();
861              
862 102           return new_id;
863             }
864              
865             /* Insert after node ID - returns new node's ID */
866 115           static long _list_insert_after_node_id(pTHX_ int id, long node_id, SV* data) {
867             DoublyList* list;
868             DoublyNode* new_node;
869             DoublyNode* node;
870 115           long new_id = 0;
871              
872             SHARED_LOCK();
873 115           list = _get_list(id);
874 115 50         if (list && !list->destroyed) {
    50          
875 115 100         if (list->length == 0) {
876             SHARED_UNLOCK();
877 1           _list_add(aTHX_ id, data);
878 1           return _list_head_node_id(id);
879             }
880 114           node = _get_node_by_id(list, node_id);
881 114 50         if (node) {
882 114           new_node = _new_node(aTHX_ data);
883 114           new_id = new_node->node_id;
884              
885 114 50         if (node->next) {
886 0           node->next->prev = new_node;
887 0           new_node->next = node->next;
888             } else {
889 114           list->tail = new_node;
890             }
891 114           new_node->prev = node;
892 114           node->next = new_node;
893 114           list->length++;
894             }
895             }
896             SHARED_UNLOCK();
897              
898 114           return new_id;
899             }
900              
901             /* Remove by node ID - also returns the next node's ID (or 0) */
902             typedef struct {
903             SV* data;
904             long next_node_id;
905             } RemoveResult;
906              
907 210           static RemoveResult _list_remove_by_node_id_ex(pTHX_ int id, long node_id) {
908             DoublyList* list;
909             DoublyNode* node;
910 210           RemoveResult result = { &PL_sv_undef, 0 };
911              
912             SHARED_LOCK();
913 210           list = _get_list(id);
914 210 50         if (list && !list->destroyed && list->length > 0) {
    50          
    100          
915 208           node = _get_node_by_id(list, node_id);
916 208 50         if (node) {
917 208           result.data = _node_to_sv(aTHX_ node);
918            
919             /* Get next node ID before we free it */
920 208 100         if (node->next) {
921 204           result.next_node_id = node->next->node_id;
922 4 100         } else if (node->prev) {
923 1           result.next_node_id = node->prev->node_id;
924             }
925              
926 208 100         if (node->prev && node->next) {
    100          
927             /* Middle node */
928 2           node->prev->next = node->next;
929 2           node->next->prev = node->prev;
930 2           _free_node(aTHX_ node);
931 2           list->length--;
932 206 100         } else if (node->prev) {
933             /* Tail node */
934 1           list->tail = node->prev;
935 1           list->tail->next = NULL;
936 1           _free_node(aTHX_ node);
937 1           list->length--;
938 205 100         } else if (node->next) {
939             /* Head node */
940 202           list->head = node->next;
941 202           list->head->prev = NULL;
942 202           _free_node(aTHX_ node);
943 202           list->length--;
944             } else {
945             /* Last node - just clear data */
946 3 50         if (node->data) {
947 3           free(node->data);
948 3           node->data = NULL;
949             }
950 3           node->data_len = 0;
951 3           node->is_number = 0;
952 3           list->length = 0;
953 3           result.next_node_id = 0;
954             }
955             }
956             }
957             SHARED_UNLOCK();
958              
959 210           return result;
960             }
961              
962             /* Wrapper for backward compatibility */
963 0           static SV* _list_remove_by_node_id(pTHX_ int id, long node_id) {
964 0           RemoveResult res = _list_remove_by_node_id_ex(aTHX_ id, node_id);
965 0           return res.data;
966             }
967              
968             /* Check if position is at start */
969 0           static int _list_is_start_pos(int id, int pos) {
970             (void)id; /* Position 0 is always start */
971 0           return (pos == 0) ? 1 : 0;
972             }
973              
974             /* Check if position is at end */
975 0           static int _list_is_end_pos(int id, int pos) {
976             DoublyList* list;
977 0           int is = 0;
978              
979             SHARED_LOCK();
980 0           list = _get_list(id);
981 0 0         if (list && !list->destroyed) {
    0          
982 0           is = (pos >= list->length - 1) ? 1 : 0;
983             }
984             SHARED_UNLOCK();
985              
986 0           return is;
987             }
988              
989             /* Add at end */
990 108624           static void _list_add(pTHX_ int id, SV* data) {
991             DoublyList* list;
992             DoublyNode* node;
993            
994             SHARED_LOCK();
995 108624           list = _get_list(id);
996 108624 50         if (list && !list->destroyed) {
    50          
997             /* Handle empty list (just set data on head node) */
998 108624 100         if (list->length == 0 && list->head) {
    50          
999             /* Free old data properly */
1000 690 50         if (list->head->is_number == 2) {
1001 0           IV old_id = (IV)list->head->num_value;
1002 0           _clear_ref_in_perl(aTHX_ old_id);
1003 690 50         } else if (list->head->is_number == 3) {
1004 0 0         if (list->head->data) {
1005 0           SV* old_sv = (SV*)list->head->data;
1006 0           SvREFCNT_dec(old_sv);
1007             }
1008 690 50         } else if (list->head->data) {
1009 0           free(list->head->data);
1010             }
1011 690           list->head->data = NULL;
1012            
1013             /* Store new data using same logic as _new_node */
1014 690           list->head->data_len = 0;
1015 690           list->head->is_number = 0;
1016 690           list->head->num_value = 0.0;
1017            
1018 690 50         if (data && SvOK(data)) {
    50          
1019 690 100         if (SvROK(data)) {
1020             /* Reference - try shared storage first */
1021 8           IV ref_id = _store_ref_in_perl(aTHX_ data);
1022 8 50         if (ref_id >= 0) {
1023 0           list->head->num_value = (NV)ref_id;
1024 0           list->head->is_number = 2;
1025             } else {
1026             /* Not threaded - store SV* directly */
1027 8           SvREFCNT_inc(data);
1028 8           list->head->data = (char*)data;
1029 8           list->head->is_number = 3;
1030             }
1031 682 100         } else if (SvNOK(data) || SvIOK(data)) {
    100          
1032             /* Store as number */
1033 664           list->head->is_number = 1;
1034 664           list->head->num_value = SvNV(data);
1035             STRLEN len;
1036 664           const char* str = SvPV(data, len);
1037 664           list->head->data = (char*)malloc(len + 1);
1038 664           Copy(str, list->head->data, len, char);
1039 664           list->head->data[len] = '\0';
1040 664           list->head->data_len = len;
1041             } else {
1042             /* String - store as C string */
1043             STRLEN len;
1044 18           const char* str = SvPV(data, len);
1045 18           list->head->data = (char*)malloc(len + 1);
1046 18           Copy(str, list->head->data, len, char);
1047 18           list->head->data[len] = '\0';
1048 18           list->head->data_len = len;
1049             }
1050             }
1051 690           list->length = 1;
1052             } else {
1053 107934           node = _new_node(aTHX_ data);
1054 107934           node->prev = list->tail;
1055 107934           list->tail->next = node;
1056 107934           list->tail = node;
1057 107934           list->length++;
1058             }
1059             }
1060             SHARED_UNLOCK();
1061 108624           }
1062              
1063             /* Remove from start */
1064 322           static SV* _list_remove_from_start(pTHX_ int id) {
1065             DoublyList* list;
1066             DoublyNode* old_head;
1067 322           SV* result = &PL_sv_undef;
1068              
1069             SHARED_LOCK();
1070 322           list = _get_list(id);
1071 322 50         if (list && !list->destroyed && list->head && list->length > 0) {
    50          
    50          
    100          
1072 320           old_head = list->head;
1073 320           result = _node_to_sv(aTHX_ old_head);
1074              
1075 320 100         if (old_head->next) {
1076 318           list->head = old_head->next;
1077 318           list->head->prev = NULL;
1078 318           _free_node(aTHX_ old_head);
1079 318           list->length--;
1080             } else {
1081             /* Last node - just clear data */
1082 2 50         if (old_head->data) {
1083 2           free(old_head->data);
1084 2           old_head->data = NULL;
1085             }
1086 2           old_head->data_len = 0;
1087 2           old_head->is_number = 0;
1088 2           list->length = 0;
1089             }
1090             }
1091             SHARED_UNLOCK();
1092              
1093 322           return result;
1094             }
1095              
1096             /* Remove from end */
1097 219           static SV* _list_remove_from_end(pTHX_ int id) {
1098             DoublyList* list;
1099             DoublyNode* old_tail;
1100 219           SV* result = &PL_sv_undef;
1101              
1102             SHARED_LOCK();
1103 219           list = _get_list(id);
1104 219 50         if (list && !list->destroyed && list->tail && list->length > 0) {
    50          
    50          
    50          
1105 219           old_tail = list->tail;
1106 219           result = _node_to_sv(aTHX_ old_tail);
1107              
1108 219 100         if (old_tail->prev) {
1109 217           list->tail = old_tail->prev;
1110 217           list->tail->next = NULL;
1111 217           _free_node(aTHX_ old_tail);
1112 217           list->length--;
1113             } else {
1114             /* Last node - just clear data */
1115 2 50         if (old_tail->data) {
1116 2           free(old_tail->data);
1117 2           old_tail->data = NULL;
1118             }
1119 2           old_tail->data_len = 0;
1120 2           old_tail->is_number = 0;
1121 2           list->length = 0;
1122             }
1123             }
1124             SHARED_UNLOCK();
1125              
1126 219           return result;
1127             }
1128              
1129             /* Remove node at position */
1130 4           static SV* _list_remove_at_pos(pTHX_ int id, int pos) {
1131             DoublyList* list;
1132             DoublyNode* node;
1133 4           SV* result = &PL_sv_undef;
1134              
1135             SHARED_LOCK();
1136 4           list = _get_list(id);
1137 4 50         if (list && !list->destroyed && list->length > 0) {
    50          
    100          
1138 3           node = _get_node_at_pos(list, pos);
1139 3 50         if (node) {
1140 3           result = _node_to_sv(aTHX_ node);
1141              
1142 3 100         if (node->prev && node->next) {
    50          
1143             /* Middle node */
1144 0           node->prev->next = node->next;
1145 0           node->next->prev = node->prev;
1146 0           _free_node(aTHX_ node);
1147 0           list->length--;
1148 3 100         } else if (node->prev) {
1149             /* Tail node */
1150 2           list->tail = node->prev;
1151 2           list->tail->next = NULL;
1152 2           _free_node(aTHX_ node);
1153 2           list->length--;
1154 1 50         } else if (node->next) {
1155             /* Head node */
1156 0           list->head = node->next;
1157 0           list->head->prev = NULL;
1158 0           _free_node(aTHX_ node);
1159 0           list->length--;
1160             } else {
1161             /* Last node - just clear data */
1162 1 50         if (node->data) {
1163 1           free(node->data);
1164 1           node->data = NULL;
1165             }
1166 1           node->data_len = 0;
1167 1           node->is_number = 0;
1168 1           list->length = 0;
1169             }
1170             }
1171             }
1172             SHARED_UNLOCK();
1173              
1174 4           return result;
1175             }
1176              
1177             /* Insert before position */
1178 0           static void _list_insert_before_pos(pTHX_ int id, int pos, SV* data) {
1179             DoublyList* list;
1180             DoublyNode* new_node;
1181             DoublyNode* node;
1182              
1183             SHARED_LOCK();
1184 0           list = _get_list(id);
1185 0 0         if (list && !list->destroyed) {
    0          
1186 0 0         if (list->length == 0) {
1187             /* Empty list - use _list_add */
1188             SHARED_UNLOCK();
1189 0           _list_add(aTHX_ id, data);
1190 0           return;
1191             }
1192 0           node = _get_node_at_pos(list, pos);
1193 0 0         if (node) {
1194 0           new_node = _new_node(aTHX_ data);
1195              
1196 0 0         if (node->prev) {
1197 0           node->prev->next = new_node;
1198 0           new_node->prev = node->prev;
1199             } else {
1200 0           list->head = new_node;
1201             }
1202 0           new_node->next = node;
1203 0           node->prev = new_node;
1204 0           list->length++;
1205             }
1206             }
1207             SHARED_UNLOCK();
1208             }
1209              
1210             /* Insert after position */
1211 0           static void _list_insert_after_pos(pTHX_ int id, int pos, SV* data) {
1212             DoublyList* list;
1213             DoublyNode* new_node;
1214             DoublyNode* node;
1215              
1216             SHARED_LOCK();
1217 0           list = _get_list(id);
1218 0 0         if (list && !list->destroyed) {
    0          
1219 0 0         if (list->length == 0) {
1220             /* Empty list - use _list_add */
1221             SHARED_UNLOCK();
1222 0           _list_add(aTHX_ id, data);
1223 0           return;
1224             }
1225 0           node = _get_node_at_pos(list, pos);
1226 0 0         if (node) {
1227 0           new_node = _new_node(aTHX_ data);
1228              
1229 0 0         if (node->next) {
1230 0           node->next->prev = new_node;
1231 0           new_node->next = node->next;
1232             } else {
1233 0           list->tail = new_node;
1234             }
1235 0           new_node->prev = node;
1236 0           node->next = new_node;
1237 0           list->length++;
1238             }
1239             }
1240             SHARED_UNLOCK();
1241             }
1242              
1243             /* Insert at start */
1244 315           static void _list_insert_at_start(pTHX_ int id, SV* data) {
1245             DoublyList* list;
1246             DoublyNode* new_node;
1247            
1248             SHARED_LOCK();
1249 315           list = _get_list(id);
1250 315 50         if (list && !list->destroyed) {
    50          
1251 315 100         if (list->length == 0) {
1252             /* Empty list - use node initialization logic for proper ref handling */
1253             /* Free old head data if any */
1254 13 50         if (list->head->is_number == 2) {
1255 0           IV old_id = (IV)list->head->num_value;
1256 0           _clear_ref_in_perl(aTHX_ old_id);
1257 13 50         } else if (list->head->is_number == 3) {
1258 0 0         if (list->head->data) {
1259 0           SV* old_sv = (SV*)list->head->data;
1260 0           SvREFCNT_dec(old_sv);
1261             }
1262 13 50         } else if (list->head->data) {
1263 0           free(list->head->data);
1264             }
1265             /* Reset and store new data properly */
1266 13           list->head->data = NULL;
1267 13           list->head->data_len = 0;
1268 13           list->head->is_number = 0;
1269 13           list->head->num_value = 0.0;
1270 13 50         if (data && SvOK(data)) {
    50          
1271 13 100         if (SvROK(data)) {
1272             /* Reference - try shared storage first */
1273 1           IV ref_id = _store_ref_in_perl(aTHX_ data);
1274 1 50         if (ref_id >= 0) {
1275 0           list->head->num_value = (NV)ref_id;
1276 0           list->head->is_number = 2;
1277             } else {
1278             /* Not threaded - store SV* directly */
1279 1           SvREFCNT_inc(data);
1280 1           list->head->data = (char*)data;
1281 1           list->head->is_number = 3;
1282             }
1283 12 50         } else if (SvNOK(data) || SvIOK(data)) {
    50          
1284 12           list->head->is_number = 1;
1285 12           list->head->num_value = SvNV(data);
1286             STRLEN len;
1287 12           const char* str = SvPV(data, len);
1288 12           list->head->data = (char*)malloc(len + 1);
1289 12           Copy(str, list->head->data, len, char);
1290 12           list->head->data[len] = '\0';
1291 12           list->head->data_len = len;
1292             } else {
1293             STRLEN len;
1294 0           const char* str = SvPV(data, len);
1295 0           list->head->data = (char*)malloc(len + 1);
1296 0           Copy(str, list->head->data, len, char);
1297 0           list->head->data[len] = '\0';
1298 0           list->head->data_len = len;
1299             }
1300             }
1301 13           list->length = 1;
1302             } else {
1303 302           new_node = _new_node(aTHX_ data);
1304 302           new_node->next = list->head;
1305 302           list->head->prev = new_node;
1306 302           list->head = new_node;
1307 302           list->length++;
1308             }
1309             }
1310             SHARED_UNLOCK();
1311 315           }
1312              
1313             /* Insert at end (same as add) */
1314 315           static void _list_insert_at_end(pTHX_ int id, SV* data) {
1315 315           _list_add(aTHX_ id, data);
1316 315           }
1317              
1318             /* Insert at position */
1319 3           static void _list_insert_at_pos(pTHX_ int id, int pos, SV* data) {
1320             DoublyList* list;
1321             DoublyNode* new_node;
1322             DoublyNode* node;
1323             int i;
1324            
1325             SHARED_LOCK();
1326 3           list = _get_list(id);
1327 3 50         if (list && !list->destroyed) {
    50          
1328 3 100         if (list->length == 0) {
1329             /* Empty list - delegate to _list_add for proper ref handling */
1330             SHARED_UNLOCK();
1331 1           _list_add(aTHX_ id, data);
1332 1           return;
1333             } else {
1334             /* Find the position (navigate pos steps from head) */
1335 2           node = list->head;
1336 3 50         for (i = 0; i < pos && node->next; i++) {
    100          
1337 1           node = node->next;
1338             }
1339            
1340 2           new_node = _new_node(aTHX_ data);
1341            
1342             /* Insert AFTER node (like Pointer's _insert_after) */
1343 2           new_node->next = node->next;
1344 2           new_node->prev = node;
1345 2 50         if (node->next) {
1346 0           node->next->prev = new_node;
1347             } else {
1348 2           list->tail = new_node;
1349             }
1350 2           node->next = new_node;
1351 2           list->length++;
1352             }
1353             }
1354             SHARED_UNLOCK();
1355             }
1356              
1357             /* Destroy list */
1358 2635           static void _list_destroy(pTHX_ int id) {
1359             DoublyList* list;
1360             DoublyNode* node;
1361             DoublyNode* next;
1362              
1363             SHARED_LOCK();
1364 2635           list = _get_list(id);
1365 2635 50         if (list && !list->destroyed) {
    50          
1366 2635           list->destroyed = 1;
1367              
1368             /* Free all nodes */
1369 2635           node = list->head;
1370 111201 100         while (node) {
1371 108566           next = node->next;
1372 108566           _free_node(aTHX_ node);
1373 108566           node = next;
1374             }
1375              
1376 2635           list->head = NULL;
1377 2635           list->tail = NULL;
1378 2635           list->length = 0;
1379             }
1380             SHARED_UNLOCK();
1381 2635           }
1382              
1383              
1384             MODULE = Doubly PACKAGE = Doubly
1385             PROTOTYPES: DISABLE
1386              
1387             BOOT:
1388 41           _init_shared(aTHX);
1389              
1390             SV*
1391             new(pkg, ...)
1392             SV* pkg
1393             PREINIT:
1394             int id;
1395             HV* self;
1396             SV* data;
1397             long node_id;
1398             #ifdef USE_ITHREADS
1399             UV owner_tid;
1400             #endif
1401             CODE:
1402 2731 100         data = (items > 1) ? ST(1) : &PL_sv_undef;
1403              
1404 2731           id = _new_list(aTHX_ data);
1405 2731           node_id = _list_head_node_id(id);
1406              
1407 2731           self = newHV();
1408 2731           hv_store(self, "_id", 3, newSViv(id), 0);
1409 2731           hv_store(self, "_node_id", 8, newSViv(node_id), 0); /* Node ID stored per-object */
1410             #ifdef USE_ITHREADS
1411             owner_tid = PTR2UV(PERL_GET_THX);
1412             hv_store(self, "_owner_tid", 10, newSVuv(owner_tid), 0);
1413             #endif
1414              
1415 2731           RETVAL = sv_bless(newRV_noinc((SV*)self), gv_stashpv("Doubly", GV_ADD));
1416             OUTPUT:
1417             RETVAL
1418              
1419             int
1420             length(self)
1421             SV* self
1422             CODE:
1423 124           HV* hash = (HV*)SvRV(self);
1424 124           SV** id_sv = hv_fetch(hash, "_id", 3, 0);
1425 124 50         int id = id_sv ? SvIV(*id_sv) : -1;
1426 124           RETVAL = _list_length(id);
1427             OUTPUT:
1428             RETVAL
1429              
1430             SV*
1431             data(self, ...)
1432             SV* self
1433             CODE:
1434 86           HV* hash = (HV*)SvRV(self);
1435 86           SV** id_sv = hv_fetch(hash, "_id", 3, 0);
1436 86           SV** node_id_sv = hv_fetch(hash, "_node_id", 8, 0);
1437 86 50         int id = id_sv ? SvIV(*id_sv) : -1;
1438 86 50         long node_id = node_id_sv ? SvIV(*node_id_sv) : 0;
1439              
1440 86 100         if (items > 1) {
1441 4           _list_set_data_by_node_id(aTHX_ id, node_id, ST(1));
1442             }
1443              
1444 86           RETVAL = _list_data_by_node_id(aTHX_ id, node_id);
1445             OUTPUT:
1446             RETVAL
1447              
1448             SV*
1449             start(self)
1450             SV* self
1451             PREINIT:
1452             HV* hash;
1453             HV* new_hash;
1454             SV** id_sv;
1455             int id;
1456             long node_id;
1457             #ifdef USE_ITHREADS
1458             SV** owner_tid_sv;
1459             #endif
1460             CODE:
1461 229           hash = (HV*)SvRV(self);
1462 229           id_sv = hv_fetch(hash, "_id", 3, 0);
1463 229 50         id = id_sv ? SvIV(*id_sv) : -1;
1464 229           node_id = _list_head_node_id(id);
1465              
1466             /* Create new object with head node_id */
1467 229           new_hash = newHV();
1468 229           hv_store(new_hash, "_id", 3, newSViv(id), 0);
1469 229           hv_store(new_hash, "_node_id", 8, newSViv(node_id), 0);
1470             #ifdef USE_ITHREADS
1471             owner_tid_sv = hv_fetch(hash, "_owner_tid", 10, 0);
1472             if (owner_tid_sv) {
1473             hv_store(new_hash, "_owner_tid", 10, newSVsv(*owner_tid_sv), 0);
1474             }
1475             #endif
1476 229           _incref(id);
1477              
1478 229           RETVAL = sv_bless(newRV_noinc((SV*)new_hash), gv_stashpv("Doubly", GV_ADD));
1479             OUTPUT:
1480             RETVAL
1481              
1482             SV*
1483             end(self)
1484             SV* self
1485             PREINIT:
1486             HV* hash;
1487             HV* new_hash;
1488             SV** id_sv;
1489             int id;
1490             long node_id;
1491             #ifdef USE_ITHREADS
1492             SV** owner_tid_sv;
1493             #endif
1494             CODE:
1495 209           hash = (HV*)SvRV(self);
1496 209           id_sv = hv_fetch(hash, "_id", 3, 0);
1497 209 50         id = id_sv ? SvIV(*id_sv) : -1;
1498 209           node_id = _list_tail_node_id(id);
1499              
1500             /* Create new object with tail node_id */
1501 209           new_hash = newHV();
1502 209           hv_store(new_hash, "_id", 3, newSViv(id), 0);
1503 209           hv_store(new_hash, "_node_id", 8, newSViv(node_id), 0);
1504             #ifdef USE_ITHREADS
1505             owner_tid_sv = hv_fetch(hash, "_owner_tid", 10, 0);
1506             if (owner_tid_sv) {
1507             hv_store(new_hash, "_owner_tid", 10, newSVsv(*owner_tid_sv), 0);
1508             }
1509             #endif
1510 209           _incref(id);
1511              
1512 209           RETVAL = sv_bless(newRV_noinc((SV*)new_hash), gv_stashpv("Doubly", GV_ADD));
1513             OUTPUT:
1514             RETVAL
1515              
1516             SV*
1517             next(self)
1518             SV* self
1519             PREINIT:
1520             HV* hash;
1521             HV* new_hash;
1522             SV** id_sv;
1523             SV** node_id_sv;
1524             int id;
1525             long node_id;
1526             long next_node_id;
1527             #ifdef USE_ITHREADS
1528             SV** owner_tid_sv;
1529             #endif
1530             CODE:
1531 244           hash = (HV*)SvRV(self);
1532 244           id_sv = hv_fetch(hash, "_id", 3, 0);
1533 244           node_id_sv = hv_fetch(hash, "_node_id", 8, 0);
1534 244 50         id = id_sv ? SvIV(*id_sv) : -1;
1535 244 50         node_id = node_id_sv ? SvIV(*node_id_sv) : 0;
1536 244           next_node_id = _list_next_node_id(id, node_id);
1537              
1538             /* Return undef if no next node */
1539 244 100         if (next_node_id == 0) {
1540 1           RETVAL = &PL_sv_undef;
1541             } else {
1542             /* Create new object with next node_id */
1543 243           new_hash = newHV();
1544 243           hv_store(new_hash, "_id", 3, newSViv(id), 0);
1545 243           hv_store(new_hash, "_node_id", 8, newSViv(next_node_id), 0);
1546             #ifdef USE_ITHREADS
1547             owner_tid_sv = hv_fetch(hash, "_owner_tid", 10, 0);
1548             if (owner_tid_sv) {
1549             hv_store(new_hash, "_owner_tid", 10, newSVsv(*owner_tid_sv), 0);
1550             }
1551             #endif
1552 243           _incref(id);
1553              
1554 243           RETVAL = sv_bless(newRV_noinc((SV*)new_hash), gv_stashpv("Doubly", GV_ADD));
1555             }
1556             OUTPUT:
1557             RETVAL
1558              
1559             SV*
1560             prev(self)
1561             SV* self
1562             PREINIT:
1563             HV* hash;
1564             HV* new_hash;
1565             SV** id_sv;
1566             SV** node_id_sv;
1567             int id;
1568             long node_id;
1569             long prev_node_id;
1570             #ifdef USE_ITHREADS
1571             SV** owner_tid_sv;
1572             #endif
1573             CODE:
1574 207           hash = (HV*)SvRV(self);
1575 207           id_sv = hv_fetch(hash, "_id", 3, 0);
1576 207           node_id_sv = hv_fetch(hash, "_node_id", 8, 0);
1577 207 50         id = id_sv ? SvIV(*id_sv) : -1;
1578 207 50         node_id = node_id_sv ? SvIV(*node_id_sv) : 0;
1579 207           prev_node_id = _list_prev_node_id(id, node_id);
1580              
1581             /* Return undef if no prev node (at start) */
1582 207 50         if (prev_node_id == 0) {
1583 0           RETVAL = &PL_sv_undef;
1584             } else {
1585             /* Create new object with prev node_id */
1586 207           new_hash = newHV();
1587 207           hv_store(new_hash, "_id", 3, newSViv(id), 0);
1588 207           hv_store(new_hash, "_node_id", 8, newSViv(prev_node_id), 0);
1589             #ifdef USE_ITHREADS
1590             owner_tid_sv = hv_fetch(hash, "_owner_tid", 10, 0);
1591             if (owner_tid_sv) {
1592             hv_store(new_hash, "_owner_tid", 10, newSVsv(*owner_tid_sv), 0);
1593             }
1594             #endif
1595 207           _incref(id);
1596              
1597 207           RETVAL = sv_bless(newRV_noinc((SV*)new_hash), gv_stashpv("Doubly", GV_ADD));
1598             }
1599             OUTPUT:
1600             RETVAL
1601              
1602             int
1603             is_start(self)
1604             SV* self
1605             CODE:
1606 6           HV* hash = (HV*)SvRV(self);
1607 6           SV** id_sv = hv_fetch(hash, "_id", 3, 0);
1608 6           SV** node_id_sv = hv_fetch(hash, "_node_id", 8, 0);
1609 6 50         int id = id_sv ? SvIV(*id_sv) : -1;
1610 6 50         long node_id = node_id_sv ? SvIV(*node_id_sv) : 0;
1611 6           RETVAL = _list_is_start_node(id, node_id);
1612             OUTPUT:
1613             RETVAL
1614              
1615             int
1616             is_end(self)
1617             SV* self
1618             CODE:
1619 4           HV* hash = (HV*)SvRV(self);
1620 4           SV** id_sv = hv_fetch(hash, "_id", 3, 0);
1621 4           SV** node_id_sv = hv_fetch(hash, "_node_id", 8, 0);
1622 4 50         int id = id_sv ? SvIV(*id_sv) : -1;
1623 4 50         long node_id = node_id_sv ? SvIV(*node_id_sv) : 0;
1624 4           RETVAL = _list_is_end_node(id, node_id);
1625             OUTPUT:
1626             RETVAL
1627              
1628             SV*
1629             add(self, data)
1630             SV* self
1631             SV* data
1632             PREINIT:
1633             HV* hash;
1634             SV** id_sv;
1635             SV** node_id_sv;
1636             int id;
1637             long node_id;
1638             CODE:
1639 6295           hash = (HV*)SvRV(self);
1640 6295           id_sv = hv_fetch(hash, "_id", 3, 0);
1641 6295           node_id_sv = hv_fetch(hash, "_node_id", 8, 0);
1642 6295 50         id = id_sv ? SvIV(*id_sv) : -1;
1643 6295 50         node_id = node_id_sv ? SvIV(*node_id_sv) : 0;
1644 6295           _list_add(aTHX_ id, data);
1645             /* If current node_id is 0 (invalid/empty), update to tail */
1646 6295 100         if (node_id == 0) {
1647 1           hv_store(hash, "_node_id", 8, newSViv(_list_tail_node_id(id)), 0);
1648             }
1649 6295           RETVAL = newSVsv(self);
1650             OUTPUT:
1651             RETVAL
1652              
1653             SV*
1654             bulk_add(self, ...)
1655             SV* self
1656             CODE:
1657 204           HV* hash = (HV*)SvRV(self);
1658 204           SV** id_sv = hv_fetch(hash, "_id", 3, 0);
1659 204 50         int id = id_sv ? SvIV(*id_sv) : -1;
1660             int i;
1661 102214 100         for (i = 1; i < items; i++) {
1662 102010           _list_add(aTHX_ id, ST(i));
1663             }
1664 204           RETVAL = newSVsv(self);
1665             OUTPUT:
1666             RETVAL
1667              
1668             SV*
1669             remove_from_start(self)
1670             SV* self
1671             PREINIT:
1672             HV* hash;
1673             SV** id_sv;
1674             SV** node_id_sv;
1675             int id;
1676             long node_id;
1677             long old_head_id;
1678             long new_head_id;
1679             CODE:
1680 322           hash = (HV*)SvRV(self);
1681 322           id_sv = hv_fetch(hash, "_id", 3, 0);
1682 322           node_id_sv = hv_fetch(hash, "_node_id", 8, 0);
1683 322 50         id = id_sv ? SvIV(*id_sv) : -1;
1684 322 50         node_id = node_id_sv ? SvIV(*node_id_sv) : 0;
1685 322           old_head_id = _list_head_node_id(id);
1686 322           RETVAL = _list_remove_from_start(aTHX_ id);
1687             /* If we were pointing to the old head, update to new head */
1688 322 100         if (node_id == old_head_id) {
1689 320           new_head_id = _list_head_node_id(id);
1690 320           hv_store(hash, "_node_id", 8, newSViv(new_head_id), 0);
1691             }
1692             OUTPUT:
1693             RETVAL
1694              
1695             SV*
1696             remove_from_end(self)
1697             SV* self
1698             PREINIT:
1699             HV* hash;
1700             SV** id_sv;
1701             SV** node_id_sv;
1702             int id;
1703             long node_id;
1704             long old_tail_id;
1705             long new_tail_id;
1706             CODE:
1707 219           hash = (HV*)SvRV(self);
1708 219           id_sv = hv_fetch(hash, "_id", 3, 0);
1709 219           node_id_sv = hv_fetch(hash, "_node_id", 8, 0);
1710 219 50         id = id_sv ? SvIV(*id_sv) : -1;
1711 219 50         node_id = node_id_sv ? SvIV(*node_id_sv) : 0;
1712 219           old_tail_id = _list_tail_node_id(id);
1713 219           RETVAL = _list_remove_from_end(aTHX_ id);
1714             /* If we were pointing to the old tail, update to new tail */
1715 219 100         if (node_id == old_tail_id) {
1716 4           new_tail_id = _list_tail_node_id(id);
1717 4           hv_store(hash, "_node_id", 8, newSViv(new_tail_id), 0);
1718             }
1719             OUTPUT:
1720             RETVAL
1721              
1722             SV*
1723             remove(self)
1724             SV* self
1725             PREINIT:
1726             HV* hash;
1727             SV** id_sv;
1728             SV** node_id_sv;
1729             int id;
1730             long node_id;
1731             RemoveResult result;
1732             CODE:
1733 210           hash = (HV*)SvRV(self);
1734 210           id_sv = hv_fetch(hash, "_id", 3, 0);
1735 210           node_id_sv = hv_fetch(hash, "_node_id", 8, 0);
1736 210 50         id = id_sv ? SvIV(*id_sv) : -1;
1737 210 50         node_id = node_id_sv ? SvIV(*node_id_sv) : 0;
1738 210           result = _list_remove_by_node_id_ex(aTHX_ id, node_id);
1739             /* Update _node_id to next node */
1740 210           hv_store(hash, "_node_id", 8, newSViv(result.next_node_id), 0);
1741 210           RETVAL = result.data;
1742             OUTPUT:
1743             RETVAL
1744              
1745             SV*
1746             remove_from_pos(self, pos)
1747             SV* self
1748             int pos
1749             CODE:
1750 4           HV* hash = (HV*)SvRV(self);
1751 4           SV** id_sv = hv_fetch(hash, "_id", 3, 0);
1752 4 50         int id = id_sv ? SvIV(*id_sv) : -1;
1753 4           RETVAL = _list_remove_at_pos(aTHX_ id, pos);
1754             OUTPUT:
1755             RETVAL
1756              
1757             SV*
1758             insert_before(self, data)
1759             SV* self
1760             SV* data
1761             PREINIT:
1762             HV* hash;
1763             HV* new_hash;
1764             SV** id_sv;
1765             SV** node_id_sv;
1766             int id;
1767             long node_id;
1768             long new_node_id;
1769             #ifdef USE_ITHREADS
1770             SV** owner_tid_sv;
1771             #endif
1772             CODE:
1773 103           hash = (HV*)SvRV(self);
1774 103           id_sv = hv_fetch(hash, "_id", 3, 0);
1775 103           node_id_sv = hv_fetch(hash, "_node_id", 8, 0);
1776 103 50         id = id_sv ? SvIV(*id_sv) : -1;
1777 103 50         node_id = node_id_sv ? SvIV(*node_id_sv) : 0;
1778 103           new_node_id = _list_insert_before_node_id(aTHX_ id, node_id, data);
1779              
1780             /* Return new object pointing to newly inserted node */
1781 103           new_hash = newHV();
1782 103           hv_store(new_hash, "_id", 3, newSViv(id), 0);
1783 103           hv_store(new_hash, "_node_id", 8, newSViv(new_node_id), 0);
1784             #ifdef USE_ITHREADS
1785             owner_tid_sv = hv_fetch(hash, "_owner_tid", 10, 0);
1786             if (owner_tid_sv) {
1787             hv_store(new_hash, "_owner_tid", 10, newSVsv(*owner_tid_sv), 0);
1788             }
1789             #endif
1790 103           _incref(id);
1791 103           RETVAL = sv_bless(newRV_noinc((SV*)new_hash), gv_stashpv("Doubly", GV_ADD));
1792             OUTPUT:
1793             RETVAL
1794              
1795             SV*
1796             insert_after(self, data)
1797             SV* self
1798             SV* data
1799             PREINIT:
1800             HV* hash;
1801             HV* new_hash;
1802             SV** id_sv;
1803             SV** node_id_sv;
1804             int id;
1805             long node_id;
1806             long new_node_id;
1807             #ifdef USE_ITHREADS
1808             SV** owner_tid_sv;
1809             #endif
1810             CODE:
1811 115           hash = (HV*)SvRV(self);
1812 115           id_sv = hv_fetch(hash, "_id", 3, 0);
1813 115           node_id_sv = hv_fetch(hash, "_node_id", 8, 0);
1814 115 50         id = id_sv ? SvIV(*id_sv) : -1;
1815 115 50         node_id = node_id_sv ? SvIV(*node_id_sv) : 0;
1816 115           new_node_id = _list_insert_after_node_id(aTHX_ id, node_id, data);
1817              
1818             /* Return new object pointing to newly inserted node */
1819 115           new_hash = newHV();
1820 115           hv_store(new_hash, "_id", 3, newSViv(id), 0);
1821 115           hv_store(new_hash, "_node_id", 8, newSViv(new_node_id), 0);
1822             #ifdef USE_ITHREADS
1823             owner_tid_sv = hv_fetch(hash, "_owner_tid", 10, 0);
1824             if (owner_tid_sv) {
1825             hv_store(new_hash, "_owner_tid", 10, newSVsv(*owner_tid_sv), 0);
1826             }
1827             #endif
1828 115           _incref(id);
1829 115           RETVAL = sv_bless(newRV_noinc((SV*)new_hash), gv_stashpv("Doubly", GV_ADD));
1830             OUTPUT:
1831             RETVAL
1832              
1833             SV*
1834             insert_at_start(self, data)
1835             SV* self
1836             SV* data
1837             PREINIT:
1838             HV* hash;
1839             HV* new_hash;
1840             SV** id_sv;
1841             int id;
1842             long new_node_id;
1843             #ifdef USE_ITHREADS
1844             SV** owner_tid_sv;
1845             #endif
1846             CODE:
1847 315           hash = (HV*)SvRV(self);
1848 315           id_sv = hv_fetch(hash, "_id", 3, 0);
1849 315 50         id = id_sv ? SvIV(*id_sv) : -1;
1850 315           _list_insert_at_start(aTHX_ id, data);
1851 315           new_node_id = _list_head_node_id(id);
1852              
1853             /* Return new object pointing to new start */
1854 315           new_hash = newHV();
1855 315           hv_store(new_hash, "_id", 3, newSViv(id), 0);
1856 315           hv_store(new_hash, "_node_id", 8, newSViv(new_node_id), 0);
1857             #ifdef USE_ITHREADS
1858             owner_tid_sv = hv_fetch(hash, "_owner_tid", 10, 0);
1859             if (owner_tid_sv) {
1860             hv_store(new_hash, "_owner_tid", 10, newSVsv(*owner_tid_sv), 0);
1861             }
1862             #endif
1863 315           _incref(id);
1864 315           RETVAL = sv_bless(newRV_noinc((SV*)new_hash), gv_stashpv("Doubly", GV_ADD));
1865             OUTPUT:
1866             RETVAL
1867              
1868             SV*
1869             insert_at_end(self, data)
1870             SV* self
1871             SV* data
1872             PREINIT:
1873             HV* hash;
1874             HV* new_hash;
1875             SV** id_sv;
1876             int id;
1877             long new_node_id;
1878             #ifdef USE_ITHREADS
1879             SV** owner_tid_sv;
1880             #endif
1881             CODE:
1882 315           hash = (HV*)SvRV(self);
1883 315           id_sv = hv_fetch(hash, "_id", 3, 0);
1884 315 50         id = id_sv ? SvIV(*id_sv) : -1;
1885 315           _list_insert_at_end(aTHX_ id, data);
1886 315           new_node_id = _list_tail_node_id(id);
1887              
1888             /* Return new object pointing to new end */
1889 315           new_hash = newHV();
1890 315           hv_store(new_hash, "_id", 3, newSViv(id), 0);
1891 315           hv_store(new_hash, "_node_id", 8, newSViv(new_node_id), 0);
1892             #ifdef USE_ITHREADS
1893             owner_tid_sv = hv_fetch(hash, "_owner_tid", 10, 0);
1894             if (owner_tid_sv) {
1895             hv_store(new_hash, "_owner_tid", 10, newSVsv(*owner_tid_sv), 0);
1896             }
1897             #endif
1898 315           _incref(id);
1899 315           RETVAL = sv_bless(newRV_noinc((SV*)new_hash), gv_stashpv("Doubly", GV_ADD));
1900             OUTPUT:
1901             RETVAL
1902              
1903             SV*
1904             insert_at_pos(self, pos, data)
1905             SV* self
1906             int pos
1907             SV* data
1908             PREINIT:
1909             HV* hash;
1910             HV* new_hash;
1911             SV** id_sv;
1912             int id;
1913             DoublyList* list;
1914             DoublyNode* node;
1915             long new_node_id;
1916             #ifdef USE_ITHREADS
1917             SV** owner_tid_sv;
1918             #endif
1919             CODE:
1920 3           hash = (HV*)SvRV(self);
1921 3           id_sv = hv_fetch(hash, "_id", 3, 0);
1922 3 50         id = id_sv ? SvIV(*id_sv) : -1;
1923 3           _list_insert_at_pos(aTHX_ id, pos, data);
1924            
1925             /* Get the node_id of the inserted node (at pos+1 since insert_at_pos inserts after) */
1926             SHARED_LOCK();
1927 3           list = _get_list(id);
1928 3           new_node_id = 0;
1929 3 50         if (list && !list->destroyed) {
    50          
1930 3           node = _get_node_at_pos(list, pos + 1);
1931 3 50         if (node) {
1932 3           new_node_id = node->node_id;
1933             }
1934             }
1935             SHARED_UNLOCK();
1936              
1937             /* Return new object pointing to inserted node */
1938 3           new_hash = newHV();
1939 3           hv_store(new_hash, "_id", 3, newSViv(id), 0);
1940 3           hv_store(new_hash, "_node_id", 8, newSViv(new_node_id), 0);
1941             #ifdef USE_ITHREADS
1942             owner_tid_sv = hv_fetch(hash, "_owner_tid", 10, 0);
1943             if (owner_tid_sv) {
1944             hv_store(new_hash, "_owner_tid", 10, newSVsv(*owner_tid_sv), 0);
1945             }
1946             #endif
1947 3           _incref(id);
1948 3           RETVAL = sv_bless(newRV_noinc((SV*)new_hash), gv_stashpv("Doubly", GV_ADD));
1949             OUTPUT:
1950             RETVAL
1951              
1952             SV*
1953             find(self, cb)
1954             SV* self
1955             SV* cb
1956             PREINIT:
1957             HV* hash;
1958             HV* new_hash;
1959             SV** id_sv;
1960             int id;
1961             DoublyList* list;
1962             DoublyNode* node;
1963             SV* node_data;
1964             int found;
1965             long found_node_id;
1966             #ifdef USE_ITHREADS
1967             SV** owner_tid_sv;
1968             #endif
1969             CODE:
1970 1           hash = (HV*)SvRV(self);
1971 1           id_sv = hv_fetch(hash, "_id", 3, 0);
1972 1 50         id = id_sv ? SvIV(*id_sv) : -1;
1973              
1974 1           found = 0;
1975 1           found_node_id = 0;
1976              
1977             /* Iterate through list, calling callback for each node */
1978             SHARED_LOCK();
1979 1           list = _get_list(id);
1980 1 50         if (list && !list->destroyed && list->length > 0) {
    50          
    50          
1981 1           node = list->head;
1982 3 50         while (node && !found) {
    100          
1983 2           long current_node_id = node->node_id;
1984 2           node_data = _node_to_sv(aTHX_ node);
1985              
1986             SHARED_UNLOCK();
1987              
1988             {
1989 2           dSP;
1990 2 50         PUSHMARK(SP);
1991 2 50         XPUSHs(sv_2mortal(node_data));
1992 2           PUTBACK;
1993 2           call_sv(cb, G_SCALAR);
1994 2           SPAGAIN;
1995 2 100         if (SvTRUE(*PL_stack_sp)) {
1996 1           found = 1;
1997 1           found_node_id = current_node_id;
1998             }
1999 2           POPs;
2000             }
2001              
2002             SHARED_LOCK();
2003 2           list = _get_list(id);
2004 2 50         if (!list || list->destroyed) {
    50          
2005             break;
2006             }
2007              
2008 2 100         if (!found) {
2009 1           node = node->next;
2010             }
2011             }
2012             }
2013             SHARED_UNLOCK();
2014              
2015 1 50         if (found) {
2016             /* Create new object with found node_id */
2017 1           new_hash = newHV();
2018 1           hv_store(new_hash, "_id", 3, newSViv(id), 0);
2019 1           hv_store(new_hash, "_node_id", 8, newSViv(found_node_id), 0);
2020             #ifdef USE_ITHREADS
2021             owner_tid_sv = hv_fetch(hash, "_owner_tid", 10, 0);
2022             if (owner_tid_sv) {
2023             hv_store(new_hash, "_owner_tid", 10, newSVsv(*owner_tid_sv), 0);
2024             }
2025             #endif
2026 1           _incref(id);
2027 1           RETVAL = sv_bless(newRV_noinc((SV*)new_hash), gv_stashpv("Doubly", GV_ADD));
2028             } else {
2029 0           RETVAL = &PL_sv_undef;
2030             }
2031             OUTPUT:
2032             RETVAL
2033              
2034             SV*
2035             insert(self, cb, data)
2036             SV* self
2037             SV* cb
2038             SV* data
2039             PREINIT:
2040             HV* hash;
2041             SV** id_sv;
2042             int id;
2043             DoublyList* list;
2044             DoublyNode* node;
2045             SV* node_data;
2046             int found;
2047             int pos;
2048             CODE:
2049 1           hash = (HV*)SvRV(self);
2050 1           id_sv = hv_fetch(hash, "_id", 3, 0);
2051 1 50         id = id_sv ? SvIV(*id_sv) : -1;
2052            
2053 1           found = 0;
2054 1           pos = 0;
2055            
2056             /* Find position using callback, then insert before it */
2057             SHARED_LOCK();
2058 1           list = _get_list(id);
2059 1 50         if (list && !list->destroyed && list->length > 0) {
    50          
    50          
2060 0           node = list->head;
2061 0 0         while (node && !found) {
    0          
2062 0           node_data = _node_to_sv(aTHX_ node);
2063            
2064             SHARED_UNLOCK();
2065            
2066             /* Call the callback - simpler approach matching Less.xs */
2067             {
2068 0           dSP;
2069 0 0         PUSHMARK(SP);
2070 0 0         XPUSHs(sv_2mortal(node_data));
2071 0           PUTBACK;
2072 0           call_sv(cb, G_SCALAR);
2073 0           SPAGAIN;
2074 0 0         if (SvTRUE(*PL_stack_sp)) {
2075 0           found = 1;
2076             }
2077 0           POPs;
2078             }
2079            
2080             SHARED_LOCK();
2081 0           list = _get_list(id);
2082 0 0         if (!list || list->destroyed) {
    0          
2083             break;
2084             }
2085            
2086 0 0         if (!found) {
2087 0           node = node->next;
2088 0           pos++;
2089             }
2090             }
2091             }
2092             SHARED_UNLOCK();
2093            
2094             /* Insert at found position */
2095 1 50         if (found) {
2096 0           _list_insert_at_pos(aTHX_ id, pos, data);
2097             } else {
2098             /* Not found - insert at end */
2099 1           _list_add(aTHX_ id, data);
2100             }
2101            
2102 1           RETVAL = newSVsv(self);
2103             OUTPUT:
2104             RETVAL
2105              
2106             void
2107             destroy(self)
2108             SV* self
2109             CODE:
2110 2635           HV* hash = (HV*)SvRV(self);
2111 2635           SV** id_sv = hv_fetch(hash, "_id", 3, 0);
2112 2635 50         int id = id_sv ? SvIV(*id_sv) : -1;
2113 2635           _list_destroy(aTHX_ id);
2114              
2115             void
2116             DESTROY(self)
2117             SV* self
2118             PREINIT:
2119             HV* hash;
2120             SV** id_sv;
2121             int id;
2122             #ifdef USE_ITHREADS
2123             SV** owner_tid_sv;
2124             UV owner_tid;
2125             UV my_tid;
2126             #endif
2127             CODE:
2128             /* Skip cleanup during global destruction - Perl is tearing down anyway */
2129 4471 50         if (PL_dirty) {
2130 0           XSRETURN_EMPTY;
2131             }
2132            
2133 4471           hash = (HV*)SvRV(self);
2134 4471           id_sv = hv_fetch(hash, "_id", 3, 0);
2135 4471 50         id = id_sv ? SvIV(*id_sv) : -1;
2136             #ifdef USE_ITHREADS
2137             owner_tid_sv = hv_fetch(hash, "_owner_tid", 10, 0);
2138             owner_tid = owner_tid_sv ? SvUV(*owner_tid_sv) : 0;
2139             my_tid = PTR2UV(PERL_GET_THX);
2140            
2141             /* Only decrement refcount if this is the owning thread */
2142             if (owner_tid == my_tid) {
2143             _decref(aTHX_ id);
2144             }
2145             #else
2146             /* Non-threaded Perl - always decref */
2147 4471           _decref(aTHX_ id);
2148             #endif
2149              
2150             void
2151             CLONE_SKIP(...)
2152             CODE:
2153             /* Return 0 - allow objects to be cloned. The cloned objects
2154             * will have the original owner's thread ID, so when they're
2155             * destroyed in the child thread, they won't call _decref
2156             * because their _owner_tid won't match. */
2157             PERL_UNUSED_VAR(items);
2158 0           XSRETURN_IV(0);