unicorn-rupcio 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. checksums.yaml +7 -0
  2. data/.CHANGELOG.old +25 -0
  3. data/.document +28 -0
  4. data/.gitattributes +5 -0
  5. data/.gitignore +25 -0
  6. data/.mailmap +26 -0
  7. data/.manifest +144 -0
  8. data/.olddoc.yml +25 -0
  9. data/Application_Timeouts +77 -0
  10. data/CONTRIBUTORS +39 -0
  11. data/COPYING +674 -0
  12. data/DESIGN +99 -0
  13. data/Documentation/.gitignore +3 -0
  14. data/Documentation/unicorn.1 +222 -0
  15. data/Documentation/unicorn_rails.1 +207 -0
  16. data/FAQ +70 -0
  17. data/GIT-VERSION-FILE +1 -0
  18. data/GIT-VERSION-GEN +39 -0
  19. data/GNUmakefile +318 -0
  20. data/HACKING +117 -0
  21. data/ISSUES +102 -0
  22. data/KNOWN_ISSUES +79 -0
  23. data/LICENSE +67 -0
  24. data/Links +58 -0
  25. data/PHILOSOPHY +139 -0
  26. data/README +165 -0
  27. data/Rakefile +17 -0
  28. data/SIGNALS +123 -0
  29. data/Sandbox +104 -0
  30. data/TODO +1 -0
  31. data/TUNING +119 -0
  32. data/archive/.gitignore +3 -0
  33. data/archive/slrnpull.conf +4 -0
  34. data/bin/unicorn +129 -0
  35. data/bin/unicorn_rails +210 -0
  36. data/examples/big_app_gc.rb +3 -0
  37. data/examples/echo.ru +27 -0
  38. data/examples/init.sh +102 -0
  39. data/examples/logger_mp_safe.rb +26 -0
  40. data/examples/logrotate.conf +44 -0
  41. data/examples/nginx.conf +156 -0
  42. data/examples/unicorn.conf.minimal.rb +14 -0
  43. data/examples/unicorn.conf.rb +111 -0
  44. data/examples/unicorn.socket +11 -0
  45. data/examples/unicorn@.service +40 -0
  46. data/ext/unicorn_http/CFLAGS +13 -0
  47. data/ext/unicorn_http/c_util.h +115 -0
  48. data/ext/unicorn_http/common_field_optimization.h +128 -0
  49. data/ext/unicorn_http/epollexclusive.h +128 -0
  50. data/ext/unicorn_http/ext_help.h +38 -0
  51. data/ext/unicorn_http/extconf.rb +40 -0
  52. data/ext/unicorn_http/global_variables.h +97 -0
  53. data/ext/unicorn_http/httpdate.c +91 -0
  54. data/ext/unicorn_http/unicorn_http.c +4348 -0
  55. data/ext/unicorn_http/unicorn_http.rl +1054 -0
  56. data/ext/unicorn_http/unicorn_http_common.rl +76 -0
  57. data/lib/unicorn/app/old_rails/static.rb +60 -0
  58. data/lib/unicorn/app/old_rails.rb +36 -0
  59. data/lib/unicorn/cgi_wrapper.rb +148 -0
  60. data/lib/unicorn/configurator.rb +749 -0
  61. data/lib/unicorn/const.rb +22 -0
  62. data/lib/unicorn/http_request.rb +180 -0
  63. data/lib/unicorn/http_response.rb +95 -0
  64. data/lib/unicorn/http_server.rb +860 -0
  65. data/lib/unicorn/launcher.rb +63 -0
  66. data/lib/unicorn/oob_gc.rb +82 -0
  67. data/lib/unicorn/preread_input.rb +34 -0
  68. data/lib/unicorn/select_waiter.rb +7 -0
  69. data/lib/unicorn/socket_helper.rb +186 -0
  70. data/lib/unicorn/stream_input.rb +152 -0
  71. data/lib/unicorn/tee_input.rb +132 -0
  72. data/lib/unicorn/tmpio.rb +34 -0
  73. data/lib/unicorn/util.rb +91 -0
  74. data/lib/unicorn/version.rb +1 -0
  75. data/lib/unicorn/worker.rb +166 -0
  76. data/lib/unicorn.rb +137 -0
  77. data/man/man1/unicorn.1 +222 -0
  78. data/man/man1/unicorn_rails.1 +207 -0
  79. data/setup.rb +1587 -0
  80. data/t/.gitignore +4 -0
  81. data/t/GNUmakefile +5 -0
  82. data/t/README +49 -0
  83. data/t/active-unix-socket.t +110 -0
  84. data/t/back-out-of-upgrade.t +44 -0
  85. data/t/bin/unused_listen +40 -0
  86. data/t/client_body_buffer_size.ru +15 -0
  87. data/t/client_body_buffer_size.t +79 -0
  88. data/t/detach.ru +12 -0
  89. data/t/env.ru +4 -0
  90. data/t/fails-rack-lint.ru +6 -0
  91. data/t/heartbeat-timeout.ru +13 -0
  92. data/t/heartbeat-timeout.t +60 -0
  93. data/t/integration.ru +129 -0
  94. data/t/integration.t +509 -0
  95. data/t/lib.perl +309 -0
  96. data/t/listener_names.ru +5 -0
  97. data/t/my-tap-lib.sh +201 -0
  98. data/t/oob_gc.ru +18 -0
  99. data/t/oob_gc_path.ru +18 -0
  100. data/t/pid.ru +4 -0
  101. data/t/preread_input.ru +23 -0
  102. data/t/reload-bad-config.t +49 -0
  103. data/t/reopen-logs.ru +14 -0
  104. data/t/reopen-logs.t +36 -0
  105. data/t/t0010-reap-logging.sh +55 -0
  106. data/t/t0012-reload-empty-config.sh +86 -0
  107. data/t/t0013-rewindable-input-false.sh +24 -0
  108. data/t/t0013.ru +13 -0
  109. data/t/t0014-rewindable-input-true.sh +24 -0
  110. data/t/t0014.ru +13 -0
  111. data/t/t0015-configurator-internals.sh +25 -0
  112. data/t/t0020-at_exit-handler.sh +49 -0
  113. data/t/t0021-process_detach.sh +29 -0
  114. data/t/t0022-listener_names-preload_app.sh +32 -0
  115. data/t/t0300-no-default-middleware.sh +20 -0
  116. data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
  117. data/t/t0301.ru +14 -0
  118. data/t/t9001-oob_gc.sh +47 -0
  119. data/t/t9002-oob_gc-path.sh +75 -0
  120. data/t/test-lib.sh +125 -0
  121. data/t/winch_ttin.t +64 -0
  122. data/t/working_directory.t +86 -0
  123. data/test/aggregate.rb +16 -0
  124. data/test/benchmark/README +60 -0
  125. data/test/benchmark/dd.ru +19 -0
  126. data/test/benchmark/ddstream.ru +51 -0
  127. data/test/benchmark/readinput.ru +41 -0
  128. data/test/benchmark/stack.ru +9 -0
  129. data/test/benchmark/uconnect.perl +66 -0
  130. data/test/exec/README +5 -0
  131. data/test/exec/test_exec.rb +1030 -0
  132. data/test/test_helper.rb +307 -0
  133. data/test/unit/test_configurator.rb +176 -0
  134. data/test/unit/test_droplet.rb +29 -0
  135. data/test/unit/test_http_parser.rb +885 -0
  136. data/test/unit/test_http_parser_ng.rb +715 -0
  137. data/test/unit/test_server.rb +245 -0
  138. data/test/unit/test_signals.rb +189 -0
  139. data/test/unit/test_socket_helper.rb +160 -0
  140. data/test/unit/test_stream_input.rb +211 -0
  141. data/test/unit/test_tee_input.rb +304 -0
  142. data/test/unit/test_util.rb +132 -0
  143. data/test/unit/test_waiter.rb +35 -0
  144. data/unicorn.gemspec +49 -0
  145. metadata +266 -0
@@ -0,0 +1,128 @@
1
+ /*
2
+ * This is only intended for use inside a unicorn worker, nowhere else.
3
+ * EPOLLEXCLUSIVE somewhat mitigates the thundering herd problem for
4
+ * mostly idle processes since we can't use blocking accept4.
5
+ * This is NOT intended for use with multi-threaded servers, nor
6
+ * single-threaded multi-client ("C10K") servers or anything advanced
7
+ * like that. This use of epoll is only appropriate for a primitive,
8
+ * single-client, single-threaded servers like unicorn that need to
9
+ * support SIGKILL timeouts and parent death detection.
10
+ */
11
+ #if defined(HAVE_EPOLL_CREATE1)
12
+ # include <sys/epoll.h>
13
+ # include <errno.h>
14
+ # include <ruby/io.h>
15
+ # include <ruby/thread.h>
16
+ #endif /* __linux__ */
17
+
18
+ #if defined(EPOLLEXCLUSIVE) && defined(HAVE_EPOLL_CREATE1)
19
+ # define USE_EPOLL (1)
20
+ #else
21
+ # define USE_EPOLL (0)
22
+ #endif
23
+
24
+ #if USE_EPOLL
25
+ #if defined(HAVE_RB_IO_DESCRIPTOR) /* Ruby 3.1+ */
26
+ # define my_fileno(io) rb_io_descriptor(io)
27
+ #else /* Ruby <3.1 */
28
+ static int my_fileno(VALUE io)
29
+ {
30
+ rb_io_t *fptr;
31
+ GetOpenFile(io, fptr);
32
+ rb_io_check_closed(fptr);
33
+ return fptr->fd;
34
+ }
35
+ #endif /* Ruby <3.1 */
36
+
37
+ /*
38
+ * :nodoc:
39
+ * returns IO object if EPOLLEXCLUSIVE works and arms readers
40
+ */
41
+ static VALUE prep_readers(VALUE cls, VALUE readers)
42
+ {
43
+ long i;
44
+ int epfd = epoll_create1(EPOLL_CLOEXEC);
45
+ VALUE epio;
46
+
47
+ if (epfd < 0) rb_sys_fail("epoll_create1");
48
+
49
+ epio = rb_funcall(cls, rb_intern("for_fd"), 1, INT2NUM(epfd));
50
+
51
+ Check_Type(readers, T_ARRAY);
52
+ for (i = 0; i < RARRAY_LEN(readers); i++) {
53
+ int rc, fd;
54
+ struct epoll_event e;
55
+ VALUE io = rb_ary_entry(readers, i);
56
+
57
+ e.data.u64 = i; /* the reason readers shouldn't change */
58
+
59
+ /*
60
+ * I wanted to use EPOLLET here, but maintaining our own
61
+ * equivalent of ep->rdllist in Ruby-space doesn't fit
62
+ * our design at all (and the kernel already has it's own
63
+ * code path for doing it). So let the kernel spend
64
+ * cycles on maintaining level-triggering.
65
+ */
66
+ e.events = EPOLLEXCLUSIVE | EPOLLIN;
67
+ fd = my_fileno(rb_io_get_io(io));
68
+ rc = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &e);
69
+ if (rc < 0) rb_sys_fail("epoll_ctl");
70
+ }
71
+ return epio;
72
+ }
73
+ #endif /* USE_EPOLL */
74
+
75
+ #if USE_EPOLL
76
+ struct ep_wait {
77
+ struct epoll_event event;
78
+ int epfd;
79
+ int timeout_msec;
80
+ };
81
+
82
+ static void *do_wait(void *ptr) /* runs w/o GVL */
83
+ {
84
+ struct ep_wait *epw = ptr;
85
+ /*
86
+ * Linux delivers epoll events in the order received, and using
87
+ * maxevents=1 ensures we pluck one item off ep->rdllist
88
+ * at-a-time (c.f. fs/eventpoll.c in linux.git, it's quite
89
+ * easy-to-understand for anybody familiar with Ruby C).
90
+ */
91
+ return (void *)(long)epoll_wait(epw->epfd, &epw->event, 1,
92
+ epw->timeout_msec);
93
+ }
94
+
95
+ /* :nodoc: */
96
+ /* readers must not change between prepare_readers and get_readers */
97
+ static VALUE
98
+ get_readers(VALUE epio, VALUE ready, VALUE readers, VALUE timeout_msec)
99
+ {
100
+ struct ep_wait epw;
101
+ long n;
102
+
103
+ Check_Type(ready, T_ARRAY);
104
+ Check_Type(readers, T_ARRAY);
105
+
106
+ epw.epfd = my_fileno(epio);
107
+ epw.timeout_msec = NUM2INT(timeout_msec);
108
+ n = (long)rb_thread_call_without_gvl(do_wait, &epw, RUBY_UBF_IO, NULL);
109
+ if (n < 0) {
110
+ if (errno != EINTR) rb_sys_fail("epoll_wait");
111
+ } else if (n > 0) { /* maxevents is hardcoded to 1 */
112
+ VALUE obj = rb_ary_entry(readers, epw.event.data.u64);
113
+
114
+ if (RTEST(obj))
115
+ rb_ary_push(ready, obj);
116
+ } /* n == 0 : timeout */
117
+ return Qfalse;
118
+ }
119
+ #endif /* USE_EPOLL */
120
+
121
+ static void init_epollexclusive(VALUE mUnicorn)
122
+ {
123
+ #if USE_EPOLL
124
+ VALUE cWaiter = rb_define_class_under(mUnicorn, "Waiter", rb_cIO);
125
+ rb_define_singleton_method(cWaiter, "prep_readers", prep_readers, 1);
126
+ rb_define_method(cWaiter, "get_readers", get_readers, 3);
127
+ #endif
128
+ }
@@ -0,0 +1,38 @@
1
+ #ifndef ext_help_h
2
+ #define ext_help_h
3
+
4
+ /* not all Ruby implementations support frozen objects (Rubinius does not) */
5
+ #if defined(OBJ_FROZEN)
6
+ # define assert_frozen(f) assert(OBJ_FROZEN(f) && "unfrozen object")
7
+ #else
8
+ # define assert_frozen(f) do {} while (0)
9
+ #endif /* !defined(OBJ_FROZEN) */
10
+
11
+ static inline int str_cstr_eq(VALUE val, const char *ptr, long len)
12
+ {
13
+ return (RSTRING_LEN(val) == len && !memcmp(ptr, RSTRING_PTR(val), len));
14
+ }
15
+
16
+ #define STR_CSTR_EQ(val, const_str) \
17
+ str_cstr_eq(val, const_str, sizeof(const_str) - 1)
18
+
19
+ /* strcasecmp isn't locale independent */
20
+ static int str_cstr_case_eq(VALUE val, const char *ptr, long len)
21
+ {
22
+ if (RSTRING_LEN(val) == len) {
23
+ const char *v = RSTRING_PTR(val);
24
+
25
+ for (; len--; ++ptr, ++v) {
26
+ if ((*ptr == *v) || (*v >= 'A' && *v <= 'Z' && (*v | 0x20) == *ptr))
27
+ continue;
28
+ return 0;
29
+ }
30
+ return 1;
31
+ }
32
+ return 0;
33
+ }
34
+
35
+ #define STR_CSTR_CASE_EQ(val, const_str) \
36
+ str_cstr_case_eq(val, const_str, sizeof(const_str) - 1)
37
+
38
+ #endif /* ext_help_h */
@@ -0,0 +1,40 @@
1
+ # -*- encoding: binary -*-
2
+ # frozen_string_literal: false
3
+ require 'mkmf'
4
+
5
+ have_func("rb_hash_clear", "ruby.h") or abort 'Ruby 2.0+ required'
6
+
7
+ message('checking if String#-@ (str_uminus) dedupes... ')
8
+ begin
9
+ a = -(%w(t e s t).join)
10
+ b = -(%w(t e s t).join)
11
+ if a.equal?(b)
12
+ $CPPFLAGS += ' -DSTR_UMINUS_DEDUPE=1 '
13
+ message("yes\n")
14
+ else
15
+ $CPPFLAGS += ' -DSTR_UMINUS_DEDUPE=0 '
16
+ message("no, needs Ruby 2.5+\n")
17
+ end
18
+ rescue NoMethodError
19
+ $CPPFLAGS += ' -DSTR_UMINUS_DEDUPE=0 '
20
+ message("no, String#-@ not available\n")
21
+ end
22
+
23
+ message('checking if Hash#[]= (rb_hash_aset) dedupes... ')
24
+ h = {}
25
+ x = {}
26
+ r = rand.to_s
27
+ h[%W(#{r}).join('')] = :foo
28
+ x[%W(#{r}).join('')] = :foo
29
+ if x.keys[0].equal?(h.keys[0])
30
+ $CPPFLAGS += ' -DHASH_ASET_DEDUPE=1 '
31
+ message("yes\n")
32
+ else
33
+ $CPPFLAGS += ' -DHASH_ASET_DEDUPE=0 '
34
+ message("no, needs Ruby 2.6+\n")
35
+ end
36
+
37
+ if have_func('epoll_create1', %w(sys/epoll.h))
38
+ have_func('rb_io_descriptor') # Ruby 3.1+
39
+ end
40
+ create_makefile("unicorn_http")
@@ -0,0 +1,97 @@
1
+ #ifndef global_variables_h
2
+ #define global_variables_h
3
+ static VALUE eHttpParserError;
4
+ static VALUE e413;
5
+ static VALUE e414;
6
+
7
+ static VALUE g_rack_url_scheme;
8
+ static VALUE g_request_method;
9
+ static VALUE g_request_uri;
10
+ static VALUE g_fragment;
11
+ static VALUE g_query_string;
12
+ static VALUE g_http_version;
13
+ static VALUE g_request_path;
14
+ static VALUE g_path_info;
15
+ static VALUE g_server_name;
16
+ static VALUE g_server_port;
17
+ static VALUE g_server_protocol;
18
+ static VALUE g_http_host;
19
+ static VALUE g_http_x_forwarded_proto;
20
+ static VALUE g_http_x_forwarded_ssl;
21
+ static VALUE g_http_transfer_encoding;
22
+ static VALUE g_content_length;
23
+ static VALUE g_http_trailer;
24
+ static VALUE g_http_connection;
25
+ static VALUE g_port_80;
26
+ static VALUE g_port_443;
27
+ static VALUE g_localhost;
28
+ static VALUE g_http;
29
+ static VALUE g_https;
30
+ static VALUE g_http_09;
31
+ static VALUE g_http_10;
32
+ static VALUE g_http_11;
33
+
34
+ /** Defines common length and error messages for input length validation. */
35
+ #define DEF_MAX_LENGTH(N, length) \
36
+ static const size_t MAX_##N##_LENGTH = length; \
37
+ static const char * const MAX_##N##_LENGTH_ERR = \
38
+ "HTTP element " # N " is longer than the " # length " allowed length."
39
+
40
+ NORETURN(static void parser_raise(VALUE klass, const char *));
41
+
42
+ /**
43
+ * Validates the max length of given input and throws an HttpParserError
44
+ * exception if over.
45
+ */
46
+ #define VALIDATE_MAX_LENGTH(len, N) do { \
47
+ if (len > MAX_##N##_LENGTH) \
48
+ parser_raise(eHttpParserError, MAX_##N##_LENGTH_ERR); \
49
+ } while (0)
50
+
51
+ #define VALIDATE_MAX_URI_LENGTH(len, N) do { \
52
+ if (len > MAX_##N##_LENGTH) \
53
+ parser_raise(e414, MAX_##N##_LENGTH_ERR); \
54
+ } while (0)
55
+
56
+ /** Defines global strings in the init method. */
57
+ #define DEF_GLOBAL(N, val) do { \
58
+ g_##N = str_new_dd_freeze(val, (long)sizeof(val) - 1); \
59
+ rb_gc_register_mark_object(g_##N); \
60
+ } while (0)
61
+
62
+ /* Defines the maximum allowed lengths for various input elements.*/
63
+ DEF_MAX_LENGTH(FIELD_NAME, 256);
64
+ DEF_MAX_LENGTH(FIELD_VALUE, 80 * 1024);
65
+ DEF_MAX_LENGTH(REQUEST_URI, 1024 * 15);
66
+ DEF_MAX_LENGTH(FRAGMENT, 1024); /* Don't know if this length is specified somewhere or not */
67
+ DEF_MAX_LENGTH(REQUEST_PATH, 4096); /* common PATH_MAX on modern systems */
68
+ DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10));
69
+
70
+ static void init_globals(void)
71
+ {
72
+ DEF_GLOBAL(rack_url_scheme, "rack.url_scheme");
73
+ DEF_GLOBAL(request_method, "REQUEST_METHOD");
74
+ DEF_GLOBAL(request_uri, "REQUEST_URI");
75
+ DEF_GLOBAL(fragment, "FRAGMENT");
76
+ DEF_GLOBAL(query_string, "QUERY_STRING");
77
+ DEF_GLOBAL(http_version, "HTTP_VERSION");
78
+ DEF_GLOBAL(request_path, "REQUEST_PATH");
79
+ DEF_GLOBAL(path_info, "PATH_INFO");
80
+ DEF_GLOBAL(server_name, "SERVER_NAME");
81
+ DEF_GLOBAL(server_port, "SERVER_PORT");
82
+ DEF_GLOBAL(server_protocol, "SERVER_PROTOCOL");
83
+ DEF_GLOBAL(http_x_forwarded_proto, "HTTP_X_FORWARDED_PROTO");
84
+ DEF_GLOBAL(http_x_forwarded_ssl, "HTTP_X_FORWARDED_SSL");
85
+ DEF_GLOBAL(port_80, "80");
86
+ DEF_GLOBAL(port_443, "443");
87
+ DEF_GLOBAL(localhost, "localhost");
88
+ DEF_GLOBAL(http, "http");
89
+ DEF_GLOBAL(https, "https");
90
+ DEF_GLOBAL(http_11, "HTTP/1.1");
91
+ DEF_GLOBAL(http_10, "HTTP/1.0");
92
+ DEF_GLOBAL(http_09, "HTTP/0.9");
93
+ }
94
+
95
+ #undef DEF_GLOBAL
96
+
97
+ #endif /* global_variables_h */
@@ -0,0 +1,91 @@
1
+ #include <ruby.h>
2
+ #include <time.h>
3
+ #include <sys/time.h>
4
+ #include <stdio.h>
5
+
6
+ static const size_t buf_capa = sizeof("Thu, 01 Jan 1970 00:00:00 GMT");
7
+ static VALUE buf;
8
+ static char *buf_ptr;
9
+ static const char week[] = "Sun\0Mon\0Tue\0Wed\0Thu\0Fri\0Sat";
10
+ static const char months[] = "Jan\0Feb\0Mar\0Apr\0May\0Jun\0"
11
+ "Jul\0Aug\0Sep\0Oct\0Nov\0Dec";
12
+
13
+ /* for people on wonky systems only */
14
+ #ifndef HAVE_GMTIME_R
15
+ # warning using fake gmtime_r
16
+ static struct tm * my_gmtime_r(time_t *now, struct tm *tm)
17
+ {
18
+ struct tm *global = gmtime(now);
19
+ if (global)
20
+ *tm = *global;
21
+ return tm;
22
+ }
23
+ # define gmtime_r my_gmtime_r
24
+ #endif
25
+
26
+
27
+ /*
28
+ * Returns a string which represents the time as rfc1123-date of HTTP-date
29
+ * defined by RFC 2616:
30
+ *
31
+ * day-of-week, DD month-name CCYY hh:mm:ss GMT
32
+ *
33
+ * Note that the result is always GMT.
34
+ *
35
+ * This method is identical to Time#httpdate in the Ruby standard library,
36
+ * except it is implemented in C for performance. We always saw
37
+ * Time#httpdate at or near the top of the profiler output so we
38
+ * decided to rewrite this in C.
39
+ *
40
+ * Caveats: it relies on a Ruby implementation with the global VM lock,
41
+ * a thread-safe version will be provided when a Unix-only, GVL-free Ruby
42
+ * implementation becomes viable.
43
+ */
44
+ static VALUE httpdate(VALUE self)
45
+ {
46
+ static time_t last;
47
+ struct timeval now;
48
+ struct tm tm;
49
+
50
+ /*
51
+ * Favor gettimeofday(2) over time(2), as the latter can return the
52
+ * wrong value in the first 1 .. 2.5 ms of every second(!)
53
+ *
54
+ * https://lore.kernel.org/git/20230320230507.3932018-1-gitster@pobox.com/
55
+ * https://inbox.sourceware.org/libc-alpha/20230306160321.2942372-1-adhemerval.zanella@linaro.org/T/
56
+ * https://sourceware.org/bugzilla/show_bug.cgi?id=30200
57
+ */
58
+ if (gettimeofday(&now, NULL))
59
+ rb_sys_fail("gettimeofday");
60
+
61
+ if (last == now.tv_sec)
62
+ return buf;
63
+ last = now.tv_sec;
64
+ gmtime_r(&now.tv_sec, &tm);
65
+
66
+ /* we can make this thread-safe later if our Ruby loses the GVL */
67
+ snprintf(buf_ptr, buf_capa,
68
+ "%s, %02d %s %4d %02d:%02d:%02d GMT",
69
+ week + (tm.tm_wday * 4),
70
+ tm.tm_mday,
71
+ months + (tm.tm_mon * 4),
72
+ tm.tm_year + 1900,
73
+ tm.tm_hour,
74
+ tm.tm_min,
75
+ tm.tm_sec);
76
+
77
+ return buf;
78
+ }
79
+
80
+ void init_unicorn_httpdate(void)
81
+ {
82
+ VALUE mod = rb_define_module("Unicorn");
83
+ mod = rb_define_module_under(mod, "HttpResponse");
84
+
85
+ buf = rb_str_new(0, buf_capa - 1);
86
+ rb_gc_register_mark_object(buf);
87
+ buf_ptr = RSTRING_PTR(buf);
88
+ httpdate(Qnil);
89
+
90
+ rb_define_method(mod, "httpdate", httpdate, 0);
91
+ }