cool.io 1.9.0 → 1.9.5
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 +4 -4
- data/.github/workflows/test.yaml +8 -19
- data/.github/workflows/valgrind.yaml +44 -0
- data/Rakefile +6 -19
- data/cool.io.gemspec +1 -3
- data/ext/cool.io/buffer.c +82 -64
- data/ext/cool.io/cool.io.h +9 -0
- data/ext/cool.io/extconf.rb +0 -1
- data/ext/cool.io/iowatcher.c +1 -1
- data/ext/cool.io/loop.c +49 -29
- data/ext/cool.io/stat_watcher.c +19 -6
- data/ext/cool.io/timer_watcher.c +5 -5
- data/ext/cool.io/watcher.c +38 -13
- data/ext/cool.io/watcher.h +14 -15
- data/ext/libev/ev.c +7 -1
- data/ext/libev/ev_select.c +10 -0
- data/gems.rb +4 -0
- data/lib/cool.io/dns_resolver.rb +53 -9
- data/lib/cool.io/version.rb +2 -2
- data/libev_win_select.diff +25 -9
- data/spec/async_watcher_spec.rb +3 -1
- data/spec/detach_race_condition_spec.rb +117 -0
- data/spec/dns_spec.rb +186 -0
- data/spec/iobuffer_spec.rb +31 -0
- data/spec/stat_watcher_spec.rb +31 -1
- data/spec/tcp_server_spec.rb +28 -4
- metadata +6 -6
data/ext/cool.io/loop.c
CHANGED
|
@@ -14,8 +14,7 @@ static VALUE mCoolio = Qnil;
|
|
|
14
14
|
static VALUE cCoolio_Loop = Qnil;
|
|
15
15
|
|
|
16
16
|
static VALUE Coolio_Loop_allocate(VALUE klass);
|
|
17
|
-
static void
|
|
18
|
-
static void Coolio_Loop_free(struct Coolio_Loop *loop);
|
|
17
|
+
static void Coolio_Loop_free(void *data);
|
|
19
18
|
|
|
20
19
|
static VALUE Coolio_Loop_ev_loop_new(VALUE self, VALUE flags);
|
|
21
20
|
static VALUE Coolio_Loop_run_once(int argc, VALUE *argv, VALUE self);
|
|
@@ -47,9 +46,26 @@ void Init_coolio_loop()
|
|
|
47
46
|
rb_define_method(cCoolio_Loop, "run_nonblock", Coolio_Loop_run_nonblock, 0);
|
|
48
47
|
}
|
|
49
48
|
|
|
49
|
+
static const rb_data_type_t Coolio_Loop_type = {
|
|
50
|
+
"Coolio::Loop",
|
|
51
|
+
{
|
|
52
|
+
NULL,
|
|
53
|
+
Coolio_Loop_free,
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
struct Coolio_Loop *Coolio_Loop_ptr(VALUE self)
|
|
58
|
+
{
|
|
59
|
+
struct Coolio_Loop *loop;
|
|
60
|
+
|
|
61
|
+
TypedData_Get_Struct(self, struct Coolio_Loop, &Coolio_Loop_type, loop);
|
|
62
|
+
return loop;
|
|
63
|
+
}
|
|
64
|
+
|
|
50
65
|
static VALUE Coolio_Loop_allocate(VALUE klass)
|
|
51
66
|
{
|
|
52
|
-
struct Coolio_Loop *loop
|
|
67
|
+
struct Coolio_Loop *loop;
|
|
68
|
+
VALUE obj = TypedData_Make_Struct(klass, struct Coolio_Loop, &Coolio_Loop_type, loop);
|
|
53
69
|
|
|
54
70
|
loop->ev_loop = 0;
|
|
55
71
|
ev_init(&loop->timer, Coolio_Loop_timeout_callback);
|
|
@@ -58,15 +74,13 @@ static VALUE Coolio_Loop_allocate(VALUE klass)
|
|
|
58
74
|
loop->eventbuf_size = DEFAULT_EVENTBUF_SIZE;
|
|
59
75
|
loop->eventbuf = (struct Coolio_Event *)xmalloc(sizeof(struct Coolio_Event) * DEFAULT_EVENTBUF_SIZE);
|
|
60
76
|
|
|
61
|
-
return
|
|
77
|
+
return obj;
|
|
62
78
|
}
|
|
63
79
|
|
|
64
|
-
static void
|
|
80
|
+
static void Coolio_Loop_free(void *data)
|
|
65
81
|
{
|
|
66
|
-
|
|
82
|
+
struct Coolio_Loop *loop = data;
|
|
67
83
|
|
|
68
|
-
static void Coolio_Loop_free(struct Coolio_Loop *loop)
|
|
69
|
-
{
|
|
70
84
|
if(!loop->ev_loop)
|
|
71
85
|
return;
|
|
72
86
|
|
|
@@ -80,7 +94,7 @@ static void Coolio_Loop_free(struct Coolio_Loop *loop)
|
|
|
80
94
|
static VALUE Coolio_Loop_ev_loop_new(VALUE self, VALUE flags)
|
|
81
95
|
{
|
|
82
96
|
struct Coolio_Loop *loop_data;
|
|
83
|
-
|
|
97
|
+
loop_data = Coolio_Loop_ptr(self);
|
|
84
98
|
|
|
85
99
|
if(loop_data->ev_loop)
|
|
86
100
|
rb_raise(rb_eRuntimeError, "loop already initialized");
|
|
@@ -96,23 +110,29 @@ void Coolio_Loop_process_event(VALUE watcher, int revents)
|
|
|
96
110
|
struct Coolio_Loop *loop_data;
|
|
97
111
|
struct Coolio_Watcher *watcher_data;
|
|
98
112
|
|
|
99
|
-
/* The Global VM
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
113
|
+
/* The Global VM Lock is held here, see the explanation below */
|
|
114
|
+
watcher_data = Coolio_Watcher_ptr(watcher);
|
|
115
|
+
|
|
116
|
+
if (watcher_data->enabled == 0) {
|
|
117
|
+
/* Ignore event because watcher was already detached. */
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
loop_data = Coolio_Loop_ptr(watcher_data->loop);
|
|
103
122
|
|
|
104
123
|
/* Well, what better place to explain how this all works than
|
|
105
124
|
* where the most wonky and convoluted stuff is going on!
|
|
106
125
|
*
|
|
107
126
|
* Our call path up to here looks a little something like:
|
|
108
127
|
*
|
|
109
|
-
* -> release GVL -> event syscall -> libev callback
|
|
110
|
-
* (GVL = Global VM Lock)
|
|
128
|
+
* -> release GVL -> event syscall -> reacquire GVL -> libev callback
|
|
129
|
+
* (GVL = Global VM Lock) ^^^ You are here
|
|
111
130
|
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
131
|
+
* libev is patched (see ev.c) to release the GVL around the blocking
|
|
132
|
+
* system call (one of epoll, kqueue, port, poll, or select, depending
|
|
133
|
+
* on the platform) so other Ruby threads can run while we wait there.
|
|
134
|
+
* Only that call runs without the GVL: libev invokes the events it
|
|
135
|
+
* collected after the call has returned, so we hold the GVL here.
|
|
116
136
|
*
|
|
117
137
|
* More specifically, this is a libev callback abstraction
|
|
118
138
|
* called from a real libev callback in every watcher,
|
|
@@ -130,19 +150,19 @@ void Coolio_Loop_process_event(VALUE watcher, int revents)
|
|
|
130
150
|
* event fired, why the hell is it telling the loop? Why
|
|
131
151
|
* doesn't it just rb_funcall() the appropriate callback?
|
|
132
152
|
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
* it back we have to:
|
|
153
|
+
* Because Ruby code doesn't run from inside libev's own event
|
|
154
|
+
* invocation. Instead:
|
|
136
155
|
*
|
|
137
|
-
* stash event and return ->
|
|
156
|
+
* stash event and return -> ev_loop() returns -> dispatch to Ruby
|
|
138
157
|
*
|
|
139
|
-
* Which is kinda ugly and confusing, but still gives us
|
|
158
|
+
* Which is kinda ugly and confusing, but still gives us
|
|
140
159
|
* an O(1) event loop whose heart is in the kernel itself. w00t!
|
|
141
160
|
*
|
|
142
161
|
* So, stash the event in the loop's data struct. When we return
|
|
143
162
|
* the ev_loop() call being made in the Coolio_Loop_run_once_blocking()
|
|
144
|
-
* function below will also return,
|
|
145
|
-
*
|
|
163
|
+
* function below will also return, and Coolio_Loop_dispatch_events()
|
|
164
|
+
* walks what we stashed and calls out to Ruby. A watcher detached
|
|
165
|
+
* along the way nils its own stashed entries, which that walk skips */
|
|
146
166
|
|
|
147
167
|
/* Grow the event buffer if it's too small */
|
|
148
168
|
if(loop_data->events_received >= loop_data->eventbuf_size) {
|
|
@@ -184,7 +204,7 @@ static VALUE Coolio_Loop_run_once(int argc, VALUE *argv, VALUE self)
|
|
|
184
204
|
rb_raise(rb_eArgError, "time interval must be positive");
|
|
185
205
|
}
|
|
186
206
|
|
|
187
|
-
|
|
207
|
+
loop_data = Coolio_Loop_ptr(self);
|
|
188
208
|
|
|
189
209
|
assert(loop_data->ev_loop && !loop_data->events_received);
|
|
190
210
|
|
|
@@ -222,7 +242,7 @@ static VALUE Coolio_Loop_run_nonblock(VALUE self)
|
|
|
222
242
|
struct Coolio_Loop *loop_data;
|
|
223
243
|
VALUE nevents;
|
|
224
244
|
|
|
225
|
-
|
|
245
|
+
loop_data = Coolio_Loop_ptr(self);
|
|
226
246
|
|
|
227
247
|
assert(loop_data->ev_loop && !loop_data->events_received);
|
|
228
248
|
|
|
@@ -247,7 +267,7 @@ static void Coolio_Loop_dispatch_events(struct Coolio_Loop *loop_data)
|
|
|
247
267
|
if(loop_data->eventbuf[i].watcher == Qnil)
|
|
248
268
|
continue;
|
|
249
269
|
|
|
250
|
-
|
|
270
|
+
watcher_data = Coolio_Watcher_ptr(loop_data->eventbuf[i].watcher);
|
|
251
271
|
watcher_data->dispatch_callback(loop_data->eventbuf[i].watcher, loop_data->eventbuf[i].revents);
|
|
252
272
|
}
|
|
253
273
|
}
|
data/ext/cool.io/stat_watcher.c
CHANGED
|
@@ -79,7 +79,9 @@ void Init_coolio_stat_watcher()
|
|
|
79
79
|
*/
|
|
80
80
|
static VALUE Coolio_StatWatcher_initialize(int argc, VALUE *argv, VALUE self)
|
|
81
81
|
{
|
|
82
|
-
|
|
82
|
+
VALUE path, interval;
|
|
83
|
+
const char *path_str;
|
|
84
|
+
long path_len;
|
|
83
85
|
struct Coolio_Watcher *watcher_data;
|
|
84
86
|
|
|
85
87
|
rb_scan_args(argc, argv, "11", &path, &interval);
|
|
@@ -87,15 +89,26 @@ static VALUE Coolio_StatWatcher_initialize(int argc, VALUE *argv, VALUE self)
|
|
|
87
89
|
interval = rb_convert_type(interval, T_FLOAT, "Float", "to_f");
|
|
88
90
|
|
|
89
91
|
path = rb_String(path);
|
|
92
|
+
path_str = StringValueCStr(path);
|
|
90
93
|
rb_iv_set(self, "@path", path);
|
|
91
94
|
|
|
92
|
-
|
|
95
|
+
watcher_data = Coolio_Watcher_ptr(self);
|
|
96
|
+
|
|
97
|
+
/* libev keeps the path pointer passed to ev_stat_init() for the lifetime of
|
|
98
|
+
* the watcher. RSTRING_PTR(path) points into a Ruby String whose buffer
|
|
99
|
+
* GC.compact may relocate, leaving libev with a dangling pointer. Keep an
|
|
100
|
+
* owned copy instead; it is released in Coolio_Watcher_free(). */
|
|
101
|
+
path_len = strlen(path_str);
|
|
102
|
+
if(watcher_data->stat_path)
|
|
103
|
+
xfree(watcher_data->stat_path);
|
|
104
|
+
watcher_data->stat_path = xmalloc(path_len + 1);
|
|
105
|
+
memcpy(watcher_data->stat_path, path_str, path_len + 1);
|
|
93
106
|
|
|
94
107
|
watcher_data->dispatch_callback = Coolio_StatWatcher_dispatch_callback;
|
|
95
108
|
ev_stat_init(
|
|
96
109
|
&watcher_data->event_types.ev_stat,
|
|
97
110
|
Coolio_StatWatcher_libev_callback,
|
|
98
|
-
|
|
111
|
+
watcher_data->stat_path,
|
|
99
112
|
interval == Qnil ? 0 : NUM2DBL(interval)
|
|
100
113
|
);
|
|
101
114
|
watcher_data->event_types.ev_stat.data = (void *)self;
|
|
@@ -119,8 +132,8 @@ static VALUE Coolio_StatWatcher_attach(VALUE self, VALUE loop)
|
|
|
119
132
|
if(!rb_obj_is_kind_of(loop, cCoolio_Loop))
|
|
120
133
|
rb_raise(rb_eArgError, "expected loop to be an instance of Coolio::Loop, not %s", RSTRING_PTR(rb_inspect(loop)));
|
|
121
134
|
|
|
122
|
-
|
|
123
|
-
|
|
135
|
+
loop_data = Coolio_Loop_ptr(loop);
|
|
136
|
+
watcher_data = Coolio_Watcher_ptr(self);
|
|
124
137
|
|
|
125
138
|
if(watcher_data->loop != Qnil)
|
|
126
139
|
Coolio_StatWatcher_detach(self);
|
|
@@ -206,7 +219,7 @@ static void Coolio_StatWatcher_libev_callback(struct ev_loop *ev_loop, struct ev
|
|
|
206
219
|
static void Coolio_StatWatcher_dispatch_callback(VALUE self, int revents)
|
|
207
220
|
{
|
|
208
221
|
struct Coolio_Watcher *watcher_data;
|
|
209
|
-
|
|
222
|
+
watcher_data = Coolio_Watcher_ptr(self);
|
|
210
223
|
|
|
211
224
|
VALUE previous_statdata = Coolio_StatInfo_build(&watcher_data->event_types.ev_stat.prev);
|
|
212
225
|
VALUE current_statdata = Coolio_StatInfo_build(&watcher_data->event_types.ev_stat.attr);
|
data/ext/cool.io/timer_watcher.c
CHANGED
|
@@ -68,7 +68,7 @@ static VALUE Coolio_TimerWatcher_initialize(int argc, VALUE *argv, VALUE self)
|
|
|
68
68
|
rb_iv_set(self, "@interval", interval);
|
|
69
69
|
rb_iv_set(self, "@repeating", repeating);
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
watcher_data = Coolio_Watcher_ptr(self);
|
|
72
72
|
|
|
73
73
|
watcher_data->dispatch_callback = Coolio_TimerWatcher_dispatch_callback;
|
|
74
74
|
ev_timer_init(
|
|
@@ -98,8 +98,8 @@ static VALUE Coolio_TimerWatcher_attach(VALUE self, VALUE loop)
|
|
|
98
98
|
if(!rb_obj_is_kind_of(loop, cCoolio_Loop))
|
|
99
99
|
rb_raise(rb_eArgError, "expected loop to be an instance of Coolio::Loop, not %s", RSTRING_PTR(rb_inspect(loop)));
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
loop_data = Coolio_Loop_ptr(loop);
|
|
102
|
+
watcher_data = Coolio_Watcher_ptr(self);
|
|
103
103
|
|
|
104
104
|
if(watcher_data->loop != Qnil)
|
|
105
105
|
Coolio_TimerWatcher_detach(self);
|
|
@@ -180,12 +180,12 @@ static VALUE Coolio_TimerWatcher_reset(VALUE self)
|
|
|
180
180
|
struct Coolio_Watcher *watcher_data;
|
|
181
181
|
struct Coolio_Loop *loop_data;
|
|
182
182
|
|
|
183
|
-
|
|
183
|
+
watcher_data = Coolio_Watcher_ptr(self);
|
|
184
184
|
|
|
185
185
|
if(watcher_data->loop == Qnil)
|
|
186
186
|
rb_raise(rb_eRuntimeError, "not attached to a loop");
|
|
187
187
|
|
|
188
|
-
|
|
188
|
+
loop_data = Coolio_Loop_ptr(watcher_data->loop);
|
|
189
189
|
|
|
190
190
|
ev_timer_again(loop_data->ev_loop, &watcher_data->event_types.ev_timer);
|
|
191
191
|
|
data/ext/cool.io/watcher.c
CHANGED
|
@@ -13,8 +13,8 @@ static VALUE mCoolio = Qnil;
|
|
|
13
13
|
static VALUE cCoolio_Watcher = Qnil;
|
|
14
14
|
|
|
15
15
|
static VALUE Coolio_Watcher_allocate(VALUE klass);
|
|
16
|
-
static void Coolio_Watcher_mark(
|
|
17
|
-
static void Coolio_Watcher_free(
|
|
16
|
+
static void Coolio_Watcher_mark(void *data);
|
|
17
|
+
static void Coolio_Watcher_free(void *data);
|
|
18
18
|
|
|
19
19
|
static VALUE Coolio_Watcher_initialize(VALUE self);
|
|
20
20
|
static VALUE Coolio_Watcher_attach(VALUE self, VALUE loop);
|
|
@@ -53,24 +53,49 @@ void Init_coolio_watcher()
|
|
|
53
53
|
rb_define_method(cCoolio_Watcher, "enabled?", Coolio_Watcher_enabled, 0);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
static const rb_data_type_t Coolio_Watcher_type = {
|
|
57
|
+
"Coolio::Watcher",
|
|
58
|
+
{
|
|
59
|
+
Coolio_Watcher_mark,
|
|
60
|
+
Coolio_Watcher_free,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
struct Coolio_Watcher *Coolio_Watcher_ptr(VALUE watcher)
|
|
65
|
+
{
|
|
66
|
+
struct Coolio_Watcher *watcher_data;
|
|
67
|
+
|
|
68
|
+
TypedData_Get_Struct(watcher, struct Coolio_Watcher, &Coolio_Watcher_type, watcher_data);
|
|
69
|
+
return watcher_data;
|
|
70
|
+
}
|
|
71
|
+
|
|
56
72
|
static VALUE Coolio_Watcher_allocate(VALUE klass)
|
|
57
73
|
{
|
|
58
|
-
struct Coolio_Watcher *watcher_data
|
|
74
|
+
struct Coolio_Watcher *watcher_data;
|
|
75
|
+
VALUE watcher = TypedData_Make_Struct(klass, struct Coolio_Watcher, &Coolio_Watcher_type, watcher_data);
|
|
59
76
|
|
|
60
77
|
watcher_data->loop = Qnil;
|
|
61
78
|
watcher_data->enabled = 0;
|
|
79
|
+
watcher_data->stat_path = NULL;
|
|
62
80
|
|
|
63
|
-
return
|
|
81
|
+
return watcher;
|
|
64
82
|
}
|
|
65
83
|
|
|
66
|
-
static void Coolio_Watcher_mark(
|
|
84
|
+
static void Coolio_Watcher_mark(void *data)
|
|
67
85
|
{
|
|
86
|
+
struct Coolio_Watcher *watcher_data = data;
|
|
87
|
+
|
|
68
88
|
if(watcher_data->loop != Qnil)
|
|
69
89
|
rb_gc_mark(watcher_data->loop);
|
|
70
90
|
}
|
|
71
91
|
|
|
72
|
-
static void Coolio_Watcher_free(
|
|
92
|
+
static void Coolio_Watcher_free(void *data)
|
|
73
93
|
{
|
|
94
|
+
struct Coolio_Watcher *watcher_data = data;
|
|
95
|
+
|
|
96
|
+
if(watcher_data->stat_path)
|
|
97
|
+
xfree(watcher_data->stat_path);
|
|
98
|
+
|
|
74
99
|
xfree(watcher_data);
|
|
75
100
|
}
|
|
76
101
|
|
|
@@ -91,7 +116,7 @@ static VALUE Coolio_Watcher_attach(VALUE self, VALUE loop)
|
|
|
91
116
|
VALUE loop_watchers, active_watchers;
|
|
92
117
|
struct Coolio_Watcher *watcher_data;
|
|
93
118
|
|
|
94
|
-
|
|
119
|
+
watcher_data = Coolio_Watcher_ptr(self);
|
|
95
120
|
watcher_data->enabled = 1;
|
|
96
121
|
|
|
97
122
|
loop_watchers = rb_iv_get(loop, "@watchers");
|
|
@@ -132,7 +157,7 @@ static VALUE Coolio_Watcher_detach(VALUE self)
|
|
|
132
157
|
VALUE loop_watchers;
|
|
133
158
|
int i;
|
|
134
159
|
|
|
135
|
-
|
|
160
|
+
watcher_data = Coolio_Watcher_ptr(self);
|
|
136
161
|
|
|
137
162
|
if(watcher_data->loop == Qnil)
|
|
138
163
|
rb_raise(rb_eRuntimeError, "not attached to a loop");
|
|
@@ -155,7 +180,7 @@ static VALUE Coolio_Watcher_detach(VALUE self)
|
|
|
155
180
|
|
|
156
181
|
watcher_data->enabled = 0;
|
|
157
182
|
|
|
158
|
-
|
|
183
|
+
loop_data = Coolio_Loop_ptr(watcher_data->loop);
|
|
159
184
|
|
|
160
185
|
/* Iterate through the events in the loop's event buffer. If there
|
|
161
186
|
* are any pending events from this watcher, mark them NULL. The
|
|
@@ -182,7 +207,7 @@ static VALUE Coolio_Watcher_detach(VALUE self)
|
|
|
182
207
|
static VALUE Coolio_Watcher_enable(VALUE self)
|
|
183
208
|
{
|
|
184
209
|
struct Coolio_Watcher *watcher_data;
|
|
185
|
-
|
|
210
|
+
watcher_data = Coolio_Watcher_ptr(self);
|
|
186
211
|
|
|
187
212
|
if(watcher_data->enabled)
|
|
188
213
|
rb_raise(rb_eRuntimeError, "already enabled");
|
|
@@ -208,7 +233,7 @@ static VALUE Coolio_Watcher_enable(VALUE self)
|
|
|
208
233
|
static VALUE Coolio_Watcher_disable(VALUE self)
|
|
209
234
|
{
|
|
210
235
|
struct Coolio_Watcher *watcher_data;
|
|
211
|
-
|
|
236
|
+
watcher_data = Coolio_Watcher_ptr(self);
|
|
212
237
|
|
|
213
238
|
if(!watcher_data->enabled)
|
|
214
239
|
rb_raise(rb_eRuntimeError, "already disabled");
|
|
@@ -234,7 +259,7 @@ static VALUE Coolio_Watcher_evloop(VALUE self)
|
|
|
234
259
|
{
|
|
235
260
|
struct Coolio_Watcher *watcher_data;
|
|
236
261
|
|
|
237
|
-
|
|
262
|
+
watcher_data = Coolio_Watcher_ptr(self);
|
|
238
263
|
return watcher_data->loop;
|
|
239
264
|
}
|
|
240
265
|
|
|
@@ -258,7 +283,7 @@ static VALUE Coolio_Watcher_attached(VALUE self)
|
|
|
258
283
|
static VALUE Coolio_Watcher_enabled(VALUE self)
|
|
259
284
|
{
|
|
260
285
|
struct Coolio_Watcher *watcher_data;
|
|
261
|
-
|
|
286
|
+
watcher_data = Coolio_Watcher_ptr(self);
|
|
262
287
|
|
|
263
288
|
return watcher_data->enabled ? Qtrue : Qfalse;
|
|
264
289
|
}
|
data/ext/cool.io/watcher.h
CHANGED
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
if(!rb_obj_is_kind_of(loop, cCoolio_Loop)) \
|
|
15
15
|
rb_raise(rb_eArgError, "expected loop to be an instance of Coolio::Loop, not %s", RSTRING_PTR(rb_inspect(loop))); \
|
|
16
16
|
\
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
watcher_data = Coolio_Watcher_ptr(watcher); \
|
|
18
|
+
loop_data = Coolio_Loop_ptr(loop); \
|
|
19
19
|
\
|
|
20
20
|
if(watcher_data->loop != Qnil) \
|
|
21
21
|
detach_func(watcher); \
|
|
@@ -26,30 +26,29 @@
|
|
|
26
26
|
|
|
27
27
|
#define Watcher_Detach(watcher_type, watcher) \
|
|
28
28
|
struct Coolio_Watcher *watcher_data; \
|
|
29
|
-
struct Coolio_Loop *loop_data; \
|
|
30
29
|
\
|
|
31
|
-
|
|
30
|
+
watcher_data = Coolio_Watcher_ptr(watcher); \
|
|
32
31
|
\
|
|
33
32
|
if(watcher_data->loop == Qnil) \
|
|
34
33
|
rb_raise(rb_eRuntimeError, "not attached to a loop"); \
|
|
35
34
|
\
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
if (watcher_data->enabled) { \
|
|
36
|
+
rb_funcall(watcher, rb_intern("disable"), 0); \
|
|
37
|
+
} \
|
|
39
38
|
rb_call_super(0, 0)
|
|
40
39
|
|
|
41
40
|
#define Watcher_Enable(watcher_type, watcher) \
|
|
42
41
|
struct Coolio_Watcher *watcher_data; \
|
|
43
42
|
struct Coolio_Loop *loop_data; \
|
|
44
43
|
\
|
|
45
|
-
|
|
44
|
+
watcher_data = Coolio_Watcher_ptr(watcher); \
|
|
46
45
|
\
|
|
47
46
|
if(watcher_data->loop == Qnil) \
|
|
48
47
|
rb_raise(rb_eRuntimeError, "not attached to a loop"); \
|
|
49
48
|
\
|
|
50
49
|
rb_call_super(0, 0); \
|
|
51
50
|
\
|
|
52
|
-
|
|
51
|
+
loop_data = Coolio_Loop_ptr(watcher_data->loop); \
|
|
53
52
|
\
|
|
54
53
|
ev_##watcher_type##_start(loop_data->ev_loop, &watcher_data->event_types.ev_##watcher_type)
|
|
55
54
|
|
|
@@ -57,15 +56,15 @@
|
|
|
57
56
|
struct Coolio_Watcher *watcher_data; \
|
|
58
57
|
struct Coolio_Loop *loop_data; \
|
|
59
58
|
\
|
|
60
|
-
|
|
59
|
+
watcher_data = Coolio_Watcher_ptr(watcher); \
|
|
61
60
|
\
|
|
62
61
|
if(watcher_data->loop == Qnil) \
|
|
63
62
|
rb_raise(rb_eRuntimeError, "not attached to a loop"); \
|
|
64
63
|
\
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
\
|
|
69
|
-
|
|
64
|
+
if (watcher_data->enabled) { \
|
|
65
|
+
loop_data = Coolio_Loop_ptr(watcher_data->loop); \
|
|
66
|
+
ev_##watcher_type##_stop(loop_data->ev_loop, &watcher_data->event_types.ev_##watcher_type); \
|
|
67
|
+
} \
|
|
68
|
+
rb_call_super(0, 0);
|
|
70
69
|
|
|
71
70
|
#endif
|
data/ext/libev/ev.c
CHANGED
|
@@ -210,7 +210,13 @@
|
|
|
210
210
|
#else
|
|
211
211
|
# include <io.h>
|
|
212
212
|
# define WIN32_LEAN_AND_MEAN
|
|
213
|
-
|
|
213
|
+
/* ruby.h above already pulled in winsock2.h, so fd_set may be dimensioned
|
|
214
|
+
* already. Defining FD_SETSIZE unconditionally here would only move the bound
|
|
215
|
+
* used by EV_WIN_FD_SET, not the array it indexes. Take whatever is in effect
|
|
216
|
+
* and only supply a default when nothing has been decided yet. */
|
|
217
|
+
# ifndef FD_SETSIZE
|
|
218
|
+
# define FD_SETSIZE 1024
|
|
219
|
+
# endif
|
|
214
220
|
# include <winsock2.h>
|
|
215
221
|
# include <windows.h>
|
|
216
222
|
# ifndef EV_SELECT_IS_WINSOCKET
|
data/ext/libev/ev_select.c
CHANGED
|
@@ -107,6 +107,16 @@ if (__i == ((fd_set *)(set))->fd_count) {\
|
|
|
107
107
|
#define EV_WIN_FD_ZERO(set) (((fd_set *)(set))->fd_count=0)
|
|
108
108
|
#define EV_WIN_FD_ISSET(fd, set) __WSAFDIsSet((SOCKET)(fd), (fd_set *)(set))
|
|
109
109
|
#define EV_WIN_FD_COUNT(set) (((fd_set *)(set))->fd_count)
|
|
110
|
+
|
|
111
|
+
/*
|
|
112
|
+
fd_set is dimensioned by whatever FD_SETSIZE was in effect when winsock2.h was
|
|
113
|
+
first pulled in, but EV_WIN_FD_SET and select_modify bound-check against the
|
|
114
|
+
FD_SETSIZE visible here. The two are decided by separate paths, so if the bound
|
|
115
|
+
ever exceeds the declared array we would write past the end of the allocation in
|
|
116
|
+
select_init. Catch that at build time instead.
|
|
117
|
+
*/
|
|
118
|
+
typedef char coolio_fd_setsize_matches_fd_set[
|
|
119
|
+
(sizeof (((fd_set *)0)->fd_array) / sizeof (SOCKET) >= (size_t)FD_SETSIZE) ? 1 : -1];
|
|
110
120
|
/* ######################################## */
|
|
111
121
|
#else
|
|
112
122
|
#define EV_WIN_FD_CLR FD_CLR
|
data/gems.rb
CHANGED
data/lib/cool.io/dns_resolver.rb
CHANGED
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
#++
|
|
19
19
|
|
|
20
20
|
require 'resolv'
|
|
21
|
+
require 'securerandom'
|
|
22
|
+
require 'socket'
|
|
21
23
|
|
|
22
24
|
module Coolio
|
|
23
25
|
# A non-blocking DNS resolver. It provides interfaces for querying both
|
|
@@ -67,14 +69,22 @@ module Coolio
|
|
|
67
69
|
# list of nameservers to query. By default the resolver will
|
|
68
70
|
# use nameservers listed in /etc/resolv.conf
|
|
69
71
|
def initialize(hostname, *nameservers)
|
|
72
|
+
nameservers = reject_ipv6_nameservers(nameservers)
|
|
70
73
|
if nameservers.empty?
|
|
71
|
-
nameservers = Resolv::DNS::Config.default_config_hash[:nameserver]
|
|
74
|
+
nameservers = reject_ipv6_nameservers(Resolv::DNS::Config.default_config_hash[:nameserver])
|
|
72
75
|
raise RuntimeError, "no nameservers found" if nameservers.empty? # TODO just call resolve_failed, not raise [also handle Errno::ENOENT)]
|
|
73
76
|
end
|
|
74
77
|
|
|
75
|
-
@nameservers = nameservers
|
|
78
|
+
@nameservers = nameservers.dup
|
|
76
79
|
@question = request_question hostname
|
|
77
80
|
|
|
81
|
+
# A guessable ID would let an off-path attacker forge a response
|
|
82
|
+
@request_id = SecureRandom.random_number(1 << 16)
|
|
83
|
+
|
|
84
|
+
# Numeric addresses this query was sent to, and the lookups behind them
|
|
85
|
+
@queried_addresses = []
|
|
86
|
+
@numeric_addresses = {}
|
|
87
|
+
|
|
78
88
|
@socket = UDPSocket.new
|
|
79
89
|
@timer = Timeout.new(self)
|
|
80
90
|
|
|
@@ -113,27 +123,44 @@ module Coolio
|
|
|
113
123
|
|
|
114
124
|
# Send a request to the DNS server
|
|
115
125
|
def send_request
|
|
116
|
-
|
|
117
|
-
|
|
126
|
+
@nameservers.rotate!
|
|
127
|
+
|
|
128
|
+
# Send to the numeric address, so we know where a response must come from
|
|
129
|
+
address = numeric_address(@nameservers.first)
|
|
130
|
+
@queried_addresses << address unless @queried_addresses.include?(address)
|
|
131
|
+
|
|
118
132
|
begin
|
|
119
|
-
@socket.send request_message, 0,
|
|
133
|
+
@socket.send request_message, 0, address, DNS_PORT
|
|
120
134
|
rescue Errno::EHOSTUNREACH # TODO figure out why it has to be wrapper here, when the other wrapper should be wrapping this one!
|
|
121
135
|
end
|
|
122
136
|
end
|
|
123
137
|
|
|
124
138
|
# Called by the subclass when the DNS response is available
|
|
125
139
|
def on_readable
|
|
126
|
-
datagram = nil
|
|
140
|
+
datagram = sender = nil
|
|
127
141
|
begin
|
|
128
|
-
datagram = @socket.recvfrom_nonblock(DATAGRAM_SIZE)
|
|
142
|
+
datagram, sender = @socket.recvfrom_nonblock(DATAGRAM_SIZE)
|
|
129
143
|
rescue Errno::ECONNREFUSED
|
|
130
144
|
end
|
|
131
145
|
|
|
146
|
+
# Ignore anything we didn't ask for, rather than resolving or failing on it.
|
|
147
|
+
# The query stays outstanding, so the retry timer still bounds us.
|
|
148
|
+
return if datagram and not solicited_response?(datagram, sender)
|
|
149
|
+
|
|
132
150
|
address = response_address datagram rescue nil
|
|
133
151
|
address ? on_success(address) : on_failure
|
|
134
152
|
detach
|
|
135
153
|
end
|
|
136
154
|
|
|
155
|
+
# Is this a reply to our query, from an address we sent it to?
|
|
156
|
+
# Retries rotate through @nameservers, so any address already queried counts.
|
|
157
|
+
def solicited_response?(datagram, sender)
|
|
158
|
+
return false unless datagram.size >= 12
|
|
159
|
+
return false unless sender and sender[1] == DNS_PORT and @queried_addresses.include?(sender[3])
|
|
160
|
+
|
|
161
|
+
datagram[0..1].unpack('n').first.to_i == @request_id
|
|
162
|
+
end
|
|
163
|
+
|
|
137
164
|
def request_question(hostname)
|
|
138
165
|
raise ArgumentError, "hostname cannot be nil" if hostname.nil?
|
|
139
166
|
|
|
@@ -151,7 +178,7 @@ module Coolio
|
|
|
151
178
|
|
|
152
179
|
def request_message
|
|
153
180
|
# Standard query header
|
|
154
|
-
message = [
|
|
181
|
+
message = [@request_id, 1, 0].pack('nCC')
|
|
155
182
|
|
|
156
183
|
# One entry
|
|
157
184
|
qdcount = 1
|
|
@@ -166,7 +193,7 @@ module Coolio
|
|
|
166
193
|
def response_address(message)
|
|
167
194
|
# Confirm the ID field
|
|
168
195
|
id = message[0..1].unpack('n').first.to_i
|
|
169
|
-
return unless id ==
|
|
196
|
+
return unless id == @request_id
|
|
170
197
|
|
|
171
198
|
# Check the QR value and confirm this message is a response
|
|
172
199
|
qr = message[2..2].unpack('B1').first.to_i
|
|
@@ -204,6 +231,23 @@ module Coolio
|
|
|
204
231
|
nil
|
|
205
232
|
end
|
|
206
233
|
|
|
234
|
+
private
|
|
235
|
+
|
|
236
|
+
def reject_ipv6_nameservers(nameservers)
|
|
237
|
+
nameservers.reject { |ns| ns.include?(':') }
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# The address of a nameserver, which may be given as a hostname.
|
|
241
|
+
# Only successful lookups are cached, so a transient failure is looked up again.
|
|
242
|
+
def numeric_address(nameserver)
|
|
243
|
+
@numeric_addresses[nameserver] ||= begin
|
|
244
|
+
addrinfo = Addrinfo.getaddrinfo(nameserver, nil, ::Socket::AF_INET, ::Socket::SOCK_DGRAM).first
|
|
245
|
+
raise SocketError, "getaddrinfo: no IPv4 address for #{nameserver}" if addrinfo.nil?
|
|
246
|
+
|
|
247
|
+
addrinfo.ip_address
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
207
251
|
class Timeout < TimerWatcher
|
|
208
252
|
def initialize(resolver)
|
|
209
253
|
@resolver = resolver
|
data/lib/cool.io/version.rb
CHANGED