cool.io 1.4.1-x64-mingw32
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.
- checksums.yaml +7 -0
- data/.gitignore +29 -0
- data/.rspec +3 -0
- data/.travis.yml +13 -0
- data/CHANGES.md +229 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +166 -0
- data/Rakefile +79 -0
- data/cool.io.gemspec +29 -0
- data/examples/callbacked_echo_server.rb +24 -0
- data/examples/dslified_echo_client.rb +34 -0
- data/examples/dslified_echo_server.rb +24 -0
- data/examples/echo_client.rb +38 -0
- data/examples/echo_server.rb +27 -0
- data/examples/google.rb +9 -0
- data/ext/cool.io/.gitignore +5 -0
- data/ext/cool.io/cool.io.h +59 -0
- data/ext/cool.io/cool.io_ext.c +25 -0
- data/ext/cool.io/ev_wrap.h +10 -0
- data/ext/cool.io/extconf.rb +61 -0
- data/ext/cool.io/iowatcher.c +189 -0
- data/ext/cool.io/libev.c +8 -0
- data/ext/cool.io/loop.c +261 -0
- data/ext/cool.io/stat_watcher.c +269 -0
- data/ext/cool.io/timer_watcher.c +219 -0
- data/ext/cool.io/utils.c +122 -0
- data/ext/cool.io/watcher.c +264 -0
- data/ext/cool.io/watcher.h +71 -0
- data/ext/iobuffer/extconf.rb +9 -0
- data/ext/iobuffer/iobuffer.c +767 -0
- data/ext/libev/Changes +507 -0
- data/ext/libev/LICENSE +37 -0
- data/ext/libev/README +58 -0
- data/ext/libev/README.embed +3 -0
- data/ext/libev/ev.c +5054 -0
- data/ext/libev/ev.h +853 -0
- data/ext/libev/ev_epoll.c +282 -0
- data/ext/libev/ev_kqueue.c +214 -0
- data/ext/libev/ev_poll.c +148 -0
- data/ext/libev/ev_port.c +185 -0
- data/ext/libev/ev_select.c +362 -0
- data/ext/libev/ev_vars.h +204 -0
- data/ext/libev/ev_win32.c +163 -0
- data/ext/libev/ev_wrap.h +200 -0
- data/ext/libev/ruby_gil.patch +97 -0
- data/ext/libev/test_libev_win32.c +123 -0
- data/ext/libev/win_select.patch +115 -0
- data/lib/.gitignore +2 -0
- data/lib/cool.io.rb +34 -0
- data/lib/cool.io/async_watcher.rb +43 -0
- data/lib/cool.io/custom_require.rb +9 -0
- data/lib/cool.io/dns_resolver.rb +219 -0
- data/lib/cool.io/dsl.rb +139 -0
- data/lib/cool.io/io.rb +194 -0
- data/lib/cool.io/iowatcher.rb +17 -0
- data/lib/cool.io/listener.rb +99 -0
- data/lib/cool.io/loop.rb +122 -0
- data/lib/cool.io/meta.rb +49 -0
- data/lib/cool.io/server.rb +75 -0
- data/lib/cool.io/socket.rb +230 -0
- data/lib/cool.io/timer_watcher.rb +17 -0
- data/lib/cool.io/version.rb +7 -0
- data/lib/coolio.rb +2 -0
- data/spec/async_watcher_spec.rb +57 -0
- data/spec/dns_spec.rb +43 -0
- data/spec/iobuffer_spec.rb +147 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/stat_watcher_spec.rb +77 -0
- data/spec/tcp_server_spec.rb +225 -0
- data/spec/tcp_socket_spec.rb +185 -0
- data/spec/timer_watcher_spec.rb +59 -0
- data/spec/udp_socket_spec.rb +58 -0
- data/spec/unix_listener_spec.rb +25 -0
- data/spec/unix_server_spec.rb +27 -0
- metadata +182 -0
@@ -0,0 +1,97 @@
|
|
1
|
+
diff --git a/ext/libev/ev.c b/ext/libev/ev.c
|
2
|
+
index e5bd5ab..10f6ff2 100644
|
3
|
+
--- a/ext/libev/ev.c
|
4
|
+
+++ b/ext/libev/ev.c
|
5
|
+
@@ -37,6 +37,10 @@
|
6
|
+
* either the BSD or the GPL.
|
7
|
+
*/
|
8
|
+
|
9
|
+
+/* ########## COOLIO PATCHERY HO! ########## */
|
10
|
+
+#include "ruby.h"
|
11
|
+
+/* ######################################## */
|
12
|
+
+
|
13
|
+
/* this big block deduces configuration from config.h */
|
14
|
+
#ifndef EV_STANDALONE
|
15
|
+
# ifdef EV_CONFIG_H
|
16
|
+
@@ -3237,9 +3241,27 @@ time_update (EV_P_ ev_tstamp max_block)
|
17
|
+
}
|
18
|
+
}
|
19
|
+
|
20
|
+
+/* ########## COOLIO PATCHERY HO! ########## */
|
21
|
+
+#if defined(HAVE_RB_THREAD_BLOCKING_REGION)
|
22
|
+
+static
|
23
|
+
+VALUE ev_backend_poll(void **args)
|
24
|
+
+{
|
25
|
+
+ struct ev_loop *loop = (struct ev_loop *)args[0];
|
26
|
+
+ ev_tstamp waittime = *(ev_tstamp *)args[1];
|
27
|
+
+ backend_poll (EV_A_ waittime);
|
28
|
+
+}
|
29
|
+
+#endif
|
30
|
+
+/* ######################################## */
|
31
|
+
+
|
32
|
+
int
|
33
|
+
ev_run (EV_P_ int flags)
|
34
|
+
{
|
35
|
+
+/* ########## COOLIO PATCHERY HO! ########## */
|
36
|
+
+#if defined(HAVE_RB_THREAD_BLOCKING_REGION)
|
37
|
+
+ void *poll_args[2];
|
38
|
+
+#endif
|
39
|
+
+/* ######################################## */
|
40
|
+
+
|
41
|
+
#if EV_FEATURE_API
|
42
|
+
++loop_depth;
|
43
|
+
#endif
|
44
|
+
@@ -3357,7 +3379,53 @@ ev_run (EV_P_ int flags)
|
45
|
+
++loop_count;
|
46
|
+
#endif
|
47
|
+
assert ((loop_done = EVBREAK_RECURSE, 1)); /* assert for side effect */
|
48
|
+
+
|
49
|
+
+/*
|
50
|
+
+########################## COOLIO PATCHERY HO! ##########################
|
51
|
+
+
|
52
|
+
+The original patch file is made by Tony Arcieri.
|
53
|
+
+https://github.com/celluloid/nio4r/blob/680143345726c5a64bb22376ca8fc3c6857019ae/ext/libev/ruby_gil.patch.
|
54
|
+
+
|
55
|
+
+According to the grandwizards of Ruby, locking and unlocking of the global
|
56
|
+
+interpreter lock are apparently too powerful a concept for a mere mortal to
|
57
|
+
+wield (although redefining what + and - do to numbers is totally cool).
|
58
|
+
+And so it came to pass that the only acceptable way to release the global
|
59
|
+
+interpreter lock is through a convoluted callback system that thakes a
|
60
|
+
+function pointer. While the grandwizard of libev foresaw this sort of scenario,
|
61
|
+
+he too attempted to place an API with callbacks on it, one that runs before
|
62
|
+
+the system call, and one that runs immediately after.
|
63
|
+
+
|
64
|
+
+And so it came to pass that trying to wrap everything up in callbacks created
|
65
|
+
+two incompatible APIs, Ruby's which releases the global interpreter lock and
|
66
|
+
+reacquires it when the callback returns, and libev's, which wants two
|
67
|
+
+callbacks, one which runs before the polling operation starts, and one which
|
68
|
+
+runs after it finishes.
|
69
|
+
+
|
70
|
+
+These two systems are incompatible as they both want to use callbacks to
|
71
|
+
+solve the same problem, however libev wants to use before/after callbacks,
|
72
|
+
+and Ruby wants to use an "around" callback. This presents a significant
|
73
|
+
+problem as these two patterns of callbacks are diametrical opposites of each
|
74
|
+
+other and thus cannot be composed.
|
75
|
+
+
|
76
|
+
+And thus we are left with no choice but to patch the internals of libev in
|
77
|
+
+order to release a mutex at just the precise moment.
|
78
|
+
+
|
79
|
+
+Let this be a lesson to the all: CALLBACKS FUCKING BLOW
|
80
|
+
+
|
81
|
+
+#######################################################################
|
82
|
+
+*/
|
83
|
+
+
|
84
|
+
+#if defined(HAVE_RB_THREAD_BLOCKING_REGION)
|
85
|
+
+ poll_args[0] = (void *)loop;
|
86
|
+
+ poll_args[1] = (void *)&waittime;
|
87
|
+
+ rb_thread_blocking_region(ev_backend_poll, (void *)&poll_args, RUBY_UBF_IO, 0);
|
88
|
+
+#else
|
89
|
+
backend_poll (EV_A_ waittime);
|
90
|
+
+#endif
|
91
|
+
+/*
|
92
|
+
+############################# END PATCHERY ############################
|
93
|
+
+*/
|
94
|
+
+
|
95
|
+
assert ((loop_done = EVBREAK_CANCEL, 1)); /* assert for side effect */
|
96
|
+
|
97
|
+
pipe_write_wanted = 0; /* just an optimisation, no fence needed */
|
@@ -0,0 +1,123 @@
|
|
1
|
+
// a single header file is required
|
2
|
+
#include <ev.h>
|
3
|
+
#include <stdio.h>
|
4
|
+
#include <io.h>
|
5
|
+
|
6
|
+
// every watcher type has its own typedef'd struct
|
7
|
+
// with the name ev_TYPE
|
8
|
+
ev_io stdin_watcher;
|
9
|
+
ev_timer timeout_watcher;
|
10
|
+
|
11
|
+
// all watcher callbacks have a similar signature
|
12
|
+
// this callback is called when data is readable on stdin
|
13
|
+
static void
|
14
|
+
stdin_cb (EV_P_ ev_io *w, int revents)
|
15
|
+
{
|
16
|
+
puts ("stdin ready or done or something");
|
17
|
+
// for one-shot events, one must manually stop the watcher
|
18
|
+
// with its corresponding stop function.
|
19
|
+
//ev_io_stop (EV_A_ w);
|
20
|
+
|
21
|
+
// this causes all nested ev_loop's to stop iterating
|
22
|
+
//ev_unloop (EV_A_ EVUNLOOP_ALL);
|
23
|
+
}
|
24
|
+
|
25
|
+
// another callback, this time for a time-out
|
26
|
+
static void
|
27
|
+
timeout_cb (EV_P_ ev_timer *w, int revents)
|
28
|
+
{
|
29
|
+
puts ("timeout");
|
30
|
+
// this causes the innermost ev_loop to stop iterating
|
31
|
+
ev_unloop (EV_A_ EVUNLOOP_ONE);
|
32
|
+
}
|
33
|
+
|
34
|
+
|
35
|
+
|
36
|
+
#include <winsock.h>
|
37
|
+
|
38
|
+
#include <stdlib.h>
|
39
|
+
#include <iostream>
|
40
|
+
int get_server_fd()
|
41
|
+
{
|
42
|
+
|
43
|
+
//----------------------
|
44
|
+
// Initialize Winsock.
|
45
|
+
WSADATA wsaData;
|
46
|
+
int iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
|
47
|
+
if (iResult != NO_ERROR) {
|
48
|
+
printf("Error at WSAStartup()\n");
|
49
|
+
return 1;
|
50
|
+
}
|
51
|
+
|
52
|
+
//----------------------
|
53
|
+
// Create a SOCKET for listening for
|
54
|
+
// incoming connection requests.
|
55
|
+
SOCKET ListenSocket;
|
56
|
+
ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
57
|
+
if (ListenSocket == INVALID_SOCKET) {
|
58
|
+
printf("Error at socket(): %ld\n", WSAGetLastError());
|
59
|
+
WSACleanup();
|
60
|
+
return 1;
|
61
|
+
}
|
62
|
+
printf("socket returned %d\n", ListenSocket);
|
63
|
+
|
64
|
+
//----------------------
|
65
|
+
// The sockaddr_in structure specifies the address family,
|
66
|
+
// IP address, and port for the socket that is being bound.
|
67
|
+
sockaddr_in service;
|
68
|
+
service.sin_family = AF_INET;
|
69
|
+
service.sin_addr.s_addr = inet_addr("127.0.0.1");
|
70
|
+
service.sin_port = htons(4444);
|
71
|
+
|
72
|
+
if (bind( ListenSocket,
|
73
|
+
(SOCKADDR*) &service,
|
74
|
+
sizeof(service)) == SOCKET_ERROR) {
|
75
|
+
printf("bind() failed.\n");
|
76
|
+
closesocket(ListenSocket);
|
77
|
+
WSACleanup();
|
78
|
+
return 1;
|
79
|
+
}
|
80
|
+
|
81
|
+
//----------------------
|
82
|
+
// Listen for incoming connection requests.
|
83
|
+
// on the created socket
|
84
|
+
if (listen( ListenSocket, 1 ) == SOCKET_ERROR) {
|
85
|
+
printf("Error listening on socket.\n");
|
86
|
+
closesocket(ListenSocket);
|
87
|
+
WSACleanup();
|
88
|
+
return 1;
|
89
|
+
}
|
90
|
+
|
91
|
+
|
92
|
+
printf("sock and osf handle are %d %d, error is \n", ListenSocket, _get_osfhandle (ListenSocket)); // -1 is invalid file handle: http://msdn.microsoft.com/en-us/library/ks2530z6.aspx
|
93
|
+
printf("err was %d\n", WSAGetLastError());
|
94
|
+
//----------------------
|
95
|
+
return ListenSocket;
|
96
|
+
}
|
97
|
+
|
98
|
+
|
99
|
+
int
|
100
|
+
main (void)
|
101
|
+
{
|
102
|
+
struct ev_loop *loopy = ev_default_loop(0);
|
103
|
+
int fd = get_server_fd();
|
104
|
+
int fd_real = _open_osfhandle(fd, NULL);
|
105
|
+
int conv = _get_osfhandle(fd_real);
|
106
|
+
printf("got server fd %d, loop %d, fd_real %d, that converted %d\n", fd, loopy, fd_real, conv);
|
107
|
+
// accept(fd, NULL, NULL);
|
108
|
+
// initialise an io watcher, then start it
|
109
|
+
// this one will watch for stdin to become readable
|
110
|
+
ev_io_init (&stdin_watcher, stdin_cb, /*STDIN_FILENO*/ conv, EV_READ);
|
111
|
+
ev_io_start (loopy, &stdin_watcher);
|
112
|
+
|
113
|
+
// initialise a timer watcher, then start it
|
114
|
+
// simple non-repeating 5.5 second timeout
|
115
|
+
//ev_timer_init (&timeout_watcher, timeout_cb, 15.5, 0.);
|
116
|
+
//ev_timer_start (loopy, &timeout_watcher);
|
117
|
+
printf("starting loop\n");
|
118
|
+
// now wait for events to arrive
|
119
|
+
ev_loop (loopy, 0);
|
120
|
+
|
121
|
+
// unloop was called, so exit
|
122
|
+
return 0;
|
123
|
+
}
|
@@ -0,0 +1,115 @@
|
|
1
|
+
diff --git a/ext/libev/ev_select.c b/ext/libev/ev_select.c
|
2
|
+
index f38d6ca..3a32642 100644
|
3
|
+
--- a/ext/libev/ev_select.c
|
4
|
+
+++ b/ext/libev/ev_select.c
|
5
|
+
@@ -67,6 +67,53 @@
|
6
|
+
|
7
|
+
#include <string.h>
|
8
|
+
|
9
|
+
+#ifdef _WIN32
|
10
|
+
+/*
|
11
|
+
+########## COOLIO PATCHERY HO! ##########
|
12
|
+
+
|
13
|
+
+Ruby undefs FD_* utilities for own implementation.
|
14
|
+
+It converts fd argument into socket handle internally on Windows,
|
15
|
+
+so libev should not use Ruby's FD_* utilities.
|
16
|
+
+
|
17
|
+
+Following FD_* utilities come from MinGW.
|
18
|
+
+RubyInstaller is built by MinGW so this should work.
|
19
|
+
+*/
|
20
|
+
+int PASCAL __WSAFDIsSet(SOCKET,fd_set*);
|
21
|
+
+#define EV_WIN_FD_CLR(fd,set) do { u_int __i;\
|
22
|
+
+for (__i = 0; __i < ((fd_set *)(set))->fd_count ; __i++) {\
|
23
|
+
+ if (((fd_set *)(set))->fd_array[__i] == (fd)) {\
|
24
|
+
+ while (__i < ((fd_set *)(set))->fd_count-1) {\
|
25
|
+
+ ((fd_set*)(set))->fd_array[__i] = ((fd_set*)(set))->fd_array[__i+1];\
|
26
|
+
+ __i++;\
|
27
|
+
+ }\
|
28
|
+
+ ((fd_set*)(set))->fd_count--;\
|
29
|
+
+ break;\
|
30
|
+
+ }\
|
31
|
+
+}\
|
32
|
+
+} while (0)
|
33
|
+
+#define EV_WIN_FD_SET(fd, set) do { u_int __i;\
|
34
|
+
+for (__i = 0; __i < ((fd_set *)(set))->fd_count ; __i++) {\
|
35
|
+
+ if (((fd_set *)(set))->fd_array[__i] == (fd)) {\
|
36
|
+
+ break;\
|
37
|
+
+ }\
|
38
|
+
+}\
|
39
|
+
+if (__i == ((fd_set *)(set))->fd_count) {\
|
40
|
+
+ if (((fd_set *)(set))->fd_count < FD_SETSIZE) {\
|
41
|
+
+ ((fd_set *)(set))->fd_array[__i] = (fd);\
|
42
|
+
+ ((fd_set *)(set))->fd_count++;\
|
43
|
+
+ }\
|
44
|
+
+}\
|
45
|
+
+} while(0)
|
46
|
+
+#define EV_WIN_FD_ZERO(set) (((fd_set *)(set))->fd_count=0)
|
47
|
+
+#define EV_WIN_FD_ISSET(fd, set) __WSAFDIsSet((SOCKET)(fd), (fd_set *)(set))
|
48
|
+
+/* ######################################## */
|
49
|
+
+#else
|
50
|
+
+#define EV_WIN_FD_CLR FD_CLR
|
51
|
+
+#define EV_WIN_FD_SET FD_SET
|
52
|
+
+#define EV_WIN_FD_ZERO FD_ZERO
|
53
|
+
+#define EV_WIN_FD_ISSET FD_ISSET
|
54
|
+
+#endif
|
55
|
+
+
|
56
|
+
static void
|
57
|
+
select_modify (EV_P_ int fd, int oev, int nev)
|
58
|
+
{
|
59
|
+
@@ -91,17 +138,17 @@ select_modify (EV_P_ int fd, int oev, int nev)
|
60
|
+
if ((oev ^ nev) & EV_READ)
|
61
|
+
#endif
|
62
|
+
if (nev & EV_READ)
|
63
|
+
- FD_SET (handle, (fd_set *)vec_ri);
|
64
|
+
+ EV_WIN_FD_SET (handle, (fd_set *)vec_ri);
|
65
|
+
else
|
66
|
+
- FD_CLR (handle, (fd_set *)vec_ri);
|
67
|
+
+ EV_WIN_FD_CLR (handle, (fd_set *)vec_ri);
|
68
|
+
|
69
|
+
#if EV_SELECT_IS_WINSOCKET
|
70
|
+
if ((oev ^ nev) & EV_WRITE)
|
71
|
+
#endif
|
72
|
+
if (nev & EV_WRITE)
|
73
|
+
- FD_SET (handle, (fd_set *)vec_wi);
|
74
|
+
+ EV_WIN_FD_SET (handle, (fd_set *)vec_wi);
|
75
|
+
else
|
76
|
+
- FD_CLR (handle, (fd_set *)vec_wi);
|
77
|
+
+ EV_WIN_FD_CLR (handle, (fd_set *)vec_wi);
|
78
|
+
|
79
|
+
#else
|
80
|
+
|
81
|
+
@@ -136,6 +183,8 @@ select_modify (EV_P_ int fd, int oev, int nev)
|
82
|
+
}
|
83
|
+
}
|
84
|
+
|
85
|
+
+#undef socket
|
86
|
+
+
|
87
|
+
static void
|
88
|
+
select_poll (EV_P_ ev_tstamp timeout)
|
89
|
+
{
|
90
|
+
@@ -230,10 +279,10 @@ select_poll (EV_P_ ev_tstamp timeout)
|
91
|
+
int handle = fd;
|
92
|
+
#endif
|
93
|
+
|
94
|
+
- if (FD_ISSET (handle, (fd_set *)vec_ro)) events |= EV_READ;
|
95
|
+
- if (FD_ISSET (handle, (fd_set *)vec_wo)) events |= EV_WRITE;
|
96
|
+
+ if (EV_WIN_FD_ISSET (handle, (fd_set *)vec_ro)) events |= EV_READ;
|
97
|
+
+ if (EV_WIN_FD_ISSET (handle, (fd_set *)vec_wo)) events |= EV_WRITE;
|
98
|
+
#ifdef _WIN32
|
99
|
+
- if (FD_ISSET (handle, (fd_set *)vec_eo)) events |= EV_WRITE;
|
100
|
+
+ if (EV_WIN_FD_ISSET (handle, (fd_set *)vec_eo)) events |= EV_WRITE;
|
101
|
+
#endif
|
102
|
+
|
103
|
+
if (expect_true (events))
|
104
|
+
@@ -279,9 +328,9 @@ select_init (EV_P_ int flags)
|
105
|
+
backend_poll = select_poll;
|
106
|
+
|
107
|
+
#if EV_SELECT_USE_FD_SET
|
108
|
+
- vec_ri = ev_malloc (sizeof (fd_set)); FD_ZERO ((fd_set *)vec_ri);
|
109
|
+
+ vec_ri = ev_malloc (sizeof (fd_set)); EV_WIN_FD_ZERO ((fd_set *)vec_ri);
|
110
|
+
vec_ro = ev_malloc (sizeof (fd_set));
|
111
|
+
- vec_wi = ev_malloc (sizeof (fd_set)); FD_ZERO ((fd_set *)vec_wi);
|
112
|
+
+ vec_wi = ev_malloc (sizeof (fd_set)); EV_WIN_FD_ZERO ((fd_set *)vec_wi);
|
113
|
+
vec_wo = ev_malloc (sizeof (fd_set));
|
114
|
+
#ifdef _WIN32
|
115
|
+
vec_eo = ev_malloc (sizeof (fd_set));
|
data/lib/.gitignore
ADDED
data/lib/cool.io.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C)2011 Tony Arcieri
|
3
|
+
# You can redistribute this under the terms of the Ruby license
|
4
|
+
# See file LICENSE for details
|
5
|
+
#++
|
6
|
+
|
7
|
+
require "cool.io/version"
|
8
|
+
require "cool.io/custom_require"
|
9
|
+
cool_require "iobuffer_ext"
|
10
|
+
cool_require "cool.io_ext"
|
11
|
+
|
12
|
+
require "cool.io/loop"
|
13
|
+
require "cool.io/meta"
|
14
|
+
require "cool.io/io"
|
15
|
+
require "cool.io/iowatcher"
|
16
|
+
require "cool.io/timer_watcher"
|
17
|
+
require "cool.io/async_watcher"
|
18
|
+
require "cool.io/listener"
|
19
|
+
require "cool.io/dns_resolver"
|
20
|
+
require "cool.io/socket"
|
21
|
+
require "cool.io/server"
|
22
|
+
|
23
|
+
module Coolio
|
24
|
+
def self.inspect
|
25
|
+
"Cool.io"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
module Cool
|
30
|
+
# Allow Coolio module to be referenced as Cool.io
|
31
|
+
def self.io
|
32
|
+
Coolio
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C)2007-10 Tony Arcieri
|
3
|
+
# You can redistribute this under the terms of the Ruby license
|
4
|
+
# See file LICENSE for details
|
5
|
+
#++
|
6
|
+
|
7
|
+
module Coolio
|
8
|
+
# The AsyncWatcher lets you signal another thread to wake up. Its
|
9
|
+
# intended use is notifying another thread of events.
|
10
|
+
class AsyncWatcher < IOWatcher
|
11
|
+
def initialize
|
12
|
+
@reader, @writer = ::IO.pipe
|
13
|
+
super(@reader)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Signal the async watcher. This call is thread safe.
|
17
|
+
def signal
|
18
|
+
# Write a byte to the pipe. What we write is meaningless, it
|
19
|
+
# merely signals an event has occurred for each byte written.
|
20
|
+
@writer.write "\0"
|
21
|
+
end
|
22
|
+
|
23
|
+
# Called whenever a signal is received
|
24
|
+
def on_signal; end
|
25
|
+
event_callback :on_signal
|
26
|
+
|
27
|
+
#########
|
28
|
+
protected
|
29
|
+
#########
|
30
|
+
|
31
|
+
def on_readable
|
32
|
+
# Read a byte from the pipe. This clears readability, unless
|
33
|
+
# another signal is pending
|
34
|
+
begin
|
35
|
+
@reader.read_nonblock 1
|
36
|
+
rescue Errno::EAGAIN
|
37
|
+
# in case there are spurious wakeups from forked processs
|
38
|
+
return
|
39
|
+
end
|
40
|
+
on_signal
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,219 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C)2007-10 Tony Arcieri
|
3
|
+
# You can redistribute this under the terms of the Ruby license
|
4
|
+
# See file LICENSE for details
|
5
|
+
#
|
6
|
+
# Gimpy hacka asynchronous DNS resolver
|
7
|
+
#
|
8
|
+
# Word to the wise: I don't know what I'm doing here. This was cobbled together
|
9
|
+
# as best I could with extremely limited knowledge of the DNS format. There's
|
10
|
+
# obviously a ton of stuff it doesn't support (like IPv6 and TCP).
|
11
|
+
#
|
12
|
+
# If you do know what you're doing with DNS, feel free to improve this!
|
13
|
+
# A good starting point my be this EventMachine Net::DNS-based asynchronous
|
14
|
+
# resolver:
|
15
|
+
#
|
16
|
+
# http://gist.github.com/663299
|
17
|
+
#
|
18
|
+
#++
|
19
|
+
|
20
|
+
require 'resolv'
|
21
|
+
|
22
|
+
module Coolio
|
23
|
+
# A non-blocking DNS resolver. It provides interfaces for querying both
|
24
|
+
# /etc/hosts and nameserves listed in /etc/resolv.conf, or nameservers of
|
25
|
+
# your choosing.
|
26
|
+
#
|
27
|
+
# Presently the client only supports UDP requests against your nameservers
|
28
|
+
# and cannot resolve anything with records larger than 512-bytes. Also,
|
29
|
+
# IPv6 is not presently supported.
|
30
|
+
#
|
31
|
+
# DNSResolver objects are one-shot. Once they resolve a domain name they
|
32
|
+
# automatically detach themselves from the event loop and cannot be used
|
33
|
+
# again.
|
34
|
+
class DNSResolver < IOWatcher
|
35
|
+
#--
|
36
|
+
DNS_PORT = 53
|
37
|
+
DATAGRAM_SIZE = 512
|
38
|
+
TIMEOUT = 3 # Retry timeout for each datagram sent
|
39
|
+
RETRIES = 4 # Number of retries to attempt
|
40
|
+
# so currently total is 12s before it will err due to timeouts
|
41
|
+
# if it errs due to inability to reach the DNS server [Errno::EHOSTUNREACH], same
|
42
|
+
# Query /etc/hosts (or the specified hostfile) for the given host
|
43
|
+
def self.hosts(host, hostfile = Resolv::Hosts::DefaultFileName)
|
44
|
+
hosts = {}
|
45
|
+
File.open(hostfile) do |f|
|
46
|
+
f.each_line do |host_entry|
|
47
|
+
entries = host_entry.gsub(/#.*$/, '').gsub(/\s+/, ' ').split(' ')
|
48
|
+
addr = entries.shift
|
49
|
+
entries.each { |e| hosts[e] ||= addr }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
hosts[host]
|
54
|
+
end
|
55
|
+
|
56
|
+
# Create a new Coolio::Watcher descended object to resolve the
|
57
|
+
# given hostname. If you so desire you can also specify a
|
58
|
+
# list of nameservers to query. By default the resolver will
|
59
|
+
# use nameservers listed in /etc/resolv.conf
|
60
|
+
def initialize(hostname, *nameservers)
|
61
|
+
if nameservers.empty?
|
62
|
+
nameservers = Resolv::DNS::Config.default_config_hash[:nameserver]
|
63
|
+
raise RuntimeError, "no nameservers found" if nameservers.empty? # TODO just call resolve_failed, not raise [also handle Errno::ENOENT)]
|
64
|
+
end
|
65
|
+
|
66
|
+
@nameservers = nameservers
|
67
|
+
@question = request_question hostname
|
68
|
+
|
69
|
+
@socket = UDPSocket.new
|
70
|
+
@timer = Timeout.new(self)
|
71
|
+
|
72
|
+
super(@socket)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Attach the DNSResolver to the given event loop
|
76
|
+
def attach(evloop)
|
77
|
+
send_request
|
78
|
+
@timer.attach(evloop)
|
79
|
+
super
|
80
|
+
end
|
81
|
+
|
82
|
+
# Detach the DNSResolver from the given event loop
|
83
|
+
def detach
|
84
|
+
@timer.detach if @timer.attached?
|
85
|
+
super
|
86
|
+
end
|
87
|
+
|
88
|
+
# Called when the name has successfully resolved to an address
|
89
|
+
def on_success(address); end
|
90
|
+
event_callback :on_success
|
91
|
+
|
92
|
+
# Called when we receive a response indicating the name didn't resolve
|
93
|
+
def on_failure; end
|
94
|
+
event_callback :on_failure
|
95
|
+
|
96
|
+
# Called if we don't receive a response, defaults to calling on_failure
|
97
|
+
def on_timeout
|
98
|
+
on_failure
|
99
|
+
end
|
100
|
+
|
101
|
+
#########
|
102
|
+
protected
|
103
|
+
#########
|
104
|
+
|
105
|
+
# Send a request to the DNS server
|
106
|
+
def send_request
|
107
|
+
nameserver = @nameservers.shift
|
108
|
+
@nameservers << nameserver # rotate them
|
109
|
+
begin
|
110
|
+
@socket.send request_message, 0, @nameservers.first, DNS_PORT
|
111
|
+
rescue Errno::EHOSTUNREACH # TODO figure out why it has to be wrapper here, when the other wrapper should be wrapping this one!
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Called by the subclass when the DNS response is available
|
116
|
+
def on_readable
|
117
|
+
datagram = nil
|
118
|
+
begin
|
119
|
+
datagram = @socket.recvfrom_nonblock(DATAGRAM_SIZE).first
|
120
|
+
rescue Errno::ECONNREFUSED
|
121
|
+
end
|
122
|
+
|
123
|
+
address = response_address datagram rescue nil
|
124
|
+
address ? on_success(address) : on_failure
|
125
|
+
detach
|
126
|
+
end
|
127
|
+
|
128
|
+
def request_question(hostname)
|
129
|
+
raise ArgumentError, "hostname cannot be nil" if hostname.nil?
|
130
|
+
|
131
|
+
# Query name
|
132
|
+
message = hostname.split('.').map { |s| [s.size].pack('C') << s }.join + "\0"
|
133
|
+
|
134
|
+
# Host address query
|
135
|
+
qtype = 1
|
136
|
+
|
137
|
+
# Internet query
|
138
|
+
qclass = 1
|
139
|
+
|
140
|
+
message << [qtype, qclass].pack('nn')
|
141
|
+
end
|
142
|
+
|
143
|
+
def request_message
|
144
|
+
# Standard query header
|
145
|
+
message = [2, 1, 0].pack('nCC')
|
146
|
+
|
147
|
+
# One entry
|
148
|
+
qdcount = 1
|
149
|
+
|
150
|
+
# No answer, authority, or additional records
|
151
|
+
ancount = nscount = arcount = 0
|
152
|
+
|
153
|
+
message << [qdcount, ancount, nscount, arcount].pack('nnnn')
|
154
|
+
message << @question
|
155
|
+
end
|
156
|
+
|
157
|
+
def response_address(message)
|
158
|
+
# Confirm the ID field
|
159
|
+
id = message[0..1].unpack('n').first.to_i
|
160
|
+
return unless id == 2
|
161
|
+
|
162
|
+
# Check the QR value and confirm this message is a response
|
163
|
+
qr = message[2..2].unpack('B1').first.to_i
|
164
|
+
return unless qr == 1
|
165
|
+
|
166
|
+
# Check the RCODE (lower nibble) and ensure there wasn't an error
|
167
|
+
rcode = message[3..3].unpack('B8').first[4..7].to_i(2)
|
168
|
+
return unless rcode == 0
|
169
|
+
|
170
|
+
# Extract the question and answer counts
|
171
|
+
qdcount, ancount = message[4..7].unpack('nn').map { |n| n.to_i }
|
172
|
+
|
173
|
+
# We only asked one question
|
174
|
+
return unless qdcount == 1
|
175
|
+
message.slice!(0, 12)
|
176
|
+
|
177
|
+
# Make sure it's the same question
|
178
|
+
return unless message[0..(@question.size-1)] == @question
|
179
|
+
message.slice!(0, @question.size)
|
180
|
+
|
181
|
+
# Extract the RDLENGTH
|
182
|
+
while not message.empty?
|
183
|
+
type = message[2..3].unpack('n').first.to_i
|
184
|
+
rdlength = message[10..11].unpack('n').first.to_i
|
185
|
+
rdata = message[12..(12 + rdlength - 1)]
|
186
|
+
message.slice!(0, 12 + rdlength)
|
187
|
+
|
188
|
+
# Only IPv4 supported
|
189
|
+
next unless rdlength == 4
|
190
|
+
|
191
|
+
# If we got an Internet address back, return it
|
192
|
+
return rdata.unpack('CCCC').join('.') if type == 1
|
193
|
+
end
|
194
|
+
|
195
|
+
nil
|
196
|
+
end
|
197
|
+
|
198
|
+
class Timeout < TimerWatcher
|
199
|
+
def initialize(resolver)
|
200
|
+
@resolver = resolver
|
201
|
+
@attempts = 0
|
202
|
+
super(TIMEOUT, true)
|
203
|
+
end
|
204
|
+
|
205
|
+
def on_timer
|
206
|
+
@attempts += 1
|
207
|
+
if @attempts <= RETRIES
|
208
|
+
begin
|
209
|
+
return @resolver.__send__(:send_request)
|
210
|
+
rescue Errno::EHOSTUNREACH # if the DNS is toast try again after the timeout occurs again
|
211
|
+
return nil
|
212
|
+
end
|
213
|
+
end
|
214
|
+
@resolver.__send__(:on_timeout)
|
215
|
+
@resolver.detach
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|