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.
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 Coolio_Loop_mark(struct Coolio_Loop *loop);
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 = (struct Coolio_Loop *)xmalloc(sizeof(struct Coolio_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 Data_Wrap_Struct(klass, Coolio_Loop_mark, Coolio_Loop_free, loop);
77
+ return obj;
62
78
  }
63
79
 
64
- static void Coolio_Loop_mark(struct Coolio_Loop *loop)
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
- Data_Get_Struct(self, struct Coolio_Loop, loop_data);
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 lock isn't held right now, but hopefully
100
- * we can still do this safely */
101
- Data_Get_Struct(watcher, struct Coolio_Watcher, watcher_data);
102
- Data_Get_Struct(watcher_data->loop, struct Coolio_Loop, loop_data);
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) ^^^ You are here
128
+ * -> release GVL -> event syscall -> reacquire GVL -> libev callback
129
+ * (GVL = Global VM Lock) ^^^ You are here
111
130
  *
112
- * We released the GVL in the Coolio_Loop_run_once() function
113
- * so other Ruby threads can run while we make a blocking
114
- * system call (one of epoll, kqueue, port, poll, or select,
115
- * depending on the platform).
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
- * Well, the problem is the Global VM Lock isn't held right
134
- * now, so we can't rb_funcall() anything. In order to get
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 -> acquire GVL -> dispatch to Ruby
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, at which point the GVL is
145
- * reacquired and we can call out to Ruby */
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
- Data_Get_Struct(self, struct Coolio_Loop, loop_data);
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
- Data_Get_Struct(self, struct Coolio_Loop, loop_data);
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
- Data_Get_Struct(loop_data->eventbuf[i].watcher, struct Coolio_Watcher, watcher_data);
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
  }
@@ -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
- VALUE path, interval;
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
- Data_Get_Struct(self, struct Coolio_Watcher, watcher_data);
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
- RSTRING_PTR(path),
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
- Data_Get_Struct(loop, struct Coolio_Loop, loop_data);
123
- Data_Get_Struct(self, struct Coolio_Watcher, watcher_data);
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
- Data_Get_Struct(self, struct Coolio_Watcher, watcher_data);
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);
@@ -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
- Data_Get_Struct(self, struct Coolio_Watcher, watcher_data);
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
- Data_Get_Struct(loop, struct Coolio_Loop, loop_data);
102
- Data_Get_Struct(self, struct Coolio_Watcher, watcher_data);
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
- Data_Get_Struct(self, struct Coolio_Watcher, watcher_data);
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
- Data_Get_Struct(watcher_data->loop, struct Coolio_Loop, loop_data);
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
 
@@ -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(struct Coolio_Watcher *watcher);
17
- static void Coolio_Watcher_free(struct Coolio_Watcher *watcher);
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 = (struct Coolio_Watcher *)xmalloc(sizeof(struct Coolio_Watcher));
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 Data_Wrap_Struct(klass, Coolio_Watcher_mark, Coolio_Watcher_free, watcher_data);
81
+ return watcher;
64
82
  }
65
83
 
66
- static void Coolio_Watcher_mark(struct Coolio_Watcher *watcher_data)
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(struct Coolio_Watcher *watcher_data)
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
- Data_Get_Struct(self, struct Coolio_Watcher, watcher_data);
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
- Data_Get_Struct(self, struct Coolio_Watcher, watcher_data);
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
- Data_Get_Struct(watcher_data->loop, struct Coolio_Loop, loop_data);
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
- Data_Get_Struct(self, struct Coolio_Watcher, watcher_data);
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
- Data_Get_Struct(self, struct Coolio_Watcher, watcher_data);
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
- Data_Get_Struct(self, struct Coolio_Watcher, watcher_data);
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
- Data_Get_Struct(self, struct Coolio_Watcher, watcher_data);
286
+ watcher_data = Coolio_Watcher_ptr(self);
262
287
 
263
288
  return watcher_data->enabled ? Qtrue : Qfalse;
264
289
  }
@@ -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
- Data_Get_Struct(watcher, struct Coolio_Watcher, watcher_data); \
18
- Data_Get_Struct(loop, struct Coolio_Loop, loop_data); \
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
- Data_Get_Struct(watcher, struct Coolio_Watcher, watcher_data); \
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
- Data_Get_Struct(watcher_data->loop, struct Coolio_Loop, loop_data); \
37
- \
38
- ev_##watcher_type##_stop(loop_data->ev_loop, &watcher_data->event_types.ev_##watcher_type); \
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
- Data_Get_Struct(watcher, struct Coolio_Watcher, watcher_data); \
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
- Data_Get_Struct(watcher_data->loop, struct Coolio_Loop, loop_data); \
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
- Data_Get_Struct(watcher, struct Coolio_Watcher, watcher_data); \
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
- rb_call_super(0, 0); \
66
- \
67
- Data_Get_Struct(watcher_data->loop, struct Coolio_Loop, loop_data); \
68
- \
69
- ev_##watcher_type##_stop(loop_data->ev_loop, &watcher_data->event_types.ev_##watcher_type)
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
- # define FD_SETSIZE 1024
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
@@ -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
@@ -7,3 +7,7 @@ group :maintenance, optional: true do
7
7
  gem "bake-gem"
8
8
  gem "bake-modernize"
9
9
  end
10
+
11
+ group :development, :test do
12
+ gem 'ruby_memcheck', '~> 3.0' if RUBY_PLATFORM.include?('linux')
13
+ end
@@ -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
- nameserver = @nameservers.shift
117
- @nameservers << nameserver # rotate them
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, @nameservers.first, DNS_PORT
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).first
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 = [2, 1, 0].pack('nCC')
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 == 2
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
@@ -1,6 +1,6 @@
1
1
  module Coolio
2
- VERSION = "1.9.0"
3
-
2
+ VERSION = "1.9.5"
3
+
4
4
  def self.version
5
5
  VERSION
6
6
  end