File Coverage

Shared.xs
Criterion Covered Total %
statement 240 242 99.1
branch 280 446 62.7
condition n/a
subroutine n/a
pod n/a
total 520 688 75.5


line stmt bran cond sub pod time code
1             #define PERL_NO_GET_CONTEXT
2             #include "EXTERN.h"
3             #include "perl.h"
4             #include "XSUB.h"
5             #include "ppport.h"
6             #include "sortedset.h"
7              
8             #define EXTRACT(sv) \
9             if (!sv_isobject(sv) || !sv_derived_from(sv, "Data::SortedSet::Shared")) \
10             croak("Expected a Data::SortedSet::Shared object"); \
11             SsHandle *h = INT2PTR(SsHandle*, SvIV(SvRV(sv))); \
12             if (!h) croak("Attempted to use a destroyed Data::SortedSet::Shared object")
13              
14             #define MAKE_OBJ(class, handle) \
15             SV *obj = newSViv(PTR2IV(handle)); \
16             SV *ref = newRV_noinc(obj); \
17             sv_bless(ref, gv_stashpv(class, GV_ADD)); \
18             RETVAL = ref
19              
20             /* collect ranks [S0, S0+LEN) (REV = reverse order) and push onto the stack:
21             members, or (member,score) pairs if WS. The read lock must be HELD on entry;
22             it is released (after the snapshot) before anything is pushed. */
23             #define EMIT_COLLECTED(S0, LEN, REV, WS) STMT_START { \
24             ss_rcollect_t col = { NULL, NULL, 0, 0 }; \
25             int ok_ = ss_collect_range(h, (S0), (LEN), (REV), &col); \
26             ss_rwlock_rdunlock(h); \
27             if (!ok_) { free(col.members); free(col.scores); croak("range: out of memory"); } \
28             EXTEND(SP, (SSize_t)((WS) ? col.n * 2 : col.n)); \
29             for (size_t i_ = 0; i_ < col.n; i_++) { \
30             PUSHs(sv_2mortal(newSViv((IV)col.members[i_]))); \
31             if (WS) PUSHs(sv_2mortal(newSVnv(col.scores[i_]))); \
32             } \
33             free(col.members); free(col.scores); \
34             } STMT_END
35              
36             /* parse trailing "withscores => bool", "limit => n", "offset => n" options */
37 78           static void ss_parse_range_opts(pTHX_ SV **sp, int first, int items, int *ws, IV *limit, IV *offset) {
38 78 50         if ((items - first) % 2 != 0) croak("range options must be key => value pairs");
39 93 100         for (int ai = first; ai + 1 < items; ai += 2) {
40 15           const char *k = SvPV_nolen(sp[ai]);
41 15 100         if (!strcmp(k, "withscores")) *ws = SvTRUE(sp[ai+1]);
42 10 50         else if (limit && !strcmp(k, "limit")) *limit = SvIV(sp[ai+1]);
    100          
43 5 50         else if (offset && !strcmp(k, "offset")) *offset = SvIV(sp[ai+1]);
    50          
44 0           else croak("unknown option '%s'", k);
45             }
46 78           }
47              
48             /* normalize Perl-style [start, stop] rank indices into a forward [*a, *b] window
49             (negatives count from the end); returns true if the window is non-empty */
50 14           static int ss_rank_window(uint32_t cnt, IV start, IV stop, IV *a, IV *b) {
51 14           *a = start; *b = stop;
52 14 100         if (*a < 0) *a += (IV)cnt;
53 14 100         if (*b < 0) *b += (IV)cnt;
54 14 50         if (*a < 0) *a = 0;
55 14 100         if (*b >= (IV)cnt) *b = (IV)cnt - 1;
56 14 100         return cnt > 0 && *a <= *b;
    100          
57             }
58              
59             MODULE = Data::SortedSet::Shared PACKAGE = Data::SortedSet::Shared
60              
61             PROTOTYPES: DISABLE
62              
63             SV *
64             new(class, path, max_entries)
65             const char *class
66             SV *path
67             UV max_entries
68             PREINIT:
69             char errbuf[SS_ERR_BUFLEN];
70             CODE:
71 35 100         const char *p = SvOK(path) ? SvPV_nolen(path) : NULL;
72 35 50         if (max_entries > UINT32_MAX) croak("Data::SortedSet::Shared->new: max_entries exceeds 2^32");
73 35           SsHandle *h = ss_create(p, (uint32_t)max_entries, errbuf);
74 35 100         if (!h) croak("Data::SortedSet::Shared->new: %s", errbuf);
75 33           MAKE_OBJ(class, h);
76             OUTPUT:
77             RETVAL
78              
79             SV *
80             new_memfd(class, name, max_entries)
81             const char *class
82             const char *name
83             UV max_entries
84             PREINIT:
85             char errbuf[SS_ERR_BUFLEN];
86             CODE:
87 1 50         if (max_entries > UINT32_MAX) croak("Data::SortedSet::Shared->new_memfd: max_entries exceeds 2^32");
88 1           SsHandle *h = ss_create_memfd(name, (uint32_t)max_entries, errbuf);
89 1 50         if (!h) croak("Data::SortedSet::Shared->new_memfd: %s", errbuf);
90 1           MAKE_OBJ(class, h);
91             OUTPUT:
92             RETVAL
93              
94             SV *
95             new_from_fd(class, fd)
96             const char *class
97             int fd
98             PREINIT:
99             char errbuf[SS_ERR_BUFLEN];
100             CODE:
101 1           SsHandle *h = ss_open_fd(fd, errbuf);
102 1 50         if (!h) croak("Data::SortedSet::Shared->new_from_fd: %s", errbuf);
103 1           MAKE_OBJ(class, h);
104             OUTPUT:
105             RETVAL
106              
107             void
108             DESTROY(self)
109             SV *self
110             CODE:
111 36 50         if (sv_isobject(self) && sv_derived_from(self, "Data::SortedSet::Shared")) {
    50          
112 36           SsHandle *h = INT2PTR(SsHandle*, SvIV(SvRV(self)));
113 36 100         if (h) { sv_setiv(SvRV(self), 0); ss_destroy(h); } /* null first: activates EXTRACT's use-after-destroy croak + makes a double DESTROY a no-op */
114             }
115              
116             UV
117             count(self)
118             SV *self
119             PREINIT:
120 25 50         EXTRACT(self);
    50          
    100          
121             CODE:
122 24           ss_rwlock_rdlock(h);
123 24           RETVAL = h->hdr->count;
124 24           ss_rwlock_rdunlock(h);
125             OUTPUT:
126             RETVAL
127              
128             UV
129             max_entries(self)
130             SV *self
131             PREINIT:
132 3 50         EXTRACT(self);
    50          
    50          
133             CODE:
134 3 50         RETVAL = h->hdr->max_entries;
135             OUTPUT:
136             RETVAL
137              
138             void
139             clear(self)
140             SV *self
141             PREINIT:
142 3 50         EXTRACT(self);
    50          
    50          
143             CODE:
144 3           ss_rwlock_wrlock(h);
145 3           ss_clear_locked(h);
146 3           ss_rwlock_wrunlock(h);
147              
148             SV *
149             add(self, member, score)
150             SV *self
151             IV member
152             NV score
153             PREINIT:
154 94352 50         EXTRACT(self);
    50          
    50          
155             int rc;
156             CODE:
157 94352 100         if (score != score) croak("add: score must not be NaN");
158 94351           ss_rwlock_wrlock(h);
159 94351           rc = ss_add_locked(h, (int64_t)member, (double)score);
160 94351           __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
161 94351           ss_rwlock_wrunlock(h);
162 94351 100         RETVAL = (rc < 0) ? &PL_sv_undef : newSViv(rc);
163             OUTPUT:
164             RETVAL
165              
166             SV *
167             score(self, member)
168             SV *self
169             IV member
170             PREINIT:
171 65720 50         EXTRACT(self);
    50          
    50          
172             double sc;
173             CODE:
174 65720           ss_rwlock_rdlock(h);
175 65720           int found = ss_idx_get(h, (int64_t)member, &sc);
176 65720           ss_rwlock_rdunlock(h);
177 65720 100         RETVAL = found ? newSVnv(sc) : &PL_sv_undef;
178             OUTPUT:
179             RETVAL
180              
181             bool
182             exists(self, member)
183             SV *self
184             IV member
185             PREINIT:
186 5 50         EXTRACT(self);
    50          
    50          
187             double sc;
188             CODE:
189 5           ss_rwlock_rdlock(h);
190 5           RETVAL = ss_idx_get(h, (int64_t)member, &sc);
191 5           ss_rwlock_rdunlock(h);
192             OUTPUT:
193             RETVAL
194              
195             bool
196             remove(self, member)
197             SV *self
198             IV member
199             PREINIT:
200 23436 50         EXTRACT(self);
    50          
    50          
201             CODE:
202 23436           ss_rwlock_wrlock(h);
203 23436           RETVAL = ss_remove_locked(h, (int64_t)member);
204 23436           __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
205 23436           ss_rwlock_wrunlock(h);
206             OUTPUT:
207             RETVAL
208              
209             NV
210             incr(self, member, delta)
211             SV *self
212             IV member
213             NV delta
214             PREINIT:
215 16396 50         EXTRACT(self);
    50          
    50          
216             double out;
217             int rc;
218             CODE:
219 16396 100         if (delta != delta) croak("incr: delta must not be NaN");
220 16395           ss_rwlock_wrlock(h);
221 16395           rc = ss_incr_locked(h, (int64_t)member, (double)delta, &out);
222 16395           __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
223 16395           ss_rwlock_wrunlock(h);
224 16395 50         if (rc == -1) croak("incr: max_entries exhausted");
225 16395 100         if (rc == -2) croak("incr: result is NaN");
226 16394 100         RETVAL = out;
227             OUTPUT:
228             RETVAL
229              
230             void
231             pop_min(self)
232             SV *self
233             PREINIT:
234 14950 50         EXTRACT(self);
    50          
    50          
235             PPCODE:
236             {
237             int64_t m; double s; int ok;
238 14950           ss_rwlock_wrlock(h);
239 14950           ok = ss_pop_locked(h, 0, &m, &s);
240 14950 100         if (ok) __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
241 14950           ss_rwlock_wrunlock(h);
242 14950 100         if (ok) { EXTEND(SP, 2); PUSHs(sv_2mortal(newSViv((IV)m))); PUSHs(sv_2mortal(newSVnv(s))); }
    50          
243             }
244              
245             void
246             pop_max(self)
247             SV *self
248             PREINIT:
249 8834 50         EXTRACT(self);
    50          
    50          
250             PPCODE:
251             {
252             int64_t m; double s; int ok;
253 8834           ss_rwlock_wrlock(h);
254 8834           ok = ss_pop_locked(h, 1, &m, &s);
255 8834 100         if (ok) __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
256 8834           ss_rwlock_wrunlock(h);
257 8834 100         if (ok) { EXTEND(SP, 2); PUSHs(sv_2mortal(newSViv((IV)m))); PUSHs(sv_2mortal(newSVnv(s))); }
    50          
258             }
259              
260             bool
261             _validate(self)
262             SV *self
263             PREINIT:
264 105 50         EXTRACT(self);
    50          
    50          
265             CODE:
266 105           ss_rwlock_rdlock(h);
267 105           RETVAL = ss_validate_tree(h);
268 105           ss_rwlock_rdunlock(h);
269             OUTPUT:
270             RETVAL
271              
272             SV *
273             rank(self, member)
274             SV *self
275             IV member
276             PREINIT:
277 9833 50         EXTRACT(self);
    50          
    50          
278             double sc;
279             CODE:
280 9833           ss_rwlock_rdlock(h);
281 9833           RETVAL = ss_idx_get(h, (int64_t)member, &sc)
282 9833 100         ? newSVuv(ss_rank_of(h, sc, (int64_t)member)) : &PL_sv_undef;
283 9833           ss_rwlock_rdunlock(h);
284             OUTPUT:
285             RETVAL
286              
287             SV *
288             rev_rank(self, member)
289             SV *self
290             IV member
291             PREINIT:
292 302 50         EXTRACT(self);
    50          
    50          
293             double sc;
294             CODE:
295 302           ss_rwlock_rdlock(h);
296 302           RETVAL = ss_idx_get(h, (int64_t)member, &sc)
297 302 50         ? newSVuv(h->hdr->count - 1 - ss_rank_of(h, sc, (int64_t)member)) : &PL_sv_undef;
298 302           ss_rwlock_rdunlock(h);
299             OUTPUT:
300             RETVAL
301              
302             SV *
303             at_rank(self, rank)
304             SV *self
305             IV rank
306             PREINIT:
307 308 50         EXTRACT(self);
    50          
    50          
308             CODE:
309 308           ss_rwlock_rdlock(h);
310             {
311 308           uint32_t cnt = h->hdr->count;
312 308 100         IV r = rank; if (r < 0) r += (IV)cnt;
313 612 50         if (r >= 0 && (uint64_t)r < cnt) { /* compare in 64-bit; large r must not truncate to an in-range index */
    100          
314 304           int pos; uint32_t leaf = ss_at_rank(h, (uint32_t)r, &pos);
315 304           RETVAL = newSViv((IV)h->nodes[leaf].members[pos]);
316 4           } else RETVAL = &PL_sv_undef;
317             }
318 308           ss_rwlock_rdunlock(h);
319             OUTPUT:
320             RETVAL
321              
322             UV
323             count_in_score(self, min, max)
324             SV *self
325             NV min
326             NV max
327             PREINIT:
328 40 50         EXTRACT(self);
    50          
    50          
329             CODE:
330 40           ss_rwlock_rdlock(h);
331 40           RETVAL = ss_count_in_score(h, (double)min, (double)max, NULL);
332 40           ss_rwlock_rdunlock(h);
333             OUTPUT:
334             RETVAL
335              
336             void
337             range_by_rank(self, start, stop, ...)
338             SV *self
339             IV start
340             IV stop
341             PREINIT:
342 10 50         EXTRACT(self);
    50          
    50          
343             PPCODE:
344 10           int ws = 0;
345 10           ss_parse_range_opts(aTHX_ &ST(0), 3, items, &ws, NULL, NULL);
346 10           ss_rwlock_rdlock(h);
347             {
348 10           uint32_t s0 = 0, len = 0;
349             IV a, b;
350 10 100         if (ss_rank_window(h->hdr->count, start, stop, &a, &b)) { s0 = (uint32_t)a; len = (uint32_t)(b - a + 1); }
351 40 50         EMIT_COLLECTED(s0, len, 0, ws);
    100          
    50          
    100          
    50          
    0          
    100          
    100          
352             }
353              
354             void
355             rev_range_by_rank(self, start, stop, ...)
356             SV *self
357             IV start
358             IV stop
359             PREINIT:
360 4 50         EXTRACT(self);
    50          
    50          
361             PPCODE:
362 4           int ws = 0;
363 4           ss_parse_range_opts(aTHX_ &ST(0), 3, items, &ws, NULL, NULL);
364 4           ss_rwlock_rdlock(h);
365             {
366 4           uint32_t cnt = h->hdr->count, s0 = 0, len = 0;
367             IV a, b;
368 4 50         if (ss_rank_window(cnt, start, stop, &a, &b)) { s0 = (uint32_t)((IV)cnt - 1 - b); len = (uint32_t)(b - a + 1); }
369 21 50         EMIT_COLLECTED(s0, len, 1, ws);
    100          
    50          
    100          
    50          
    0          
    100          
    100          
370             }
371              
372             void
373             range_by_score(self, min, max, ...)
374             SV *self
375             NV min
376             NV max
377             PREINIT:
378 41 50         EXTRACT(self);
    50          
    50          
379             PPCODE:
380 41           int ws = 0; IV limit = -1, offset = 0;
381 41           ss_parse_range_opts(aTHX_ &ST(0), 3, items, &ws, &limit, &offset);
382 41           ss_rwlock_rdlock(h);
383             {
384             uint32_t lo;
385 41           uint32_t win = ss_count_in_score(h, (double)min, (double)max, &lo);
386 41 100         uint32_t off = (offset > 0) ? (offset >= (IV)win ? win : (uint32_t)offset) : 0;
    100          
387 41           uint32_t s0 = lo, len = 0;
388 41 100         if (off < win) { s0 = lo + off; len = win - off; if (limit >= 0 && limit < (IV)len) len = (uint32_t)limit; }
    100          
    100          
389 42568 50         EMIT_COLLECTED(s0, len, 0, ws);
    100          
    50          
    100          
    50          
    0          
    100          
    100          
390             }
391              
392             void
393             rev_range_by_score(self, max, min, ...)
394             SV *self
395             NV max
396             NV min
397             PREINIT:
398 23 50         EXTRACT(self);
    50          
    50          
399             PPCODE:
400 23           int ws = 0; IV limit = -1, offset = 0;
401 23           ss_parse_range_opts(aTHX_ &ST(0), 3, items, &ws, &limit, &offset);
402 23           ss_rwlock_rdlock(h);
403             {
404             uint32_t lo;
405 23           uint32_t win = ss_count_in_score(h, (double)min, (double)max, &lo);
406 23 100         uint32_t off = (offset > 0) ? (offset >= (IV)win ? win : (uint32_t)offset) : 0;
    100          
407 23           uint32_t s0 = lo, len = 0;
408 23 100         if (off < win) { len = win - off; if (limit >= 0 && limit < (IV)len) len = (uint32_t)limit;
    100          
    50          
409 22           s0 = lo + (win - off - len); }
410 33353 50         EMIT_COLLECTED(s0, len, 1, ws);
    50          
    50          
    50          
    50          
    0          
    50          
    100          
411             }
412              
413             void
414             peek_min(self)
415             SV *self
416             PREINIT:
417 5 50         EXTRACT(self);
    50          
    50          
418             PPCODE:
419 5           ss_rwlock_rdlock(h);
420 5 100         if (h->hdr->root != SS_NONE) {
421 4           SsNode *nd = &h->nodes[h->hdr->leftmost];
422 4           IV m = (IV)nd->members[0]; NV s = nd->scores[0];
423 4           ss_rwlock_rdunlock(h);
424 4 50         EXTEND(SP, 2);
425 4           PUSHs(sv_2mortal(newSViv(m)));
426 4           PUSHs(sv_2mortal(newSVnv(s)));
427 1           } else ss_rwlock_rdunlock(h);
428              
429             void
430             peek_max(self)
431             SV *self
432             PREINIT:
433 3 50         EXTRACT(self);
    50          
    50          
434             PPCODE:
435 3           ss_rwlock_rdlock(h);
436 3 50         if (h->hdr->root != SS_NONE) {
437 3           SsNode *nd = &h->nodes[h->hdr->rightmost];
438 3           IV m = (IV)nd->members[nd->num - 1]; NV s = nd->scores[nd->num - 1];
439 3           ss_rwlock_rdunlock(h);
440 3 50         EXTEND(SP, 2);
441 3           PUSHs(sv_2mortal(newSViv(m)));
442 3           PUSHs(sv_2mortal(newSVnv(s)));
443 0           } else ss_rwlock_rdunlock(h);
444              
445             void
446             each(self, cb)
447             SV *self
448             SV *cb
449             PREINIT:
450 5 50         EXTRACT(self);
    50          
    50          
451             CODE:
452 5 50         if (!SvROK(cb) || SvTYPE(SvRV(cb)) != SVt_PVCV) croak("each: callback must be a code ref");
    50          
453             {
454 5           ss_rcollect_t col = { NULL, NULL, 0, 0 };
455 5           ss_rwlock_rdlock(h);
456 5           int ok = ss_collect_range(h, 0, h->hdr->count, 0, &col);
457 5           ss_rwlock_rdunlock(h);
458 5 50         if (!ok) { free(col.members); free(col.scores); croak("each: out of memory"); }
459 9834 100         for (size_t i = 0; i < col.n; i++) {
460 9830 50         dSP; ENTER; SAVETMPS; PUSHMARK(SP);
461 9830 50         XPUSHs(sv_2mortal(newSViv((IV)col.members[i])));
462 9830 50         XPUSHs(sv_2mortal(newSVnv(col.scores[i])));
463 9830           PUTBACK;
464 9830           call_sv(cb, G_VOID|G_DISCARD|G_EVAL);
465 9830 50         FREETMPS; LEAVE;
466 9830 50         if (SvTRUE(ERRSV)) { free(col.members); free(col.scores); croak_sv(ERRSV); }
    100          
    50          
467             }
468 4           free(col.members); free(col.scores);
469             }
470              
471             SV *
472             path(self)
473             SV *self
474             PREINIT:
475 2 50         EXTRACT(self);
    50          
    50          
476             CODE:
477 2 100         RETVAL = h->path ? newSVpv(h->path, 0) : &PL_sv_undef;
478             OUTPUT:
479             RETVAL
480              
481             int
482             memfd(self)
483             SV *self
484             PREINIT:
485 1 50         EXTRACT(self);
    50          
    50          
486             CODE:
487 1 50         RETVAL = h->backing_fd;
488             OUTPUT:
489             RETVAL
490              
491             void
492             sync(self)
493             SV *self
494             PREINIT:
495 5 50         EXTRACT(self);
    50          
    50          
496             CODE:
497 5 50         if (ss_msync(h) != 0) croak("sync: %s", strerror(errno));
498              
499             void
500             unlink(self, ...)
501             SV *self
502             CODE:
503             {
504 2           const char *p = NULL;
505 3 100         if (sv_isobject(self) && sv_derived_from(self, "Data::SortedSet::Shared")) {
    50          
506 1           SsHandle *h = INT2PTR(SsHandle*, SvIV(SvRV(self)));
507 1 50         if (h) p = h->path;
508 1 50         } else if (items >= 2 && SvOK(ST(1))) {
    50          
509 1           p = SvPV_nolen(ST(1));
510             }
511 2 50         if (p && unlink(p) != 0 && errno != ENOENT) croak("unlink: %s", strerror(errno));
    50          
    0          
512             }
513              
514             int
515             eventfd(self)
516             SV *self
517             PREINIT:
518 1 50         EXTRACT(self);
    50          
    50          
519             CODE:
520 1           RETVAL = ss_create_eventfd(h);
521 1 50         if (RETVAL < 0) croak("eventfd: %s", strerror(errno));
522             OUTPUT:
523             RETVAL
524              
525             int
526             fileno(self)
527             SV *self
528             PREINIT:
529 2 50         EXTRACT(self);
    50          
    50          
530             CODE:
531 2 50         RETVAL = h->notify_fd;
532             OUTPUT:
533             RETVAL
534              
535             bool
536             notify(self)
537             SV *self
538             PREINIT:
539 1 50         EXTRACT(self);
    50          
    50          
540             CODE:
541 1 50         RETVAL = ss_notify(h);
542             OUTPUT:
543             RETVAL
544              
545             SV *
546             eventfd_consume(self)
547             SV *self
548             PREINIT:
549 1 50         EXTRACT(self);
    50          
    50          
550             CODE:
551             {
552 1           int64_t v = ss_eventfd_consume(h);
553 1 50         RETVAL = (v < 0) ? &PL_sv_undef : newSVuv((UV)v);
554             }
555             OUTPUT:
556             RETVAL
557              
558             IV
559             add_many(self, rows)
560             SV *self
561             SV *rows
562             PREINIT:
563 6 50         EXTRACT(self);
    50          
    50          
564 6 50         int added = 0;
565             CODE:
566 6 100         if (!SvROK(rows) || SvTYPE(SvRV(rows)) != SVt_PVAV)
    50          
567 1           croak("add_many: expected an arrayref of [member, score] rows");
568             {
569 5           AV *av = (AV *)SvRV(rows);
570 5           SSize_t nr = av_len(av) + 1;
571 5           ss_rwlock_wrlock(h);
572 3021 100         for (SSize_t i = 0; i < nr; i++) {
573 3017           SV **rv = av_fetch(av, i, 0);
574 3017 50         if (!rv || !SvROK(*rv) || SvTYPE(SvRV(*rv)) != SVt_PVAV) continue; /* skip malformed */
    100          
    50          
575 3016           AV *row = (AV *)SvRV(*rv);
576 3016 100         if (av_len(row) + 1 < 2) continue;
577 3014           SV **ms = av_fetch(row, 0, 0), **sv = av_fetch(row, 1, 0);
578 3014 50         if (!ms || !sv) continue;
    50          
579 3014           double score = SvNV(*sv);
580 3014 100         if (score != score) continue; /* skip NaN */
581 3013           int rc = ss_add_locked(h, (int64_t)SvIV(*ms), score);
582 3013 100         if (rc == 1) added++;
583 3 100         else if (rc == -1) break; /* pool full */
584             }
585 5           __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
586 5           ss_rwlock_wrunlock(h);
587             }
588 5 100         RETVAL = added;
589             OUTPUT:
590             RETVAL
591              
592             SV *
593             stats(self)
594             SV *self
595             PREINIT:
596 2 50         EXTRACT(self);
    50          
    50          
597             CODE:
598             {
599 2           HV *hv = newHV();
600 2           ss_rwlock_rdlock(h);
601 2           SsHeader *hd = h->hdr;
602 2           uint32_t nfree = 0, f = hd->node_free_head;
603 1317 100         while (f != SS_NONE) { nfree++; f = h->nodes[f].parent; }
604 2           uint32_t iload = hd->count; /* backward-shift delete leaves no tombstones: occupied slots == count */
605 2           hv_stores(hv, "count", newSVuv(hd->count));
606 2           hv_stores(hv, "max_entries", newSVuv(hd->max_entries));
607 2           hv_stores(hv, "height", newSVuv(hd->height));
608 2           hv_stores(hv, "node_capacity", newSVuv(hd->node_capacity));
609 2           hv_stores(hv, "nodes_used", newSVuv(hd->node_capacity - nfree));
610 2           hv_stores(hv, "index_slots", newSVuv(hd->index_slots));
611 2           hv_stores(hv, "index_load", newSVnv((double)iload / (double)hd->index_slots));
612 2           hv_stores(hv, "ops", newSVuv(hd->stat_ops));
613 2           hv_stores(hv, "mmap_size", newSVuv((UV)h->mmap_size));
614 2           ss_rwlock_rdunlock(h);
615 2           RETVAL = newRV_noinc((SV *)hv);
616             }
617             OUTPUT:
618             RETVAL