File Coverage

blib/lib/Hypersonic/UA/Pool.pm
Criterion Covered Total %
statement 53 53 100.0
branch n/a
condition 2 4 50.0
subroutine 16 16 100.0
pod 0 12 0.0
total 71 85 83.5


line stmt bran cond sub pod time code
1             package Hypersonic::UA::Pool;
2              
3 1     1   219968 use strict;
  1         2  
  1         29  
4 1     1   4 use warnings;
  1         1  
  1         42  
5 1     1   12 use 5.010;
  1         5  
6              
7             our $VERSION = '0.15';
8              
9             use constant {
10 1         1527 MAX_PER_HOST => 6,
11             MAX_TOTAL => 100,
12             MAX_HOSTS => 256,
13             IDLE_TIMEOUT => 60,
14 1     1   5 };
  1         1  
15              
16             sub generate_c_code {
17 1     1 0 12318 my ($class, $builder, $opts) = @_;
18              
19 1   50     6 my $max_per_host = $opts->{max_per_host} // MAX_PER_HOST;
20 1   50     4 my $max_hosts = $opts->{max_hosts} // MAX_HOSTS;
21              
22 1         4 $class->gen_pool_structures($builder, $max_per_host, $max_hosts);
23 1         5 $class->gen_pool_helpers($builder);
24 1         3 $class->gen_xs_init($builder);
25 1         2 $class->gen_xs_get($builder);
26 1         5 $class->gen_xs_put($builder);
27 1         3 $class->gen_xs_remove($builder);
28 1         3 $class->gen_xs_clear($builder);
29 1         3 $class->gen_xs_prune($builder);
30 1         3 $class->gen_xs_stats($builder);
31 1         2 $class->gen_xs_is_alive($builder);
32             }
33              
34             sub get_xs_functions {
35             return {
36 1     1 0 6540 'Hypersonic::UA::Pool::init' => { source => 'xs_pool_init', is_xs_native => 1 },
37             'Hypersonic::UA::Pool::get' => { source => 'xs_pool_get', is_xs_native => 1 },
38             'Hypersonic::UA::Pool::put' => { source => 'xs_pool_put', is_xs_native => 1 },
39             'Hypersonic::UA::Pool::remove' => { source => 'xs_pool_remove', is_xs_native => 1 },
40             'Hypersonic::UA::Pool::clear' => { source => 'xs_pool_clear', is_xs_native => 1 },
41             'Hypersonic::UA::Pool::prune' => { source => 'xs_pool_prune', is_xs_native => 1 },
42             'Hypersonic::UA::Pool::stats' => { source => 'xs_pool_stats', is_xs_native => 1 },
43             'Hypersonic::UA::Pool::is_alive' => { source => 'xs_pool_is_alive', is_xs_native => 1 },
44             };
45             }
46              
47             sub gen_pool_structures {
48 1     1 0 2 my ($class, $builder, $max_per_host, $max_hosts) = @_;
49              
50 1         13 $builder->line('#include ')
51             ->line('#include ')
52             ->line('#include ')
53             ->line('#include ')
54             ->line('#include ')
55             ->line('#include ')
56             ->line('#include ')
57             ->blank;
58              
59 1         7 $builder->line("#define POOL_MAX_PER_HOST $max_per_host")
60             ->line("#define POOL_MAX_HOSTS $max_hosts")
61             ->blank;
62              
63 1         9 $builder->line('typedef struct {')
64             ->line(' int fd;')
65             ->line(' int tls;')
66             ->line(' time_t last_used;')
67             ->line(' int in_use;')
68             ->line('} PoolConn;')
69             ->blank;
70              
71 1         9 $builder->line('typedef struct {')
72             ->line(' char host[256];')
73             ->line(' uint16_t port;')
74             ->line(' int tls;')
75             ->line(' int count;')
76             ->line(" PoolConn conns[POOL_MAX_PER_HOST];")
77             ->line('} PoolBucket;')
78             ->blank;
79              
80 1         11 $builder->line('typedef struct {')
81             ->line(' int max_per_host;')
82             ->line(' int max_total;')
83             ->line(' int idle_timeout;')
84             ->line(' int total_count;')
85             ->line(' int hits;')
86             ->line(' int misses;')
87             ->line(" PoolBucket buckets[POOL_MAX_HOSTS];")
88             ->line(' int bucket_count;')
89             ->line('} ConnectionPool;')
90             ->blank;
91              
92 1         3 $builder->line('static ConnectionPool g_pool;')
93             ->blank;
94             }
95              
96             sub gen_pool_helpers {
97 1     1 0 2 my ($class, $builder) = @_;
98              
99             # Find bucket by host:port:tls
100 1         11 $builder->line('static PoolBucket* pool_find_bucket(const char* host, uint16_t port, int tls) {')
101             ->line(' int i;')
102             ->line(' for (i = 0; i < g_pool.bucket_count; i++) {')
103             ->line(' PoolBucket* b = &g_pool.buckets[i];')
104             ->line(' if (b->port == port && b->tls == tls && strcasecmp(b->host, host) == 0) {')
105             ->line(' return b;')
106             ->line(' }')
107             ->line(' }')
108             ->line(' return NULL;')
109             ->line('}')
110             ->blank;
111              
112             # Create or find bucket
113 1         15 $builder->line('static PoolBucket* pool_get_bucket(const char* host, uint16_t port, int tls) {')
114             ->line(' PoolBucket* b = pool_find_bucket(host, port, tls);')
115             ->line(' if (b) return b;')
116             ->blank
117             ->line(' if (g_pool.bucket_count >= POOL_MAX_HOSTS) return NULL;')
118             ->blank
119             ->line(' b = &g_pool.buckets[g_pool.bucket_count++];')
120             ->line(' memset(b, 0, sizeof(PoolBucket));')
121             ->line(' strncpy(b->host, host, 255);')
122             ->line(' b->host[255] = \'\\0\';')
123             ->line(' b->port = port;')
124             ->line(' b->tls = tls;')
125             ->line(' return b;')
126             ->line('}')
127             ->blank;
128              
129             # Check if socket is alive
130 1         29 $builder->line('static int pool_check_alive(int fd) {')
131             ->line(' fd_set rfds;')
132             ->line(' FD_ZERO(&rfds);')
133             ->line(' FD_SET(fd, &rfds);')
134             ->blank
135             ->line(' struct timeval tv = {0, 0};')
136             ->line(' int ready = select(fd + 1, &rfds, NULL, NULL, &tv);')
137             ->blank
138             ->line(' if (ready > 0) {')
139             ->line(' char peek;')
140             ->line(' int n = recv(fd, &peek, 1, MSG_PEEK | MSG_DONTWAIT);')
141             ->line(' if (n == 0) return 0;')
142             ->line(' if (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK) return 0;')
143             ->line(' }')
144             ->blank
145             ->line(' return 1;')
146             ->line('}')
147             ->blank;
148              
149             # Close a connection
150 1         8 $builder->line('static void pool_close_conn(PoolConn* c) {')
151             ->line(' if (c->fd > 0) {')
152             ->line(' close(c->fd);')
153             ->line(' }')
154             ->line(' c->fd = 0;')
155             ->line(' c->in_use = 0;')
156             ->line('}')
157             ->blank;
158             }
159              
160             sub gen_xs_init {
161 1     1 0 1 my ($class, $builder) = @_;
162              
163 1         25 $builder->comment('Initialize connection pool')
164             ->xs_function('xs_pool_init')
165             ->xs_preamble
166             ->line('int max_per_host;')
167             ->line('int max_total;')
168             ->line('int idle_timeout;')
169             ->blank
170             ->line('if (items > 3) croak("Usage: init([max_per_host], [max_total], [idle_timeout])");')
171             ->blank
172             ->line('max_per_host = (items > 0) ? SvIV(ST(0)) : 6;')
173             ->line('max_total = (items > 1) ? SvIV(ST(1)) : 100;')
174             ->line('idle_timeout = (items > 2) ? SvIV(ST(2)) : 60;')
175             ->blank
176             ->line('memset(&g_pool, 0, sizeof(g_pool));')
177             ->line('g_pool.max_per_host = max_per_host;')
178             ->line('g_pool.max_total = max_total;')
179             ->line('g_pool.idle_timeout = idle_timeout;')
180             ->blank
181             ->line('ST(0) = sv_2mortal(newSViv(1));')
182             ->xs_return('1')
183             ->xs_end
184             ->blank;
185             }
186              
187             sub gen_xs_get {
188 1     1 0 2 my ($class, $builder) = @_;
189              
190 1         76 $builder->comment('Get connection from pool')
191             ->xs_function('xs_pool_get')
192             ->xs_preamble
193             ->line('const char* host;')
194             ->line('uint16_t port;')
195             ->line('int tls;')
196             ->line('PoolBucket* b;')
197             ->line('int i;')
198             ->line('time_t now;')
199             ->blank
200             ->line('if (items != 3) croak("Usage: get(host, port, tls)");')
201             ->blank
202             ->line('host = SvPV_nolen(ST(0));')
203             ->line('port = (uint16_t)SvIV(ST(1));')
204             ->line('tls = SvIV(ST(2));')
205             ->blank
206             ->line('b = pool_find_bucket(host, port, tls);')
207             ->line('if (!b || b->count == 0) {')
208             ->line(' g_pool.misses++;')
209             ->line(' ST(0) = &PL_sv_undef;')
210             ->line(' XSRETURN(1);')
211             ->line('}')
212             ->blank
213             ->line('now = time(NULL);')
214             ->blank
215             ->line('for (i = 0; i < POOL_MAX_PER_HOST; i++) {')
216             ->line(' PoolConn* c = &b->conns[i];')
217             ->line(' if (c->fd <= 0 || c->in_use) continue;')
218             ->blank
219             ->line(' int age = now - c->last_used;')
220             ->line(' if (age >= g_pool.idle_timeout) {')
221             ->line(' pool_close_conn(c);')
222             ->line(' b->count--;')
223             ->line(' g_pool.total_count--;')
224             ->line(' continue;')
225             ->line(' }')
226             ->blank
227             ->line(' if (pool_check_alive(c->fd)) {')
228             ->line(' c->in_use = 1;')
229             ->line(' g_pool.hits++;')
230             ->line(' ST(0) = sv_2mortal(newSViv(c->fd));')
231             ->line(' XSRETURN(1);')
232             ->line(' } else {')
233             ->line(' pool_close_conn(c);')
234             ->line(' b->count--;')
235             ->line(' g_pool.total_count--;')
236             ->line(' }')
237             ->line('}')
238             ->blank
239             ->line('g_pool.misses++;')
240             ->line('ST(0) = &PL_sv_undef;')
241             ->xs_return('1')
242             ->xs_end
243             ->blank;
244             }
245              
246             sub gen_xs_put {
247 1     1 0 2 my ($class, $builder) = @_;
248              
249 1         53 $builder->comment('Return connection to pool')
250             ->xs_function('xs_pool_put')
251             ->xs_preamble
252             ->line('const char* host;')
253             ->line('uint16_t port;')
254             ->line('int tls;')
255             ->line('int fd;')
256             ->line('PoolBucket* b;')
257             ->line('int i;')
258             ->blank
259             ->line('if (items != 4) croak("Usage: put(host, port, tls, fd)");')
260             ->blank
261             ->line('host = SvPV_nolen(ST(0));')
262             ->line('port = (uint16_t)SvIV(ST(1));')
263             ->line('tls = SvIV(ST(2));')
264             ->line('fd = SvIV(ST(3));')
265             ->blank
266             ->line('if (g_pool.total_count >= g_pool.max_total) {')
267             ->line(' close(fd);')
268             ->line(' ST(0) = sv_2mortal(newSViv(0));')
269             ->line(' XSRETURN(1);')
270             ->line('}')
271             ->blank
272             ->line('b = pool_get_bucket(host, port, tls);')
273             ->line('if (!b) {')
274             ->line(' close(fd);')
275             ->line(' ST(0) = sv_2mortal(newSViv(0));')
276             ->line(' XSRETURN(1);')
277             ->line('}')
278             ->line('if (b->count >= g_pool.max_per_host) {')
279             ->line(' time_t oldest_time = time(NULL);')
280             ->line(' int oldest_idx = -1;')
281             ->line(' for (i = 0; i < POOL_MAX_PER_HOST; i++) {')
282             ->line(' if (b->conns[i].fd > 0 && !b->conns[i].in_use) {')
283             ->line(' if (b->conns[i].last_used < oldest_time) {')
284             ->line(' oldest_time = b->conns[i].last_used;')
285             ->line(' oldest_idx = i;')
286             ->line(' }')
287             ->line(' }')
288             ->line(' }')
289             ->line(' if (oldest_idx >= 0) {')
290             ->line(' pool_close_conn(&b->conns[oldest_idx]);')
291             ->line(' b->count--;')
292             ->line(' g_pool.total_count--;')
293             ->line(' }')
294             ->line('}')
295             ->blank
296             ->line('for (i = 0; i < POOL_MAX_PER_HOST; i++) {')
297             ->line(' if (b->conns[i].fd <= 0) {')
298             ->line(' b->conns[i].fd = fd;')
299             ->line(' b->conns[i].tls = tls;')
300             ->line(' b->conns[i].last_used = time(NULL);')
301             ->line(' b->conns[i].in_use = 0;')
302             ->line(' b->count++;')
303             ->line(' g_pool.total_count++;')
304             ->line(' ST(0) = sv_2mortal(newSViv(1));')
305             ->line(' XSRETURN(1);')
306             ->line(' }')
307             ->line('}')
308             ->blank
309             ->line('close(fd);')
310             ->line('ST(0) = sv_2mortal(newSViv(0));')
311             ->xs_return('1')
312             ->xs_end
313             ->blank;
314             }
315              
316             sub gen_xs_remove {
317 1     1 0 21 my ($class, $builder) = @_;
318              
319 1         38 $builder->comment('Remove connection from pool')
320             ->xs_function('xs_pool_remove')
321             ->xs_preamble
322             ->line('const char* host;')
323             ->line('uint16_t port;')
324             ->line('int tls;')
325             ->line('int fd;')
326             ->line('int i;')
327             ->line('PoolBucket* b;')
328             ->blank
329             ->line('if (items != 4) croak("Usage: remove(host, port, tls, fd)");')
330             ->blank
331             ->line('host = SvPV_nolen(ST(0));')
332             ->line('port = (uint16_t)SvIV(ST(1));')
333             ->line('tls = SvIV(ST(2));')
334             ->line('fd = SvIV(ST(3));')
335             ->blank
336             ->line('b = pool_find_bucket(host, port, tls);')
337             ->line('if (!b) {')
338             ->line(' ST(0) = sv_2mortal(newSViv(0));')
339             ->line(' XSRETURN(1);')
340             ->line('}')
341             ->blank
342             ->line('for (i = 0; i < POOL_MAX_PER_HOST; i++) {')
343             ->line(' if (b->conns[i].fd == fd) {')
344             ->line(' pool_close_conn(&b->conns[i]);')
345             ->line(' b->count--;')
346             ->line(' g_pool.total_count--;')
347             ->line(' ST(0) = sv_2mortal(newSViv(1));')
348             ->line(' XSRETURN(1);')
349             ->line(' }')
350             ->line('}')
351             ->blank
352             ->line('ST(0) = sv_2mortal(newSViv(0));')
353             ->xs_return('1')
354             ->xs_end
355             ->blank;
356             }
357              
358             sub gen_xs_clear {
359 1     1 0 1 my ($class, $builder) = @_;
360              
361 1         28 $builder->comment('Clear all connections')
362             ->xs_function('xs_pool_clear')
363             ->xs_preamble
364             ->blank
365             ->line('int i, j;')
366             ->line('for (i = 0; i < g_pool.bucket_count; i++) {')
367             ->line(' PoolBucket* b = &g_pool.buckets[i];')
368             ->line(' for (j = 0; j < POOL_MAX_PER_HOST; j++) {')
369             ->line(' if (b->conns[j].fd > 0) {')
370             ->line(' pool_close_conn(&b->conns[j]);')
371             ->line(' }')
372             ->line(' }')
373             ->line('}')
374             ->blank
375             ->line('g_pool.bucket_count = 0;')
376             ->line('g_pool.total_count = 0;')
377             ->blank
378             ->line('ST(0) = sv_2mortal(newSViv(1));')
379             ->xs_return('1')
380             ->xs_end
381             ->blank;
382             }
383              
384             sub gen_xs_prune {
385 1     1 0 2 my ($class, $builder) = @_;
386              
387 1         23 $builder->comment('Prune expired connections')
388             ->xs_function('xs_pool_prune')
389             ->xs_preamble
390             ->blank
391             ->line('int i, j;')
392             ->line('time_t now = time(NULL);')
393             ->line('int pruned = 0;')
394             ->blank
395             ->line('for (i = 0; i < g_pool.bucket_count; i++) {')
396             ->line(' PoolBucket* b = &g_pool.buckets[i];')
397             ->line(' for (j = 0; j < POOL_MAX_PER_HOST; j++) {')
398             ->line(' PoolConn* c = &b->conns[j];')
399             ->line(' if (c->fd > 0 && !c->in_use) {')
400             ->line(' int age = now - c->last_used;')
401             ->line(' if (age >= g_pool.idle_timeout) {')
402             ->line(' pool_close_conn(c);')
403             ->line(' b->count--;')
404             ->line(' g_pool.total_count--;')
405             ->line(' pruned++;')
406             ->line(' }')
407             ->line(' }')
408             ->line(' }')
409             ->line('}')
410             ->blank
411             ->line('ST(0) = sv_2mortal(newSViv(pruned));')
412             ->xs_return('1')
413             ->xs_end
414             ->blank;
415             }
416              
417             sub gen_xs_stats {
418 1     1 0 2 my ($class, $builder) = @_;
419              
420 1         56 $builder->comment('Get pool statistics')
421             ->xs_function('xs_pool_stats')
422             ->xs_preamble
423             ->blank
424             ->line('HV* stats = newHV();')
425             ->blank
426             ->line('hv_stores(stats, "total_connections", newSViv(g_pool.total_count));')
427             ->line('hv_stores(stats, "hosts_tracked", newSViv(g_pool.bucket_count));')
428             ->line('hv_stores(stats, "max_per_host", newSViv(g_pool.max_per_host));')
429             ->line('hv_stores(stats, "max_total", newSViv(g_pool.max_total));')
430             ->line('hv_stores(stats, "idle_timeout", newSViv(g_pool.idle_timeout));')
431             ->line('hv_stores(stats, "hits", newSViv(g_pool.hits));')
432             ->line('hv_stores(stats, "misses", newSViv(g_pool.misses));')
433             ->blank
434             ->line('double hit_rate = 0.0;')
435             ->line('int total_requests = g_pool.hits + g_pool.misses;')
436             ->line('if (total_requests > 0) {')
437             ->line(' hit_rate = (double)g_pool.hits / total_requests;')
438             ->line('}')
439             ->line('hv_stores(stats, "hit_rate", newSVnv(hit_rate));')
440             ->blank
441             ->line('ST(0) = sv_2mortal(newRV_noinc((SV*)stats));')
442             ->xs_return('1')
443             ->xs_end
444             ->blank;
445             }
446              
447             sub gen_xs_is_alive {
448 1     1 0 2 my ($class, $builder) = @_;
449              
450 1         12 $builder->comment('Check if fd is alive')
451             ->xs_function('xs_pool_is_alive')
452             ->xs_preamble
453             ->line('if (items != 1) croak("Usage: is_alive(fd)");')
454             ->blank
455             ->line('int fd = SvIV(ST(0));')
456             ->line('int alive = pool_check_alive(fd);')
457             ->blank
458             ->line('ST(0) = alive ? &PL_sv_yes : &PL_sv_no;')
459             ->xs_return('1')
460             ->xs_end
461             ->blank;
462             }
463              
464             1;
465              
466             __END__