File Coverage

Shared.xs
Criterion Covered Total %
statement 112 114 98.2
branch 98 160 61.2
condition n/a
subroutine n/a
pod n/a
total 210 274 76.6


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 "cms.h"
7              
8             #define EXTRACT(sv) \
9             if (!sv_isobject(sv) || !sv_derived_from(sv, "Data::CountMinSketch::Shared")) \
10             croak("Expected a Data::CountMinSketch::Shared object"); \
11             CmsHandle *h = INT2PTR(CmsHandle*, SvIV(SvRV(sv))); \
12             if (!h) croak("Attempted to use a destroyed Data::CountMinSketch::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             MODULE = Data::CountMinSketch::Shared PACKAGE = Data::CountMinSketch::Shared
21              
22             PROTOTYPES: DISABLE
23              
24             SV *
25             new(class, path = &PL_sv_undef, epsilon = 0.001, delta = 0.001)
26             const char *class
27             SV *path
28             double epsilon
29             double delta
30             PREINIT:
31             char errbuf[CMS_ERR_BUFLEN];
32             CODE:
33 31 100         const char *p = SvOK(path) ? SvPV_nolen(path) : NULL;
34 31           CmsHandle *h = cms_create(p, epsilon, delta, errbuf); /* validates epsilon/delta into errbuf */
35 31 100         if (!h) croak("Data::CountMinSketch::Shared->new: %s", errbuf);
36 24           MAKE_OBJ(class, h);
37             OUTPUT:
38             RETVAL
39              
40             SV *
41             new_memfd(class, name = &PL_sv_undef, epsilon = 0.001, delta = 0.001)
42             const char *class
43             SV *name
44             double epsilon
45             double delta
46             PREINIT:
47             char errbuf[CMS_ERR_BUFLEN];
48             CODE:
49 4 100         const char *nm = SvOK(name) ? SvPV_nolen(name) : NULL; /* undef -> default label */
50 4           CmsHandle *h = cms_create_memfd(nm, epsilon, delta, errbuf); /* validates epsilon/delta into errbuf */
51 4 100         if (!h) croak("Data::CountMinSketch::Shared->new_memfd: %s", errbuf);
52 2           MAKE_OBJ(class, h);
53             OUTPUT:
54             RETVAL
55              
56             SV *
57             new_from_fd(class, fd)
58             const char *class
59             int fd
60             PREINIT:
61             char errbuf[CMS_ERR_BUFLEN];
62             CODE:
63 2           CmsHandle *h = cms_open_fd(fd, errbuf);
64 2 100         if (!h) croak("Data::CountMinSketch::Shared->new_from_fd: %s", errbuf);
65 1           MAKE_OBJ(class, h);
66             OUTPUT:
67             RETVAL
68              
69             void
70             DESTROY(self)
71             SV *self
72             CODE:
73 29 50         if (sv_isobject(self) && sv_derived_from(self, "Data::CountMinSketch::Shared")) {
    50          
74 29           CmsHandle *h = INT2PTR(CmsHandle*, SvIV(SvRV(self)));
75 29 100         if (h) { sv_setiv(SvRV(self), 0); cms_destroy(h); } /* null first: activates EXTRACT's use-after-destroy croak + makes a double DESTROY a no-op */
76             }
77              
78             UV
79             add(self, item, n = 1)
80             SV *self
81             SV *item
82             UV n
83             PREINIT:
84 7127 50         EXTRACT(self);
    50          
    50          
85             STRLEN len;
86             const char *s;
87             UV total;
88             CODE:
89 7127           s = SvPVbyte(item, len); /* may croak (wide char) -- BEFORE the lock */
90 7126           cms_rwlock_wrlock(h);
91 7126           cms_add_locked(h, s, len, (uint64_t)n);
92 7126           total = (UV)h->hdr->total;
93 7126           __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
94 7126           cms_rwlock_wrunlock(h);
95 7126 100         RETVAL = total;
96             OUTPUT:
97             RETVAL
98              
99             UV
100             add_many(self, items)
101             SV *self
102             SV *items
103             PREINIT:
104 7 50         EXTRACT(self);
    50          
    50          
105             AV *av;
106             IV top;
107             CODE:
108 7 100         if (!SvROK(items) || SvTYPE(SvRV(items)) != SVt_PVAV)
    100          
109 2           croak("Data::CountMinSketch::Shared->add_many: expected an array reference");
110 5           av = (AV *)SvRV(items);
111 5           top = av_len(av); /* last index, -1 if empty */
112             {
113 5 100         STRLEN cnt = (top >= 0) ? (STRLEN)(top + 1) : 0, i;
114 5           const char **ps = NULL; STRLEN *ls = NULL;
115 5 100         if (cnt) { /* resolve all bytes BEFORE locking */
116 4 50         Newx(ps, cnt, const char *); SAVEFREEPV(ps); /* freed on return OR unwind */
117 4 50         Newx(ls, cnt, STRLEN); SAVEFREEPV(ls);
118 2505 100         for (i = 0; i < cnt; i++) { /* a croak here holds NO lock; SAVEFREEPV cleans up */
119 2502           SV **el = av_fetch(av, (SSize_t)i, 0);
120 2502 50         if (el && *el) ps[i] = SvPVbyte(*el, ls[i]);
    50          
121 0           else { ps[i] = ""; ls[i] = 0; }
122             }
123             }
124 4           cms_rwlock_wrlock(h); /* locked region: NO croak-capable calls */
125 2504 100         for (i = 0; i < cnt; i++) cms_add_locked(h, ps[i], ls[i], 1);
126 4           __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED); /* a call always counts, even an empty batch */
127 4           cms_rwlock_wrunlock(h);
128 4 50         RETVAL = (UV)cnt; /* every element is added (CMS add never fails) */
129             }
130             OUTPUT:
131             RETVAL
132              
133             UV
134             estimate(self, item)
135             SV *self
136             SV *item
137             PREINIT:
138 10019 50         EXTRACT(self);
    50          
    100          
139             STRLEN len;
140             const char *s;
141             UV est;
142             CODE:
143 10018           s = SvPVbyte(item, len); /* may croak (wide char) -- BEFORE the lock */
144 10018           cms_rwlock_rdlock(h);
145 10018           est = (UV)cms_estimate_locked(h, s, len);
146 10018           cms_rwlock_rdunlock(h);
147 10018 100         RETVAL = est;
148             OUTPUT:
149             RETVAL
150              
151             void
152             merge(self, other)
153             SV *self
154             SV *other
155             PREINIT:
156 4 50         EXTRACT(self);
    50          
    50          
157             CODE:
158 4 50         if (!sv_isobject(other) || !sv_derived_from(other, "Data::CountMinSketch::Shared"))
    50          
159 0           croak("Data::CountMinSketch::Shared->merge: expected a Data::CountMinSketch::Shared object");
160 4           CmsHandle *o = INT2PTR(CmsHandle*, SvIV(SvRV(other)));
161 4 50         if (!o) croak("Attempted to use a destroyed Data::CountMinSketch::Shared object");
162              
163             /* w and d are immutable after creation -- compare lock-free, croak BEFORE
164             * allocating, so a mismatch holds no lock and leaks no buffer. */
165 4           uint64_t ow = o->hdr->w;
166 4           uint32_t od = o->hdr->d;
167 4 100         if (ow != h->hdr->w || od != h->hdr->d)
    50          
168 1           croak("Data::CountMinSketch::Shared->merge: geometry mismatch (w=%llu/d=%u vs w=%llu/d=%u)",
169             (unsigned long long)h->hdr->w, h->hdr->d,
170             (unsigned long long)ow, od);
171              
172             /* Snapshot the other's cells (+ its total) under its read lock into a temp
173             * buffer, then release before taking self's write lock. Copying to a temp
174             * avoids holding two locks at once (deadlock-free regardless of acquisition
175             * order between two processes merging each other). */
176 3           uint64_t cells = (uint64_t)od * ow;
177             uint64_t other_total;
178             uint64_t *tmp;
179 3 50         Newx(tmp, (size_t)cells, uint64_t);
180 3           SAVEFREEPV(tmp); /* freed on normal return OR croak unwind */
181 3           cms_rwlock_rdlock(o);
182 3           memcpy(tmp, cms_counters(o), (size_t)cells * sizeof(uint64_t));
183 3           other_total = o->hdr->total;
184 3           cms_rwlock_rdunlock(o);
185              
186 3           cms_rwlock_wrlock(h);
187 3           cms_merge_counters(cms_counters(h), tmp, cells);
188 3 50         if (h->hdr->total > UINT64_MAX - other_total) h->hdr->total = UINT64_MAX;
189 3           else h->hdr->total += other_total;
190 3           __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
191 3           cms_rwlock_wrunlock(h);
192              
193             void
194             clear(self)
195             SV *self
196             PREINIT:
197 1 50         EXTRACT(self);
    50          
    50          
198             CODE:
199 1           cms_rwlock_wrlock(h);
200 1           cms_clear_locked(h);
201 1           __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
202 1           cms_rwlock_wrunlock(h);
203              
204             UV
205             total(self)
206             SV *self
207             PREINIT:
208 14 50         EXTRACT(self);
    50          
    50          
209             UV t;
210             CODE:
211 14           cms_rwlock_rdlock(h);
212 14           t = (UV)h->hdr->total;
213 14           cms_rwlock_rdunlock(h);
214 14 50         RETVAL = t;
215             OUTPUT:
216             RETVAL
217              
218             UV
219             width(self)
220             SV *self
221             PREINIT:
222 7 50         EXTRACT(self);
    50          
    50          
223             CODE:
224 7 50         RETVAL = (UV)h->hdr->w;
225             OUTPUT:
226             RETVAL
227              
228             UV
229             depth(self)
230             SV *self
231             PREINIT:
232 6 50         EXTRACT(self);
    50          
    50          
233             CODE:
234 6 50         RETVAL = (UV)h->hdr->d;
235             OUTPUT:
236             RETVAL
237              
238             UV
239             cells(self)
240             SV *self
241             PREINIT:
242 1 50         EXTRACT(self);
    50          
    50          
243             CODE:
244 1 50         RETVAL = (UV)((uint64_t)h->hdr->d * h->hdr->w);
245             OUTPUT:
246             RETVAL
247              
248             SV *
249             stats(self)
250             SV *self
251             PREINIT:
252 3 50         EXTRACT(self);
    50          
    50          
253             CODE:
254             {
255             uint64_t w, total, ops, cells;
256             uint32_t d;
257             /* Snapshot under the lock; do all (croak-capable) Perl allocation after
258             releasing it -- so an OOM in newHV/newSVuv can never strand the lock. */
259 3           cms_rwlock_rdlock(h);
260 3           w = h->hdr->w;
261 3           d = h->hdr->d;
262 3           total = h->hdr->total;
263 3           ops = h->hdr->stat_ops;
264 3           cms_rwlock_rdunlock(h);
265 3           cells = (uint64_t)d * w;
266              
267 3           HV *hv = newHV();
268 3           hv_stores(hv, "width", newSVuv((UV)w));
269 3           hv_stores(hv, "depth", newSVuv((UV)d));
270 3           hv_stores(hv, "total", newSVuv((UV)total));
271 3           hv_stores(hv, "cells", newSVuv((UV)cells));
272 3           hv_stores(hv, "epsilon", newSVnv(M_E / (double)w)); /* achieved error factor */
273 3           hv_stores(hv, "delta", newSVnv(exp(-(double)d))); /* achieved failure prob */
274 3           hv_stores(hv, "ops", newSVuv((UV)ops));
275 3           hv_stores(hv, "mmap_size", newSVuv((UV)h->mmap_size));
276 3           RETVAL = newRV_noinc((SV *)hv);
277             }
278             OUTPUT:
279             RETVAL
280              
281             SV *
282             path(self)
283             SV *self
284             PREINIT:
285 3 50         EXTRACT(self);
    50          
    50          
286             CODE:
287 3 100         RETVAL = h->path ? newSVpv(h->path, 0) : &PL_sv_undef;
288             OUTPUT:
289             RETVAL
290              
291             int
292             memfd(self)
293             SV *self
294             PREINIT:
295 3 50         EXTRACT(self);
    50          
    50          
296             CODE:
297 3 50         RETVAL = h->backing_fd;
298             OUTPUT:
299             RETVAL
300              
301             void
302             sync(self)
303             SV *self
304             PREINIT:
305 2 50         EXTRACT(self);
    50          
    50          
306             CODE:
307 2 50         if (cms_msync(h) != 0) croak("sync: %s", strerror(errno));
308              
309             void
310             unlink(self, ...)
311             SV *self
312             CODE:
313 3 100         if (sv_isobject(self) && sv_derived_from(self, "Data::CountMinSketch::Shared")) {
    50          
314 1           CmsHandle *h = INT2PTR(CmsHandle*, SvIV(SvRV(self)));
315 1 50         if (h && h->path) unlink(h->path);
    50          
316 1 50         } else if (items >= 2 && SvOK(ST(1))) {
    50          
317 1           unlink(SvPV_nolen(ST(1)));
318             }