cool.io 1.4.1-x64-mingw32
Sign up to get free protection for your applications and to get access to all the features.
- 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
|