File Coverage

Shared.xs
Criterion Covered Total %
statement 120 122 98.3
branch 109 176 61.9
condition n/a
subroutine n/a
pod n/a
total 229 298 76.8


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