File Coverage

feersum_conn.xs
Criterion Covered Total %
statement 253 324 78.0
branch 203 328 61.8
condition n/a
subroutine n/a
pod n/a
total 456 652 69.9


line stmt bran cond sub pod time code
1             MODULE = Feersum PACKAGE = Feersum::Connection::Handle
2              
3             PROTOTYPES: ENABLE
4              
5             int
6             fileno (feer_conn_handle *hdl)
7             CODE:
8 0 0         RETVAL = c->fd;
9             OUTPUT:
10             RETVAL
11              
12             void
13             DESTROY (SV *self)
14             ALIAS:
15             Feersum::Connection::Reader::DESTROY = 1
16             Feersum::Connection::Writer::DESTROY = 2
17             PPCODE:
18             {
19 388           feer_conn_handle *hdl = sv_2feer_conn_handle(self, 0);
20              
21 388 100         if (hdl == NULL) {
22             trace3("DESTROY handle (closed) class=%s\n",
23             HvNAME(SvSTASH(SvRV(self))));
24             }
25             else {
26 316           struct feer_conn *c = (struct feer_conn *)hdl;
27             trace3("DESTROY handle fd=%d, class=%s\n", c->fd,
28             HvNAME(SvSTASH(SvRV(self))));
29 316 100         if (ix == 2)
30 15           feersum_close_handle(aTHX_ c, 1);
31             else
32 301           SvREFCNT_dec(c->self); // reader: balance new_feer_conn_handle
33             }
34             }
35              
36             SV*
37             read (feer_conn_handle *hdl, SV *buf, size_t len, ...)
38             PROTOTYPE: $$$;$
39             PPCODE:
40             {
41 99           STRLEN buf_len = 0, src_len = 0;
42             ssize_t offset;
43 99           char *src_ptr = NULL;
44              
45             // optimizes for the "read everything" case.
46              
47 99 100         if (unlikely(items == 4) && SvOK(ST(3)) && SvIOK(ST(3)))
    50          
    50          
48 2           offset = SvIV(ST(3));
49             else
50 97           offset = 0;
51              
52             trace("read fd=%d : request len=%"Sz_uf" off=%"Ssz_df"\n",
53             c->fd, (Sz)len, (Ssz)offset);
54              
55 99 50         if (unlikely(c->receiving <= RECEIVE_HEADERS))
56 0           croak("can't call read() until the body begins to arrive");
57              
58 99 100         if (!SvPOK(buf)) {
59             // force to a PV and ensure buffer space
60 9           sv_setpvn(buf,"",0);
61 9 50         SvGROW(buf, len+1);
    100          
62             }
63              
64 99 50         if (unlikely(SvREADONLY(buf)))
65 0           croak("buffer must not be read-only");
66              
67 99 50         if (unlikely(len == 0))
68 0           XSRETURN_IV(0); // assumes undef buffer got allocated to empty-string
69              
70 99           (void)SvPV(buf, buf_len);
71 99 100         if (likely(c->rbuf))
72 96           src_ptr = SvPV(c->rbuf, src_len);
73              
74 99 100         if (unlikely(offset < 0))
75 1 50         offset = (-offset >= c->received_cl) ? 0 : c->received_cl + offset;
76              
77             // Defensive: ensure offset doesn't exceed buffer (shouldn't happen in normal operation)
78 99 50         if (unlikely(offset > (ssize_t)src_len))
79 0           offset = src_len;
80              
81 99 100         if (unlikely(len + offset > src_len))
82 9           len = src_len - offset;
83              
84             // Don't read past Content-Length boundary into pipelined request data
85 99 50         if (c->expected_cl > 0) {
86 99           ssize_t consumed = c->received_cl - (ssize_t)src_len;
87 99           ssize_t remaining_body = c->expected_cl - consumed - offset;
88 99 100         if (remaining_body <= 0)
89 3           XSRETURN_IV(0);
90 96 100         if ((ssize_t)len > remaining_body)
91 1           len = (size_t)remaining_body;
92             }
93              
94             trace("read fd=%d : normalized len=%"Sz_uf" off=%"Ssz_df" src_len=%"Sz_uf"\n",
95             c->fd, (Sz)len, (Ssz)offset, (Sz)src_len);
96              
97 96 50         if (unlikely(!c->rbuf || src_len == 0 || offset >= c->received_cl)) {
    50          
    50          
    50          
98             trace2("rbuf empty during read %d\n", c->fd);
99 0 0         if (c->receiving == RECEIVE_SHUTDOWN) {
100 0           XSRETURN_IV(0);
101             }
102             else {
103 0           errno = EAGAIN;
104 0           XSRETURN_UNDEF;
105             }
106             }
107              
108 96 100         if (likely(len == src_len && offset == 0)) {
    50          
109             trace2("appending entire rbuf fd=%d\n", c->fd);
110 80           sv_2mortal(c->rbuf); // allow pv to be stolen
111 80 100         if (likely(buf_len == 0)) {
112 78           sv_setsv(buf, c->rbuf);
113             }
114             else {
115 2           sv_catsv(buf, c->rbuf);
116             }
117 80           c->rbuf = NULL;
118             }
119             else {
120 16           src_ptr += offset;
121             trace2("appending partial rbuf fd=%d len=%"Sz_uf" off=%"Ssz_df" ptr=%p\n",
122             c->fd, len, offset, src_ptr);
123 16 100         SvGROW(buf, SvCUR(buf) + len);
    100          
124 16           sv_catpvn(buf, src_ptr, len);
125 16 100         if (likely(items == 3)) {
126             // there wasn't an offset param, throw away beginning
127             // Ensure we own the buffer before modifying with sv_chop
128 14 50         if (unlikely(SvREFCNT(c->rbuf) > 1 || SvREADONLY(c->rbuf))) {
    50          
129 0           SV *copy = newSVsv(c->rbuf);
130 0           SvREFCNT_dec(c->rbuf);
131 0           c->rbuf = copy;
132             }
133             // Safety: ensure len doesn't exceed current buffer length
134 14           STRLEN cur_len = SvCUR(c->rbuf);
135 14 50         if (unlikely(len > cur_len)) len = cur_len;
136 14           sv_chop(c->rbuf, SvPVX(c->rbuf) + len);
137             }
138             }
139              
140 96           XSRETURN_IV(len);
141             }
142              
143             STRLEN
144             write (feer_conn_handle *hdl, ...)
145             PROTOTYPE: $;$
146             CODE:
147             {
148 74 50         if (unlikely(c->responding != RESPOND_STREAMING))
149 0           croak("can only call write in streaming mode");
150              
151             // RFC 7230 §3.3: 1xx/204/205/304 MUST NOT have a body — discard writes
152             // (auto_cl is only set for H1; H2 handles this in its own DATA provider)
153 74 50         if (unlikely(!c->auto_cl && !h2_is_stream(c)))
    0          
154 0           XSRETURN_IV(0);
155              
156 74 50         SV *body = (items == 2) ? ST(1) : &PL_sv_undef;
157 74 50         if (unlikely(!body || !SvOK(body)))
    50          
158 0           XSRETURN_IV(0);
159              
160             trace("write fd=%d c=%p, body=%p\n", c->fd, c, body);
161 74 100         if (SvROK(body)) {
162 1           SV *refd = SvRV(body);
163 1 50         if (SvOK(refd) && SvPOK(refd)) {
    0          
164 0           body = refd;
165             }
166             else {
167 1           croak("body must be a scalar, scalar ref or undef");
168             }
169             }
170 73           (void)SvPV(body, RETVAL);
171              
172 73 50         if (!h2_try_write_chunk(aTHX_ c, body)) {
173 73 100         if (c->use_chunked)
174 45           add_chunk_sv_to_wbuf(c, body);
175             else
176 28           add_sv_to_wbuf(c, body);
177              
178 73           conn_write_ready(c);
179             }
180             }
181             OUTPUT:
182             RETVAL
183              
184             void
185             write_array (feer_conn_handle *hdl, AV *abody)
186             PROTOTYPE: $$
187             PPCODE:
188             {
189 2 50         if (unlikely(c->responding != RESPOND_STREAMING))
190 0           croak("can only call write in streaming mode");
191              
192 2 50         if (unlikely(!c->auto_cl && !h2_is_stream(c)))
    0          
193 0           XSRETURN_EMPTY;
194              
195             trace("write_array fd=%d c=%p, abody=%p\n", c->fd, c, abody);
196              
197 2           I32 amax = av_len(abody);
198              
199 2 50         if (h2_is_stream(c)) {
200             /* H2 stream: feed each element through the H2 data provider */
201 0 0         for (I32 i = 0; i <= amax; i++) {
202 0           SV *sv = fetch_av_normal(aTHX_ abody, i);
203 0 0         if (likely(sv)) h2_try_write_chunk(aTHX_ c, sv);
204             }
205 0           XSRETURN_EMPTY;
206             }
207              
208 2 50         if (c->use_chunked) {
209 12 100         for (I32 i = 0; i <= amax; i++) {
210 10           SV *sv = fetch_av_normal(aTHX_ abody, i);
211 10 100         if (likely(sv)) add_chunk_sv_to_wbuf(c, sv);
212             }
213             }
214             else {
215 0 0         for (I32 i = 0; i <= amax; i++) {
216 0           SV *sv = fetch_av_normal(aTHX_ abody, i);
217 0 0         if (likely(sv)) add_sv_to_wbuf(c, sv);
218             }
219             }
220              
221 2           conn_write_ready(c);
222             }
223              
224             void
225             sendfile (feer_conn_handle *hdl, SV *fh, ...)
226             PROTOTYPE: $$;$$
227             PPCODE:
228             {
229             #ifdef __linux__
230 12 50         if (h2_is_stream(c))
231 0           croak("sendfile not supported for HTTP/2 streams (use write instead)");
232 12 50         if (unlikely(c->responding != RESPOND_STREAMING))
233 0           croak("sendfile: can only call after start_streaming()");
234              
235             // Get file descriptor from filehandle
236 12           int file_fd = -1;
237 12           off_t offset = 0;
238 12           size_t length = 0;
239              
240 12 50         if (SvIOK(fh)) {
241             // Bare file descriptor
242 0           file_fd = SvIV(fh);
243             }
244 24 50         else if (SvROK(fh) && SvTYPE(SvRV(fh)) == SVt_PVGV) {
    50          
245             // Glob reference (filehandle)
246 12           IO *io = GvIOp(SvRV(fh));
247 12 50         if (io && IoIFP(io)) {
    100          
248 11           file_fd = PerlIO_fileno(IoIFP(io));
249             }
250             }
251 0 0         else if (SvTYPE(fh) == SVt_PVGV) {
252             // Bare glob
253 0           IO *io = GvIOp(fh);
254 0 0         if (io && IoIFP(io)) {
    0          
255 0           file_fd = PerlIO_fileno(IoIFP(io));
256             }
257             }
258              
259 12 100         if (file_fd < 0)
260 1           croak("sendfile: invalid file handle");
261              
262             // Get file size for length if not specified
263             struct stat st;
264 11 50         if (fstat(file_fd, &st) < 0)
265 0           croak("sendfile: fstat failed: %s", strerror(errno));
266              
267 11 100         if (!S_ISREG(st.st_mode))
268 1           croak("sendfile: not a regular file");
269              
270             // Parse optional offset and validate before using
271 10 100         if (items >= 3 && SvOK(ST(2))) {
    50          
272 7           IV offset_iv = SvIV(ST(2));
273 7 100         if (offset_iv < 0)
274 1           croak("sendfile: offset must be non-negative");
275 6           offset = (off_t)offset_iv;
276             }
277              
278 9 100         if (st.st_size == 0) {
279 1           XSRETURN_EMPTY;
280             }
281 8 100         if (offset >= st.st_size)
282 1           croak("sendfile: offset out of range");
283              
284 7 100         if (items >= 4 && SvOK(ST(3))) {
    50          
285 2           UV length_uv = SvUV(ST(3));
286             // Check that length fits in ssize_t (signed) before casting
287             // This prevents bypass via values >= 2^63 becoming negative
288             // Use (UV)((~(size_t)0) >> 1) as portable SSIZE_MAX
289 2 50         if (length_uv > (UV)((~(size_t)0) >> 1))
290 0           croak("sendfile: length too large");
291 2           length = (size_t)length_uv;
292             // Validate length doesn't exceed file size - offset
293 2 50         if (length > (size_t)(st.st_size - offset))
294 2           croak("sendfile: offset + length exceeds file size");
295             } else {
296 5           length = st.st_size - offset;
297             }
298              
299 5 50         if (length == 0)
300 0           XSRETURN_EMPTY;
301              
302             trace("sendfile setup: fd=%d file_fd=%d off=%ld len=%zu\n",
303             c->fd, file_fd, (long)offset, length);
304              
305             // Close any in-progress sendfile before starting a new one
306 5 50         CLOSE_SENDFILE_FD(c);
    0          
307             // Dup the fd so we own it (caller can close their handle)
308 5           c->sendfile_fd = dup(file_fd);
309 5 50         if (c->sendfile_fd < 0)
310 0           croak("sendfile: dup failed: %s", strerror(errno));
311              
312 5           c->sendfile_off = offset;
313 5           c->sendfile_remain = length;
314              
315 5           conn_write_ready(c);
316 5           XSRETURN_EMPTY;
317             #else
318             PERL_UNUSED_VAR(fh);
319             croak("sendfile: only supported on Linux");
320             #endif
321             }
322              
323             int
324             seek (feer_conn_handle *hdl, ssize_t offset, ...)
325             PROTOTYPE: $$;$
326             CODE:
327             {
328 7           int whence = SEEK_CUR;
329 7 50         if (items == 3 && SvOK(ST(2)) && SvIOK(ST(2)))
    50          
    50          
330 7           whence = SvIV(ST(2));
331              
332             trace("seek fd=%d offset=%"Ssz_df" whence=%d\n", c->fd, offset, whence);
333              
334 7 50         if (unlikely(!c->rbuf)) {
335             // handle is effectively "closed"
336 0           RETVAL = 0;
337             }
338 7 100         else if (offset == 0) {
339 1           RETVAL = 1; // stay put for any whence
340             }
341 6 100         else if (offset > 0 && (whence == SEEK_CUR || whence == SEEK_SET)) {
    100          
    50          
342             STRLEN len;
343 2           const char *str = SvPV_const(c->rbuf, len);
344 2 50         if (offset > len)
345 0           offset = len;
346             // Ensure we own the buffer before modifying with sv_chop
347             // (sv_chop modifies the SV in-place, unsafe if shared)
348 2 50         if (SvREFCNT(c->rbuf) > 1 || SvREADONLY(c->rbuf)) {
    50          
349 0           SV *copy = newSVsv(c->rbuf);
350 0           SvREFCNT_dec(c->rbuf);
351 0           c->rbuf = copy;
352 0           str = SvPV_const(c->rbuf, len);
353             }
354 2           sv_chop(c->rbuf, str + offset);
355 2           RETVAL = 1;
356             }
357 6 50         else if (offset < 0 && whence == SEEK_END) {
    100          
358             STRLEN len;
359 2           const char *str = SvPV_const(c->rbuf, len);
360 2           offset += len; // can't be > len since block is offset<0
361 2 50         if (offset == 0) {
362 0           RETVAL = 1; // no-op, but OK
363             }
364 2 100         else if (offset > 0) {
365             // Ensure we own the buffer before modifying
366 1 50         if (SvREFCNT(c->rbuf) > 1 || SvREADONLY(c->rbuf)) {
    50          
367 0           SV *copy = newSVsv(c->rbuf);
368 0           SvREFCNT_dec(c->rbuf);
369 0           c->rbuf = copy;
370 0           str = SvPV_const(c->rbuf, len);
371             }
372 1           sv_chop(c->rbuf, str + offset);
373 1           RETVAL = 1;
374             }
375             else {
376             // past beginning of string
377 1           RETVAL = 0;
378             }
379             }
380             else {
381             // invalid seek
382 2           RETVAL = 0;
383             }
384             }
385             OUTPUT:
386             RETVAL
387              
388             int
389             close (feer_conn_handle *hdl)
390             PROTOTYPE: $
391             ALIAS:
392             Feersum::Connection::Reader::close = 1
393             Feersum::Connection::Writer::close = 2
394             CODE:
395             {
396             assert(ix && "close() must be called via Reader::close or Writer::close");
397 46           RETVAL = feersum_close_handle(aTHX_ c, (ix == 2));
398 46 100         SvUVX(hdl_sv) = 0;
399             }
400             OUTPUT:
401             RETVAL
402              
403             void
404             _poll_cb (feer_conn_handle *hdl, SV *cb)
405             PROTOTYPE: $$
406             ALIAS:
407             Feersum::Connection::Reader::poll_cb = 1
408             Feersum::Connection::Writer::poll_cb = 2
409             PPCODE:
410             {
411 23 50         if (unlikely(ix < 1 || ix > 2))
    50          
412 0           croak("can't call _poll_cb directly");
413              
414 23           bool is_read = (ix == 1);
415 23 100         SV **cb_slot = is_read ? &c->poll_read_cb : &c->poll_write_cb;
416              
417 23 100         if (*cb_slot != NULL) {
418 11           SvREFCNT_dec(*cb_slot);
419 11           *cb_slot = NULL;
420             }
421              
422 23 100         if (!SvOK(cb)) {
423             trace("unset poll_cb ix=%d\n", ix);
424 11 100         if (is_read && c->receiving == RECEIVE_STREAMING)
    50          
425 2           change_receiving_state(c, RECEIVE_BODY);
426 11           return;
427             }
428 12 50         else if (unlikely(!IsCodeRef(cb)))
    50          
429 0           croak("must supply a code reference to poll_cb");
430              
431 12           *cb_slot = newSVsv(cb);
432              
433 12 100         if (is_read) {
434             // Switch to streaming receive mode
435             // Allow from RECEIVE_BODY (normal body) or RECEIVE_SHUTDOWN
436             // (post-upgrade, e.g. WebSocket 101 where body reading was stopped)
437 2 50         if (c->receiving == RECEIVE_BODY || c->receiving == RECEIVE_SHUTDOWN) {
    50          
438 2           change_receiving_state(c, RECEIVE_STREAMING);
439             }
440             // If there's already body data in rbuf, call the callback immediately
441 2 50         if (c->rbuf && SvCUR(c->rbuf) > 0) {
    50          
442 2           call_poll_callback(c, 0); // 0 = read callback
443             }
444             else {
445 0           start_read_watcher(c);
446             }
447             }
448             else {
449 10           conn_write_ready(c);
450             }
451             }
452              
453             SV*
454             response_guard (feer_conn_handle *hdl, ...)
455             PROTOTYPE: $;$
456             CODE:
457 3 100         RETVAL = feersum_conn_guard(aTHX_ c, (items==2) ? ST(1) : NULL);
458             OUTPUT:
459             RETVAL
460              
461             void
462             return_from_psgix_io (feer_conn_handle *hdl, SV *io_sv)
463             PROTOTYPE: $$
464             PPCODE:
465             {
466 1           SSize_t cnt = feersum_return_from_io(aTHX_ c, io_sv, "return_from_psgix_io");
467 1 50         mXPUSHi(cnt);
468             }
469              
470             MODULE = Feersum PACKAGE = Feersum::Connection
471              
472             PROTOTYPES: ENABLE
473              
474             SV *
475             start_streaming (struct feer_conn *c, SV *message, AV *headers)
476             PROTOTYPE: $$\@
477             CODE:
478 51           feersum_start_response(aTHX_ c, message, headers, 1);
479 50           RETVAL = new_feer_conn_handle(aTHX_ c, 1); // RETVAL gets mortalized
480             OUTPUT:
481             RETVAL
482              
483             int
484             is_http11 (struct feer_conn *c)
485             CODE:
486 6 50         RETVAL = c->is_http11;
487             OUTPUT:
488             RETVAL
489              
490             size_t
491             send_response (struct feer_conn *c, SV* message, AV *headers, SV *body)
492             PROTOTYPE: $$\@$
493             CODE:
494 394 50         if (unlikely(!SvOK(body)))
495 0           croak("can't send_response with an undef body");
496 394           feersum_start_response(aTHX_ c, message, headers, 0);
497 382           RETVAL = feersum_write_whole_body(aTHX_ c, body);
498             OUTPUT:
499             RETVAL
500              
501             SV*
502             _continue_streaming_psgi (struct feer_conn *c, SV *psgi_response)
503             PROTOTYPE: $\@
504             CODE:
505             {
506             AV *av;
507 8           int len = 0;
508              
509 8 50         if (IsArrayRef(psgi_response)) {
    50          
510 8           av = (AV*)SvRV(psgi_response);
511 8           len = av_len(av) + 1;
512             }
513              
514 8 100         if (len == 3) {
515             // 0 is "don't recurse" (i.e. don't allow another code-ref)
516 3           feersum_handle_psgi_response(aTHX_ c, psgi_response, 0);
517 3           RETVAL = &PL_sv_undef;
518             }
519 5 50         else if (len == 2) {
520 5           SV *message = *(av_fetch(av,0,0));
521 5           SV *headers = *(av_fetch(av,1,0));
522 5 50         if (unlikely(!IsArrayRef(headers)))
    50          
523 0           croak("PSGI headers must be an array ref");
524 5           feersum_start_response(aTHX_ c, message, (AV*)SvRV(headers), 1);
525 4           RETVAL = new_feer_conn_handle(aTHX_ c, 1); // RETVAL gets mortalized
526             }
527             else {
528 0           croak("PSGI response starter expects a 2 or 3 element array-ref");
529             }
530             }
531             OUTPUT:
532             RETVAL
533              
534             void
535             force_http10 (struct feer_conn *c)
536             PROTOTYPE: $
537             ALIAS:
538             force_http11 = 1
539             PPCODE:
540 11           c->is_http11 = ix;
541              
542             SV *
543             env (struct feer_conn *c)
544             PROTOTYPE: $
545             CODE:
546 204           RETVAL = newRV_noinc((SV*)feersum_env(aTHX_ c));
547             OUTPUT:
548             RETVAL
549              
550             SV *
551             method (struct feer_conn *c)
552             PROTOTYPE: $
553             CODE:
554 28           struct feer_req *r = c->req;
555 28 50         if (unlikely(!r))
556 0           croak("Cannot access request method: no active request");
557             #ifdef FEERSUM_HAS_H2
558             RETVAL = feersum_env_method_h2(aTHX_ c, r);
559             #else
560 28           RETVAL = feersum_env_method(aTHX_ r);
561             #endif
562             OUTPUT:
563             RETVAL
564              
565             SV *
566             uri (struct feer_conn *c)
567             PROTOTYPE: $
568             CODE:
569 2           struct feer_req *r = c->req;
570 2 50         if (unlikely(!r))
571 0           croak("Cannot access request URI: no active request");
572 2           RETVAL = feersum_env_uri(aTHX_ r);
573             OUTPUT:
574             RETVAL
575              
576             SV *
577             protocol (struct feer_conn *c)
578             PROTOTYPE: $
579             CODE:
580 2           struct feer_req *r = c->req;
581 2 50         if (unlikely(!r))
582 0           croak("Cannot access request protocol: no active request");
583 2           RETVAL = SvREFCNT_inc_simple_NN(feersum_env_protocol(aTHX_ r));
584             OUTPUT:
585             RETVAL
586              
587             SV *
588             path (struct feer_conn *c)
589             PROTOTYPE: $
590             CODE:
591 27           struct feer_req *r = c->req;
592 27 50         if (unlikely(!r))
593 0           croak("Cannot access request path: no active request");
594 27           RETVAL = SvREFCNT_inc_simple_NN(feersum_env_path(aTHX_ r));
595             OUTPUT:
596             RETVAL
597              
598             SV *
599             query (struct feer_conn *c)
600             PROTOTYPE: $
601             CODE:
602 14           struct feer_req *r = c->req;
603 14 50         if (unlikely(!r))
604 0           croak("Cannot access request query: no active request");
605 14           RETVAL = SvREFCNT_inc_simple_NN(feersum_env_query(aTHX_ r));
606             OUTPUT:
607             RETVAL
608              
609             SV *
610             remote_address (struct feer_conn *c)
611             PROTOTYPE: $
612             CODE:
613 14           RETVAL = SvREFCNT_inc_simple_NN(feersum_env_addr(aTHX_ c));
614             OUTPUT:
615             RETVAL
616              
617             SV *
618             remote_port (struct feer_conn *c)
619             PROTOTYPE: $
620             CODE:
621 3           RETVAL = SvREFCNT_inc_simple_NN(feersum_env_port(aTHX_ c));
622             OUTPUT:
623             RETVAL
624              
625             SV *
626             proxy_tlvs (struct feer_conn *c)
627             PROTOTYPE: $
628             CODE:
629             // Returns PROXY protocol v2 TLVs hashref (native interface only)
630             // Keys are TLV type numbers as strings, values are raw TLV data
631 3 100         RETVAL = c->proxy_tlvs ? SvREFCNT_inc(c->proxy_tlvs) : &PL_sv_undef;
632             OUTPUT:
633             RETVAL
634              
635             SV *
636             trailers (struct feer_conn *c)
637             PROTOTYPE: $
638             CODE:
639 0 0         RETVAL = c->trailers ? newRV_inc((SV*)c->trailers) : &PL_sv_undef;
640             OUTPUT:
641             RETVAL
642              
643             SV *
644             client_address (struct feer_conn *c)
645             PROTOTYPE: $
646             CODE:
647             {
648 8           SV *fwd = NULL;
649 8 100         if (c->cached_use_reverse_proxy && c->req)
    50          
650 5           fwd = extract_forwarded_addr(aTHX_ c->req);
651 8 100         RETVAL = fwd ? fwd : SvREFCNT_inc_simple_NN(feersum_env_addr(aTHX_ c));
652             }
653             OUTPUT:
654             RETVAL
655              
656             SV *
657             url_scheme (struct feer_conn *c)
658             PROTOTYPE: $
659             CODE:
660             {
661 11           RETVAL = feer_determine_url_scheme(aTHX_ c);
662 11 100         if (!RETVAL) RETVAL = newSVpvs("http");
663             }
664             OUTPUT:
665             RETVAL
666              
667             ssize_t
668             content_length (struct feer_conn *c)
669             PROTOTYPE: $
670             CODE:
671 18 100         RETVAL = c->expected_cl;
672             OUTPUT:
673             RETVAL
674              
675             SV *
676             input (struct feer_conn *c)
677             PROTOTYPE: $
678             CODE:
679 11 50         if (likely(c->expected_cl > 0)) {
680 11           RETVAL = new_feer_conn_handle(aTHX_ c, 0);
681             } else {
682 0           RETVAL = &PL_sv_undef;
683             }
684             OUTPUT:
685             RETVAL
686              
687             SV *
688             headers (struct feer_conn *c, int norm = 0)
689             PROTOTYPE: $;$
690             CODE:
691 7           struct feer_req *r = c->req;
692 7 50         if (unlikely(!r))
693 0           croak("Cannot access request headers: no active request");
694 7           RETVAL = newRV_noinc((SV*)feersum_env_headers(aTHX_ r, norm));
695             OUTPUT:
696             RETVAL
697              
698             SV *
699             header (struct feer_conn *c, SV *name)
700             PROTOTYPE: $$
701             CODE:
702 10           struct feer_req *r = c->req;
703 10 50         if (unlikely(!r))
704 0           croak("Cannot access request header: no active request");
705 10           RETVAL = feersum_env_header(aTHX_ r, name);
706             OUTPUT:
707             RETVAL
708              
709             int
710             fileno (struct feer_conn *c)
711             CODE:
712 10 100         RETVAL = c->fd;
713             OUTPUT:
714             RETVAL
715              
716             SV *
717             io (struct feer_conn *c)
718             CODE:
719 13           RETVAL = feersum_env_io(aTHX_ c);
720             OUTPUT:
721             RETVAL
722              
723             void
724             return_from_io (struct feer_conn *c, SV *io_sv)
725             PROTOTYPE: $$
726             PPCODE:
727             {
728 3           SSize_t cnt = feersum_return_from_io(aTHX_ c, io_sv, "return_from_io");
729 2 50         mXPUSHi(cnt);
730             }
731              
732             bool
733             is_keepalive (struct feer_conn *c)
734             CODE:
735 4 100         RETVAL = c->is_keepalive;
736             OUTPUT:
737             RETVAL
738              
739             SV*
740             response_guard (struct feer_conn *c, ...)
741             PROTOTYPE: $;$
742             CODE:
743 3 100         RETVAL = feersum_conn_guard(aTHX_ c, (items == 2) ? ST(1) : NULL);
744             OUTPUT:
745             RETVAL
746              
747             void
748             DESTROY (struct feer_conn *c)
749             PPCODE:
750             {
751             unsigned i;
752 656           int fd = c->fd;
753             trace("DESTROY connection fd=%d c=%p\n", fd, c);
754              
755 656           feer_conn_set_busy(c);
756              
757             if (FEERSUM_CONN_FREE_ENABLED()) {
758             FEERSUM_CONN_FREE(fd);
759             }
760              
761             // During global destruction, SV arena is being torn down and refcounts
762             // are unreliable. Only close the fd; all memory is reclaimed at exit.
763 656 100         if (unlikely(PL_phase == PERL_PHASE_DESTRUCT)) {
764 11           safe_close_conn(c, "close at destruction");
765 11           return;
766             }
767              
768             // Stop any active watchers/timers to prevent them from firing on a freed object.
769             // We don't decrement refcount here because DESTROY is already cleaning up.
770 645 50         if (ev_is_active(&c->read_ev_io)) {
771 0           ev_io_stop(feersum_ev_loop, &c->read_ev_io);
772             }
773 645 50         if (ev_is_active(&c->write_ev_io)) {
774 0           ev_io_stop(feersum_ev_loop, &c->write_ev_io);
775             }
776 645 50         if (ev_is_active(&c->read_ev_timer)) {
777 0           ev_timer_stop(feersum_ev_loop, &c->read_ev_timer);
778             }
779 645 50         if (ev_is_active(&c->header_ev_timer)) {
780 0           ev_timer_stop(feersum_ev_loop, &c->header_ev_timer);
781             }
782 645 50         if (ev_is_active(&c->write_ev_timer)) {
783 0           ev_timer_stop(feersum_ev_loop, &c->write_ev_timer);
784             }
785              
786             /* SvREFCNT_dec is NULL-safe — no need to guard each field. */
787 645           SvREFCNT_dec(c->rbuf);
788 645           SvREFCNT_dec((SV*)c->trailers);
789 645           SvREFCNT_dec(c->proxy_tlvs);
790              
791 645 50         if (c->wbuf_rinq) {
792             struct iomatrix *m;
793 0 0         while ((m = (struct iomatrix *)rinq_shift(&c->wbuf_rinq)) != NULL) {
794 0 0         for (i=0; i < m->count; i++)
795 0           SvREFCNT_dec(m->sv[i]);
796 0 0         IOMATRIX_FREE(m);
797             }
798             }
799              
800 645           free_request(c);
801             #ifdef FEERSUM_HAS_H2
802             if (c->h2_session)
803             feer_h2_free_session(c);
804             #endif
805             #ifdef FEERSUM_HAS_TLS
806             #ifdef FEERSUM_HAS_H2
807             if (!c->is_h2_stream)
808             #endif
809 645           feer_tls_free_conn(c);
810             #endif
811 645           SvREFCNT_dec(c->remote_addr);
812 645           SvREFCNT_dec(c->remote_port);
813              
814 645           safe_close_conn(c, "close at destruction");
815              
816 645           SvREFCNT_dec(c->poll_write_cb);
817 645           SvREFCNT_dec(c->poll_read_cb);
818 645           SvREFCNT_dec(c->ext_guard);
819              
820             {
821 645           struct feer_server *server = c->server;
822 645           server->active_conns--;
823 645           SvREFCNT_dec(server->self); // release server ref held since new_feer_conn
824              
825             /* If a listener was capacity-paused, clear that bit now that a slot
826             * is free. Other pause reasons (user pause_accept, EMFILE backoff)
827             * are preserved. */
828 645 100         if (unlikely(server->max_connections > 0
    50          
    100          
    100          
829             && server->active_conns < server->max_connections
830             && !server->shutting_down)) {
831             int i;
832 1497 100         for (i = 0; i < server->n_listeners; i++) {
833 856           struct feer_listen *lsnr = &server->listeners[i];
834 856 100         if (lsnr->pause_flags & FEER_PAUSE_CAP) {
835 1           lsnr->pause_flags &= ~FEER_PAUSE_CAP;
836 1 50         if (!lsnr->pause_flags && lsnr->fd >= 0)
    50          
837 1           ev_io_start(feersum_ev_loop, &lsnr->accept_w);
838             }
839             }
840             }
841              
842 645 100         if (unlikely(server->shutting_down && server->active_conns <= 0)) {
    100          
843 1           ev_idle_stop(feersum_ev_loop, &server->ei);
844 1           ev_prepare_stop(feersum_ev_loop, &server->ep);
845 1           ev_check_stop(feersum_ev_loop, &server->ec);
846              
847             trace3("... was last conn, going to try shutdown\n");
848 1 50         if (server->shutdown_cb_cv)
849 1           invoke_shutdown_cb(aTHX_ server);
850             }
851             }
852             }
853