diff options
Diffstat (limited to 'ext')
-rw-r--r-- | ext/unicorn_http/epollexclusive.h | 56 | ||||
-rw-r--r-- | ext/unicorn_http/extconf.rb | 5 | ||||
-rw-r--r-- | ext/unicorn_http/httpdate.c | 20 | ||||
-rw-r--r-- | ext/unicorn_http/unicorn_http.rl | 21 | ||||
-rw-r--r-- | ext/unicorn_http/unicorn_http_common.rl | 2 |
5 files changed, 69 insertions, 35 deletions
diff --git a/ext/unicorn_http/epollexclusive.h b/ext/unicorn_http/epollexclusive.h index 677e1fe..c74a779 100644 --- a/ext/unicorn_http/epollexclusive.h +++ b/ext/unicorn_http/epollexclusive.h @@ -22,6 +22,18 @@ #endif #if USE_EPOLL +#if defined(HAVE_RB_IO_DESCRIPTOR) /* Ruby 3.1+ */ +# define my_fileno(io) rb_io_descriptor(io) +#else /* Ruby <3.1 */ +static int my_fileno(VALUE io) +{ + rb_io_t *fptr; + GetOpenFile(io, fptr); + rb_io_check_closed(fptr); + return fptr->fd; +} +#endif /* Ruby <3.1 */ + /* * :nodoc: * returns IO object if EPOLLEXCLUSIVE works and arms readers @@ -38,9 +50,8 @@ static VALUE prep_readers(VALUE cls, VALUE readers) Check_Type(readers, T_ARRAY); for (i = 0; i < RARRAY_LEN(readers); i++) { - int rc; + int rc, fd; struct epoll_event e; - rb_io_t *fptr; VALUE io = rb_ary_entry(readers, i); e.data.u64 = i; /* the reason readers shouldn't change */ @@ -53,9 +64,8 @@ static VALUE prep_readers(VALUE cls, VALUE readers) * cycles on maintaining level-triggering. */ e.events = EPOLLEXCLUSIVE | EPOLLIN; - io = rb_io_get_io(io); - GetOpenFile(io, fptr); - rc = epoll_ctl(epfd, EPOLL_CTL_ADD, fptr->fd, &e); + fd = my_fileno(rb_io_get_io(io)); + rc = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &e); if (rc < 0) rb_sys_fail("epoll_ctl"); } return epio; @@ -64,18 +74,22 @@ static VALUE prep_readers(VALUE cls, VALUE readers) #if USE_EPOLL struct ep_wait { - struct epoll_event *events; - rb_io_t *fptr; - int maxevents; + struct epoll_event event; + int epfd; int timeout_msec; }; static void *do_wait(void *ptr) /* runs w/o GVL */ { struct ep_wait *epw = ptr; - - return (void *)(long)epoll_wait(epw->fptr->fd, epw->events, - epw->maxevents, epw->timeout_msec); + /* + * Linux delivers epoll events in the order received, and using + * maxevents=1 ensures we pluck one item off ep->rdllist + * at-a-time (c.f. fs/eventpoll.c in linux.git, it's quite + * easy-to-understand for anybody familiar with Ruby C). + */ + return (void *)(long)epoll_wait(epw->epfd, &epw->event, 1, + epw->timeout_msec); } /* :nodoc: */ @@ -84,32 +98,22 @@ static VALUE get_readers(VALUE epio, VALUE ready, VALUE readers, VALUE timeout_msec) { struct ep_wait epw; - long i, n; - VALUE buf; + long n; Check_Type(ready, T_ARRAY); Check_Type(readers, T_ARRAY); - epw.maxevents = RARRAY_LENINT(readers); - buf = rb_str_buf_new(sizeof(struct epoll_event) * epw.maxevents); - epw.events = (struct epoll_event *)RSTRING_PTR(buf); - epio = rb_io_get_io(epio); - GetOpenFile(epio, epw.fptr); + epw.epfd = my_fileno(epio); epw.timeout_msec = NUM2INT(timeout_msec); n = (long)rb_thread_call_without_gvl(do_wait, &epw, RUBY_UBF_IO, NULL); if (n < 0) { if (errno != EINTR) rb_sys_fail("epoll_wait"); - n = 0; - } - /* Linux delivers events in order received */ - for (i = 0; i < n; i++) { - struct epoll_event *ev = &epw.events[i]; - VALUE obj = rb_ary_entry(readers, ev->data.u64); + } else if (n > 0) { /* maxevents is hardcoded to 1 */ + VALUE obj = rb_ary_entry(readers, epw.event.data.u64); if (RTEST(obj)) rb_ary_push(ready, obj); - } - rb_str_resize(buf, 0); + } /* n == 0 : timeout */ return Qfalse; } #endif /* USE_EPOLL */ diff --git a/ext/unicorn_http/extconf.rb b/ext/unicorn_http/extconf.rb index 80d00e5..de896fe 100644 --- a/ext/unicorn_http/extconf.rb +++ b/ext/unicorn_http/extconf.rb @@ -1,4 +1,5 @@ # -*- encoding: binary -*- +# frozen_string_literal: false require 'mkmf' have_func("rb_hash_clear", "ruby.h") or abort 'Ruby 2.0+ required' @@ -33,5 +34,7 @@ else message("no, needs Ruby 2.6+\n") end -have_func('epoll_create1', %w(sys/epoll.h)) +if have_func('epoll_create1', %w(sys/epoll.h)) + have_func('rb_io_descriptor') # Ruby 3.1+ +end create_makefile("unicorn_http") diff --git a/ext/unicorn_http/httpdate.c b/ext/unicorn_http/httpdate.c index 3f512dd..0faf5da 100644 --- a/ext/unicorn_http/httpdate.c +++ b/ext/unicorn_http/httpdate.c @@ -1,5 +1,6 @@ #include <ruby.h> #include <time.h> +#include <sys/time.h> #include <stdio.h> static const size_t buf_capa = sizeof("Thu, 01 Jan 1970 00:00:00 GMT"); @@ -43,13 +44,24 @@ static struct tm * my_gmtime_r(time_t *now, struct tm *tm) static VALUE httpdate(VALUE self) { static time_t last; - time_t now = time(NULL); /* not a syscall on modern 64-bit systems */ + struct timeval now; struct tm tm; - if (last == now) + /* + * Favor gettimeofday(2) over time(2), as the latter can return the + * wrong value in the first 1 .. 2.5 ms of every second(!) + * + * https://lore.kernel.org/git/20230320230507.3932018-1-gitster@pobox.com/ + * https://inbox.sourceware.org/libc-alpha/20230306160321.2942372-1-adhemerval.zanella@linaro.org/T/ + * https://sourceware.org/bugzilla/show_bug.cgi?id=30200 + */ + if (gettimeofday(&now, NULL)) + rb_sys_fail("gettimeofday"); + + if (last == now.tv_sec) return buf; - last = now; - gmtime_r(&now, &tm); + last = now.tv_sec; + gmtime_r(&now.tv_sec, &tm); /* we can make this thread-safe later if our Ruby loses the GVL */ snprintf(buf_ptr, buf_capa, diff --git a/ext/unicorn_http/unicorn_http.rl b/ext/unicorn_http/unicorn_http.rl index ba23438..fb5dcde 100644 --- a/ext/unicorn_http/unicorn_http.rl +++ b/ext/unicorn_http/unicorn_http.rl @@ -28,10 +28,15 @@ void init_unicorn_httpdate(void); #define UH_FL_TO_CLEAR 0x200 #define UH_FL_RESSTART 0x400 /* for check_client_connection */ #define UH_FL_HIJACK 0x800 +#define UH_FL_RES_CHUNK_VER (1U << 12) +#define UH_FL_RES_CHUNK_METHOD (1U << 13) /* all of these flags need to be set for keepalive to be supported */ #define UH_FL_KEEPALIVE (UH_FL_KAVERSION | UH_FL_REQEOF | UH_FL_HASHEADER) +/* we can only chunk responses for non-HEAD HTTP/1.1 requests */ +#define UH_FL_RES_CHUNKABLE (UH_FL_RES_CHUNK_VER | UH_FL_RES_CHUNK_METHOD) + static unsigned int MAX_HEADER_LEN = 1024 * (80 + 32); /* same as Mongrel */ /* this is only intended for use with Rainbows! */ @@ -145,6 +150,9 @@ request_method(struct http_parser *hp, const char *ptr, size_t len) { VALUE v = rb_str_new(ptr, len); + if (len != 4 || memcmp(ptr, "HEAD", 4)) + HP_FL_SET(hp, RES_CHUNK_METHOD); + rb_hash_aset(hp->env, g_request_method, v); } @@ -158,6 +166,7 @@ http_version(struct http_parser *hp, const char *ptr, size_t len) if (CONST_MEM_EQ("HTTP/1.1", ptr, len)) { /* HTTP/1.1 implies keepalive unless "Connection: close" is set */ HP_FL_SET(hp, KAVERSION); + HP_FL_SET(hp, RES_CHUNK_VER); v = g_http_11; } else if (CONST_MEM_EQ("HTTP/1.0", ptr, len)) { v = g_http_10; @@ -801,6 +810,14 @@ static VALUE HttpParser_keepalive(VALUE self) return HP_FL_ALL(hp, KEEPALIVE) ? Qtrue : Qfalse; } +/* :nodoc: */ +static VALUE chunkable_response_p(VALUE self) +{ + const struct http_parser *hp = data_get(self); + + return HP_FL_ALL(hp, RES_CHUNKABLE) ? Qtrue : Qfalse; +} + /** * call-seq: * parser.next? => true or false @@ -981,6 +998,7 @@ void Init_unicorn_http(void) rb_define_method(cHttpParser, "content_length", HttpParser_content_length, 0); rb_define_method(cHttpParser, "body_eof?", HttpParser_body_eof, 0); rb_define_method(cHttpParser, "keepalive?", HttpParser_keepalive, 0); + rb_define_method(cHttpParser, "chunkable_response?", chunkable_response_p, 0); rb_define_method(cHttpParser, "headers?", HttpParser_has_headers, 0); rb_define_method(cHttpParser, "next?", HttpParser_next, 0); rb_define_method(cHttpParser, "buf", HttpParser_buf, 0); @@ -1015,9 +1033,6 @@ void Init_unicorn_http(void) id_set_backtrace = rb_intern("set_backtrace"); init_unicorn_httpdate(); -#ifndef HAVE_RB_HASH_CLEAR - id_clear = rb_intern("clear"); -#endif id_is_chunked_p = rb_intern("is_chunked?"); init_epollexclusive(mUnicorn); diff --git a/ext/unicorn_http/unicorn_http_common.rl b/ext/unicorn_http/unicorn_http_common.rl index 0988b54..507e570 100644 --- a/ext/unicorn_http/unicorn_http_common.rl +++ b/ext/unicorn_http/unicorn_http_common.rl @@ -4,7 +4,7 @@ #### HTTP PROTOCOL GRAMMAR # line endings - CRLF = ("\r\n" | "\n"); + CRLF = ("\r\n" | "\n"); # character types CTL = (cntrl | 127); |