cool.io 1.2.0-x86-mingw32 → 1.2.3-x86-mingw32

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,13 @@
1
+ language: ruby
2
+
1
3
  rvm:
2
- - 1.8.7
3
- - 1.9.2
4
+ - 1.9.3
5
+ - 2.0.0
6
+ - 2.1.0
7
+ - 2.1.1
8
+ - ruby-head
4
9
  - rbx
10
+
11
+ matrix:
12
+ allow_failures:
13
+ - rvm: rbx
data/CHANGES.md CHANGED
@@ -1,9 +1,27 @@
1
+ 1.2.3
2
+ -----
3
+
4
+ * Fix CPU consuming issue on Windows.
5
+
6
+ 1.2.2
7
+ -----
8
+
9
+ * Add timeout option to Loop#run and Loop#run_once. Default by nil
10
+ * Support Ruby 2.2.0
11
+
12
+ 1.2.1
13
+ -----
14
+
15
+ * Release the GIL when libev polls (#24)
16
+ * Add Listener#listen method to change backlog size
17
+
1
18
  1.2.0
2
19
  -----
3
20
 
4
21
  * Support Windows environment via cross compilation
5
22
  * Include iobuffer library
6
23
  * Update to libev 4.15
24
+ * Remove Ruby 1.8 support
7
25
 
8
26
  1.1.0
9
27
  -----
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source "http://rubygems.org"
1
+ source "https://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in cool.io.gemspec
4
4
  gemspec
data/README.md CHANGED
@@ -1,8 +1,7 @@
1
1
  Cool.io
2
2
  =======
3
3
 
4
- ### NOTE: cool.io is in maintenance mode only and is not being actively developed
5
- ### Please check out [Celluloid::IO](http://github.com/celluloid/celluloid-io) instead!
4
+ ### If you are interested in Celluloid based IO framework, please check out [Celluloid::IO](http://github.com/celluloid/celluloid-io)
6
5
 
7
6
  Cool.io is an event library for Ruby, built on the libev event library which
8
7
  provides a cross-platform interface to high performance system calls . This
@@ -15,10 +14,8 @@ applications.
15
14
 
16
15
  You can include Cool.io in your programs with:
17
16
 
18
- require 'rubygems'
19
17
  require 'cool.io'
20
18
 
21
- Questions? Sign up for the mailing list by emailing: [cool.io@librelist.com](mailto:cool.io@librelist.com)
22
19
 
23
20
  Anatomy
24
21
  -------
@@ -97,7 +94,6 @@ Example Program
97
94
 
98
95
  Cool.io provides a Sinatra-like DSL for authoring event-driven programs:
99
96
 
100
- require 'rubygems'
101
97
  require 'cool.io'
102
98
 
103
99
  ADDR = '127.0.0.1'
@@ -32,6 +32,7 @@ struct Coolio_Event
32
32
  struct Coolio_Loop
33
33
  {
34
34
  struct ev_loop *ev_loop;
35
+ struct ev_timer timer; /* for timeouts */
35
36
 
36
37
  int running;
37
38
  int events_received;
@@ -1,7 +1,9 @@
1
1
  #define EV_STANDALONE /* keeps ev from requiring config.h */
2
+
2
3
  #ifdef _WIN32
3
- # define EV_SELECT_IS_WINSOCKET 1 /* configure libev for windows select */
4
- # define FD_SETSIZE 2048 /* wishful thinking, as msvcrt6 [?] seems to only allow 512 fd's and 256 sockets max */
4
+ #define EV_SELECT_IS_WINSOCKET 1 /* configure libev for windows select */
5
+ #define EV_USE_MONOTONIC 0
6
+ #define EV_USE_REALTIME 0
5
7
  #endif
6
8
 
7
9
  #include "../libev/ev.h"
@@ -8,6 +8,10 @@ if have_func('rb_thread_blocking_region')
8
8
  $defs << '-DHAVE_RB_THREAD_BLOCKING_REGION'
9
9
  end
10
10
 
11
+ if have_func('rb_thread_call_without_gvl')
12
+ $defs << '-DHAVE_RB_THEREAD_CALL_WITHOUT_GVL'
13
+ end
14
+
11
15
  if have_func('rb_thread_alone')
12
16
  $defs << '-DHAVE_RB_THREAD_ALONE'
13
17
  end
@@ -11,14 +11,6 @@
11
11
 
12
12
  #include "cool.io.h"
13
13
 
14
- #if defined(HAVE_RB_THREAD_BLOCKING_REGION)
15
- # define Coolio_Loop_may_block_safely() (1)
16
- #elif defined(HAVE_RB_THREAD_ALONE)
17
- # define Coolio_Loop_may_block_safely() (rb_thread_alone())
18
- #else /* just in case Ruby changes: */
19
- # define Coolio_Loop_may_block_safely() (0)
20
- #endif
21
-
22
14
  static VALUE mCoolio = Qnil;
23
15
  static VALUE cCoolio_Loop = Qnil;
24
16
 
@@ -28,10 +20,10 @@ static void Coolio_Loop_free(struct Coolio_Loop *loop);
28
20
 
29
21
  static VALUE Coolio_Loop_initialize(VALUE self);
30
22
  static VALUE Coolio_Loop_ev_loop_new(VALUE self, VALUE flags);
31
- static VALUE Coolio_Loop_run_once(VALUE self);
23
+ static VALUE Coolio_Loop_run_once(int argc, VALUE *argv, VALUE self);
32
24
  static VALUE Coolio_Loop_run_nonblock(VALUE self);
33
25
 
34
- static void Coolio_Loop_ev_loop_oneshot(struct Coolio_Loop *loop_data);
26
+ static void Coolio_Loop_timeout_callback(struct ev_loop *ev_loop, struct ev_timer *timer, int revents);
35
27
  static void Coolio_Loop_dispatch_events(struct Coolio_Loop *loop_data);
36
28
 
37
29
  #define DEFAULT_EVENTBUF_SIZE 32
@@ -54,7 +46,7 @@ void Init_coolio_loop()
54
46
 
55
47
  rb_define_method(cCoolio_Loop, "initialize", Coolio_Loop_initialize, 0);
56
48
  rb_define_private_method(cCoolio_Loop, "ev_loop_new", Coolio_Loop_ev_loop_new, 1);
57
- rb_define_method(cCoolio_Loop, "run_once", Coolio_Loop_run_once, 0);
49
+ rb_define_method(cCoolio_Loop, "run_once", Coolio_Loop_run_once, -1);
58
50
  rb_define_method(cCoolio_Loop, "run_nonblock", Coolio_Loop_run_nonblock, 0);
59
51
  }
60
52
 
@@ -63,6 +55,7 @@ static VALUE Coolio_Loop_allocate(VALUE klass)
63
55
  struct Coolio_Loop *loop = (struct Coolio_Loop *)xmalloc(sizeof(struct Coolio_Loop));
64
56
 
65
57
  loop->ev_loop = 0;
58
+ ev_init(&loop->timer, Coolio_Loop_timeout_callback);
66
59
  loop->running = 0;
67
60
  loop->events_received = 0;
68
61
  loop->eventbuf_size = DEFAULT_EVENTBUF_SIZE;
@@ -174,90 +167,57 @@ void Coolio_Loop_process_event(VALUE watcher, int revents)
174
167
  loop_data->events_received++;
175
168
  }
176
169
 
170
+ /* Called whenever a timeout fires on the event loop */
171
+ static void Coolio_Loop_timeout_callback(struct ev_loop *ev_loop, struct ev_timer *timer, int revents)
172
+ {
173
+ /* We don't actually need to do anything here, the mere firing of the
174
+ timer is sufficient to interrupt the selector. However, libev still wants a callback */
175
+ }
176
+
177
177
  /**
178
178
  * call-seq:
179
179
  * Coolio::Loop.run_once -> nil
180
180
  *
181
181
  * Run the Coolio::Loop once, blocking until events are received.
182
182
  */
183
- static VALUE Coolio_Loop_run_once(VALUE self)
183
+ static VALUE Coolio_Loop_run_once(int argc, VALUE *argv, VALUE self)
184
184
  {
185
+ VALUE timeout;
185
186
  VALUE nevents;
187
+ struct Coolio_Loop *loop_data;
186
188
 
187
- if (Coolio_Loop_may_block_safely()) {
188
- struct Coolio_Loop *loop_data;
189
-
190
- Data_Get_Struct(self, struct Coolio_Loop, loop_data);
189
+ rb_scan_args(argc, argv, "01", &timeout);
191
190
 
192
- assert(loop_data->ev_loop && !loop_data->events_received);
191
+ if (timeout != Qnil && NUM2DBL(timeout) < 0) {
192
+ rb_raise(rb_eArgError, "time interval must be positive");
193
+ }
193
194
 
194
- Coolio_Loop_ev_loop_oneshot(loop_data);
195
- Coolio_Loop_dispatch_events(loop_data);
195
+ Data_Get_Struct(self, struct Coolio_Loop, loop_data);
196
196
 
197
- nevents = INT2NUM(loop_data->events_received);
198
- loop_data->events_received = 0;
197
+ assert(loop_data->ev_loop && !loop_data->events_received);
199
198
 
199
+ /* Implement the optional timeout (if any) as a ev_timer */
200
+ /* Using the technique written at
201
+ http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod#code_ev_timer_code_relative_and_opti,
202
+ the timer is not stopped/started everytime when timeout is specified, instead,
203
+ the timer is stopped when timeout is not specified. */
204
+ if (timeout != Qnil) {
205
+ /* It seems libev is not a fan of timers being zero, so fudge a little */
206
+ loop_data->timer.repeat = NUM2DBL(timeout) + 0.0001;
207
+ ev_timer_again(loop_data->ev_loop, &loop_data->timer);
200
208
  } else {
201
- nevents = Coolio_Loop_run_nonblock(self);
202
- rb_thread_schedule();
209
+ ev_timer_stop(loop_data->ev_loop, &loop_data->timer);
203
210
  }
204
211
 
205
- return nevents;
206
- }
207
-
208
- /* Ruby 1.9 supports blocking system calls through rb_thread_blocking_region() */
209
- #ifdef HAVE_RB_THREAD_BLOCKING_REGION
210
- #define HAVE_EV_LOOP_ONESHOT
211
- static VALUE Coolio_Loop_ev_loop_oneshot_blocking(void *ptr)
212
- {
213
- /* The libev loop has now escaped through the Global VM Lock unscathed! */
214
- struct Coolio_Loop *loop_data = (struct Coolio_Loop *)ptr;
215
-
212
+ /* libev is patched to release the GIL when it makes its system call */
216
213
  RUN_LOOP(loop_data, EVLOOP_ONESHOT);
217
-
218
- return Qnil;
219
- }
220
214
 
221
- static void Coolio_Loop_ev_loop_oneshot(struct Coolio_Loop *loop_data)
222
- {
223
- /* Use Ruby 1.9's rb_thread_blocking_region call to make a blocking system call */
224
- rb_thread_blocking_region(Coolio_Loop_ev_loop_oneshot_blocking, loop_data, RUBY_UBF_IO, 0);
225
- }
226
- #endif
227
-
228
- /* Ruby 1.8 requires us to periodically run the event loop then defer back to
229
- * the green threads scheduler */
230
- #ifndef HAVE_EV_LOOP_ONESHOT
231
- #define BLOCKING_INTERVAL 0.01 /* Block for 10ms at a time */
232
-
233
- /* Stub for scheduler's ev_timer callback */
234
- static void timer_callback(struct ev_loop *ev_loop, struct ev_timer *timer, int revents)
235
- {
236
- ev_timer_again (ev_loop, timer);
237
- }
238
-
239
- /* Run the event loop, calling rb_thread_schedule every 10ms */
240
- static void Coolio_Loop_ev_loop_oneshot(struct Coolio_Loop *loop_data)
241
- {
242
- struct ev_timer timer;
243
- struct timeval tv;
244
-
245
- /* Set up an ev_timer to unblock the loop every 10ms */
246
- ev_timer_init(&timer, timer_callback, BLOCKING_INTERVAL, BLOCKING_INTERVAL);
247
- ev_timer_start(loop_data->ev_loop, &timer);
248
-
249
- /* Loop until we receive events */
250
- while(!loop_data->events_received) {
251
- TRAP_BEG;
252
- RUN_LOOP(loop_data, EVLOOP_ONESHOT);
253
- TRAP_END;
254
-
255
- rb_thread_schedule();
256
- }
215
+ Coolio_Loop_dispatch_events(loop_data);
216
+ nevents = INT2NUM(loop_data->events_received);
217
+ loop_data->events_received = 0;
257
218
 
258
- ev_timer_stop(loop_data->ev_loop, &timer);
219
+ return nevents;
259
220
  }
260
- #endif
261
221
 
262
222
  /**
263
223
  * call-seq:
@@ -37,6 +37,10 @@
37
37
  * either the BSD or the GPL.
38
38
  */
39
39
 
40
+ /* ########## COOLIO PATCHERY HO! ########## */
41
+ #include "ruby.h"
42
+ /* ######################################## */
43
+
40
44
  /* this big block deduces configuration from config.h */
41
45
  #ifndef EV_STANDALONE
42
46
  # ifdef EV_CONFIG_H
@@ -3237,9 +3241,27 @@ time_update (EV_P_ ev_tstamp max_block)
3237
3241
  }
3238
3242
  }
3239
3243
 
3244
+ /* ########## COOLIO PATCHERY HO! ########## */
3245
+ #if defined(HAVE_RB_THREAD_BLOCKING_REGION) || defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
3246
+ static
3247
+ VALUE ev_backend_poll(void **args)
3248
+ {
3249
+ struct ev_loop *loop = (struct ev_loop *)args[0];
3250
+ ev_tstamp waittime = *(ev_tstamp *)args[1];
3251
+ backend_poll (EV_A_ waittime);
3252
+ }
3253
+ #endif
3254
+ /* ######################################## */
3255
+
3240
3256
  int
3241
3257
  ev_run (EV_P_ int flags)
3242
3258
  {
3259
+ /* ########## COOLIO PATCHERY HO! ########## */
3260
+ #if defined(HAVE_RB_THREAD_BLOCKING_REGION) || defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
3261
+ void *poll_args[2];
3262
+ #endif
3263
+ /* ######################################## */
3264
+
3243
3265
  #if EV_FEATURE_API
3244
3266
  ++loop_depth;
3245
3267
  #endif
@@ -3357,7 +3379,67 @@ ev_run (EV_P_ int flags)
3357
3379
  ++loop_count;
3358
3380
  #endif
3359
3381
  assert ((loop_done = EVBREAK_RECURSE, 1)); /* assert for side effect */
3382
+
3383
+ /*
3384
+ ########################## COOLIO PATCHERY HO! ##########################
3385
+
3386
+ The original patch file is made by Tony Arcieri.
3387
+ https://github.com/celluloid/nio4r/blob/680143345726c5a64bb22376ca8fc3c6857019ae/ext/libev/ruby_gil.patch.
3388
+
3389
+ According to the grandwizards of Ruby, locking and unlocking of the global
3390
+ interpreter lock are apparently too powerful a concept for a mere mortal to
3391
+ wield (although redefining what + and - do to numbers is totally cool).
3392
+ And so it came to pass that the only acceptable way to release the global
3393
+ interpreter lock is through a convoluted callback system that thakes a
3394
+ function pointer. While the grandwizard of libev foresaw this sort of scenario,
3395
+ he too attempted to place an API with callbacks on it, one that runs before
3396
+ the system call, and one that runs immediately after.
3397
+
3398
+ And so it came to pass that trying to wrap everything up in callbacks created
3399
+ two incompatible APIs, Ruby's which releases the global interpreter lock and
3400
+ reacquires it when the callback returns, and libev's, which wants two
3401
+ callbacks, one which runs before the polling operation starts, and one which
3402
+ runs after it finishes.
3403
+
3404
+ These two systems are incompatible as they both want to use callbacks to
3405
+ solve the same problem, however libev wants to use before/after callbacks,
3406
+ and Ruby wants to use an "around" callback. This presents a significant
3407
+ problem as these two patterns of callbacks are diametrical opposites of each
3408
+ other and thus cannot be composed.
3409
+
3410
+ And thus we are left with no choice but to patch the internals of libev in
3411
+ order to release a mutex at just the precise moment.
3412
+
3413
+ Let this be a lesson to the all: CALLBACKS FUCKING BLOW
3414
+
3415
+ #######################################################################
3416
+ */
3417
+
3418
+ /*
3419
+ simulate to rb_thread_call_without_gvl using rb_theread_blocking_region.
3420
+ https://github.com/brianmario/mysql2/blob/master/ext/mysql2/client.h#L8
3421
+ */
3422
+
3423
+ #ifndef HAVE_RB_THREAD_CALL_WITHOUT_GVL
3424
+ #ifdef HAVE_RB_THREAD_BLOCKING_REGION
3425
+
3426
+ #define rb_thread_call_without_gvl(func, data1, ubf, data2) \
3427
+ rb_thread_blocking_region((rb_blocking_function_t *)func, data1, ubf, data2)
3428
+
3429
+ #endif
3430
+ #endif
3431
+
3432
+ #if defined(HAVE_RB_THREAD_BLOCKING_REGION) || defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
3433
+ poll_args[0] = (void *)loop;
3434
+ poll_args[1] = (void *)&waittime;
3435
+ rb_thread_call_without_gvl(ev_backend_poll, (void *)&poll_args, RUBY_UBF_IO, 0);
3436
+ #else
3360
3437
  backend_poll (EV_A_ waittime);
3438
+ #endif
3439
+ /*
3440
+ ############################# END PATCHERY ############################
3441
+ */
3442
+
3361
3443
  assert ((loop_done = EVBREAK_CANCEL, 1)); /* assert for side effect */
3362
3444
 
3363
3445
  pipe_write_wanted = 0; /* just an optimisation, no fence needed */
@@ -67,6 +67,53 @@
67
67
 
68
68
  #include <string.h>
69
69
 
70
+ #ifdef _WIN32
71
+ /*
72
+ ########## COOLIO PATCHERY HO! ##########
73
+
74
+ Ruby undefs FD_* utilities for own implementation.
75
+ It converts fd argument into socket handle internally on Windows,
76
+ so libev should not use Ruby's FD_* utilities.
77
+
78
+ Following FD_* utilities come from MinGW.
79
+ RubyInstaller is built by MinGW so this should work.
80
+ */
81
+ int PASCAL __WSAFDIsSet(SOCKET,fd_set*);
82
+ #define EV_WIN_FD_CLR(fd,set) do { u_int __i;\
83
+ for (__i = 0; __i < ((fd_set *)(set))->fd_count ; __i++) {\
84
+ if (((fd_set *)(set))->fd_array[__i] == (fd)) {\
85
+ while (__i < ((fd_set *)(set))->fd_count-1) {\
86
+ ((fd_set*)(set))->fd_array[__i] = ((fd_set*)(set))->fd_array[__i+1];\
87
+ __i++;\
88
+ }\
89
+ ((fd_set*)(set))->fd_count--;\
90
+ break;\
91
+ }\
92
+ }\
93
+ } while (0)
94
+ #define EV_WIN_FD_SET(fd, set) do { u_int __i;\
95
+ for (__i = 0; __i < ((fd_set *)(set))->fd_count ; __i++) {\
96
+ if (((fd_set *)(set))->fd_array[__i] == (fd)) {\
97
+ break;\
98
+ }\
99
+ }\
100
+ if (__i == ((fd_set *)(set))->fd_count) {\
101
+ if (((fd_set *)(set))->fd_count < FD_SETSIZE) {\
102
+ ((fd_set *)(set))->fd_array[__i] = (fd);\
103
+ ((fd_set *)(set))->fd_count++;\
104
+ }\
105
+ }\
106
+ } while(0)
107
+ #define EV_WIN_FD_ZERO(set) (((fd_set *)(set))->fd_count=0)
108
+ #define EV_WIN_FD_ISSET(fd, set) __WSAFDIsSet((SOCKET)(fd), (fd_set *)(set))
109
+ /* ######################################## */
110
+ #else
111
+ #define EV_WIN_FD_CLR FD_CLR
112
+ #define EV_WIN_FD_SET FD_SET
113
+ #define EV_WIN_FD_ZERO FD_ZERO
114
+ #define EV_WIN_FD_ISSET FD_ISSET
115
+ #endif
116
+
70
117
  static void
71
118
  select_modify (EV_P_ int fd, int oev, int nev)
72
119
  {
@@ -91,17 +138,17 @@ select_modify (EV_P_ int fd, int oev, int nev)
91
138
  if ((oev ^ nev) & EV_READ)
92
139
  #endif
93
140
  if (nev & EV_READ)
94
- FD_SET (handle, (fd_set *)vec_ri);
141
+ EV_WIN_FD_SET (handle, (fd_set *)vec_ri);
95
142
  else
96
- FD_CLR (handle, (fd_set *)vec_ri);
143
+ EV_WIN_FD_CLR (handle, (fd_set *)vec_ri);
97
144
 
98
145
  #if EV_SELECT_IS_WINSOCKET
99
146
  if ((oev ^ nev) & EV_WRITE)
100
147
  #endif
101
148
  if (nev & EV_WRITE)
102
- FD_SET (handle, (fd_set *)vec_wi);
149
+ EV_WIN_FD_SET (handle, (fd_set *)vec_wi);
103
150
  else
104
- FD_CLR (handle, (fd_set *)vec_wi);
151
+ EV_WIN_FD_CLR (handle, (fd_set *)vec_wi);
105
152
 
106
153
  #else
107
154
 
@@ -136,6 +183,8 @@ select_modify (EV_P_ int fd, int oev, int nev)
136
183
  }
137
184
  }
138
185
 
186
+ #undef socket
187
+
139
188
  static void
140
189
  select_poll (EV_P_ ev_tstamp timeout)
141
190
  {
@@ -230,10 +279,10 @@ select_poll (EV_P_ ev_tstamp timeout)
230
279
  int handle = fd;
231
280
  #endif
232
281
 
233
- if (FD_ISSET (handle, (fd_set *)vec_ro)) events |= EV_READ;
234
- if (FD_ISSET (handle, (fd_set *)vec_wo)) events |= EV_WRITE;
282
+ if (EV_WIN_FD_ISSET (handle, (fd_set *)vec_ro)) events |= EV_READ;
283
+ if (EV_WIN_FD_ISSET (handle, (fd_set *)vec_wo)) events |= EV_WRITE;
235
284
  #ifdef _WIN32
236
- if (FD_ISSET (handle, (fd_set *)vec_eo)) events |= EV_WRITE;
285
+ if (EV_WIN_FD_ISSET (handle, (fd_set *)vec_eo)) events |= EV_WRITE;
237
286
  #endif
238
287
 
239
288
  if (expect_true (events))
@@ -279,9 +328,9 @@ select_init (EV_P_ int flags)
279
328
  backend_poll = select_poll;
280
329
 
281
330
  #if EV_SELECT_USE_FD_SET
282
- vec_ri = ev_malloc (sizeof (fd_set)); FD_ZERO ((fd_set *)vec_ri);
331
+ vec_ri = ev_malloc (sizeof (fd_set)); EV_WIN_FD_ZERO ((fd_set *)vec_ri);
283
332
  vec_ro = ev_malloc (sizeof (fd_set));
284
- vec_wi = ev_malloc (sizeof (fd_set)); FD_ZERO ((fd_set *)vec_wi);
333
+ vec_wi = ev_malloc (sizeof (fd_set)); EV_WIN_FD_ZERO ((fd_set *)vec_wi);
285
334
  vec_wo = ev_malloc (sizeof (fd_set));
286
335
  #ifdef _WIN32
287
336
  vec_eo = ev_malloc (sizeof (fd_set));
@@ -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 */
@@ -21,6 +21,10 @@ module Coolio
21
21
  @listen_socket.fileno
22
22
  end
23
23
 
24
+ def listen(backlog)
25
+ @listen_socket.listen(backlog)
26
+ end
27
+
24
28
  # Close the listener
25
29
  def close
26
30
  detach if attached?
@@ -53,9 +57,9 @@ module Coolio
53
57
  end
54
58
  end
55
59
 
56
- class TCPListener < Listener
57
- DEFAULT_BACKLOG = 1024
60
+ DEFAULT_BACKLOG = 1024
58
61
 
62
+ class TCPListener < Listener
59
63
  # Create a new Coolio::TCPListener on the specified address and port.
60
64
  # Accepts the following options:
61
65
  #
@@ -87,7 +91,9 @@ module Coolio
87
91
  # Optionally, it can also take anyn existing UNIXServer object
88
92
  # and create a Coolio::UNIXListener out of it.
89
93
  def initialize(*args)
90
- super(::UNIXServer === args.first ? args.first : ::UNIXServer.new(*args))
94
+ s = ::UNIXServer === args.first ? args.first : ::UNIXServer.new(*args)
95
+ s.instance_eval { listen(DEFAULT_BACKLOG) }
96
+ super(s)
91
97
  end
92
98
  end
93
99
  end
@@ -88,12 +88,12 @@ module Coolio
88
88
  # event callbacks to watchers until all watchers associated with
89
89
  # the loop have been disabled or detached. The loop may be
90
90
  # explicitly stopped by calling the stop method on the loop object.
91
- def run
91
+ def run(timeout = nil)
92
92
  raise RuntimeError, "no watchers for this loop" if @watchers.empty?
93
93
 
94
94
  @running = true
95
95
  while @running and not @active_watchers.zero?
96
- run_once
96
+ run_once(timeout)
97
97
  end
98
98
  @running = false
99
99
  end
@@ -56,7 +56,7 @@ module Coolio
56
56
  raise ArgumentError, "port must be an integer" if nil == port
57
57
  ::TCPServer.new(host, port)
58
58
  end
59
- listen_socket.instance_eval { listen(1024) } # Change listen backlog to 1024
59
+ listen_socket.instance_eval { listen(DEFAULT_BACKLOG) } # Change listen backlog to 1024
60
60
  super(listen_socket, klass, *args, &block)
61
61
  end
62
62
  end
@@ -68,6 +68,7 @@ module Coolio
68
68
  class UNIXServer < Server
69
69
  def initialize(path, klass = UNIXSocket, *args, &block)
70
70
  s = ::UNIXServer === path ? path : ::UNIXServer.new(path)
71
+ s.instance_eval { listen(DEFAULT_BACKLOG) }
71
72
  super(s, klass, *args, &block)
72
73
  end
73
74
  end
@@ -1,5 +1,5 @@
1
1
  module Coolio
2
- VERSION = "1.2.0"
2
+ VERSION = "1.2.3"
3
3
 
4
4
  def self.version; VERSION; end
5
5
  end
@@ -36,4 +36,4 @@ describe "DNS" do
36
36
  @loop.run
37
37
  end.should raise_error(WontResolve)
38
38
  end
39
- end
39
+ end
@@ -4,6 +4,13 @@ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
4
4
  require 'rspec'
5
5
  require 'cool.io'
6
6
 
7
+ def unused_port
8
+ s = TCPServer.open(0)
9
+ port = s.addr[1]
10
+ s.close
11
+ port
12
+ end
13
+
7
14
  RSpec.configure do |c|
8
15
  if RUBY_PLATFORM =~ /mingw|win32/
9
16
  $stderr.puts "Skip some specs on Windows"
@@ -0,0 +1,132 @@
1
+ require File.expand_path('../spec_helper', __FILE__)
2
+
3
+ TIMEOUT = 0.010
4
+ HOST = '127.0.0.1'
5
+ PORT = unused_port
6
+
7
+ def send_data(data)
8
+ io = TCPSocket.new('127.0.0.1', PORT)
9
+ begin
10
+ io.write data
11
+ ensure
12
+ io.close
13
+ end
14
+ end
15
+
16
+ class MyConnection < Coolio::Socket
17
+ attr_accessor :data, :connected, :closed
18
+
19
+ def initialize(io, on_message)
20
+ super(io)
21
+ @on_message = on_message
22
+ end
23
+
24
+ def on_connect
25
+ @connected = true
26
+ end
27
+
28
+ def on_close
29
+ @closed = true
30
+ end
31
+
32
+ def on_read(data)
33
+ @on_message.call(data)
34
+ end
35
+ end
36
+
37
+ @data = ""
38
+ def on_message(data)
39
+ @data = data
40
+ end
41
+
42
+ def test_run(data = nil)
43
+ reactor = Coolio::Loop.new
44
+ server = Cool.io::TCPServer.new(HOST, PORT, MyConnection, method(:on_message))
45
+ reactor.attach(server)
46
+ thread = Thread.new { reactor.run }
47
+ send_data(data) if data
48
+ sleep TIMEOUT
49
+ reactor.stop
50
+ server.detach
51
+ send_data('') # to leave from blocking loop
52
+ thread.join
53
+ @data
54
+ ensure
55
+ server.close
56
+ end
57
+
58
+ def test_run_once(data = nil)
59
+ reactor = Coolio::Loop.new
60
+ server = Cool.io::TCPServer.new(HOST, PORT, MyConnection, method(:on_message))
61
+ reactor.attach(server)
62
+ thread = Thread.new do
63
+ reactor.run_once # on_connect
64
+ reactor.run_once # on_read
65
+ end
66
+ send_data(data) if data
67
+ thread.join
68
+ server.detach
69
+ @data
70
+ ensure
71
+ server.close
72
+ end
73
+
74
+ def test_run_once_timeout(timeout = TIMEOUT)
75
+ reactor = Coolio::Loop.new
76
+ server = Cool.io::TCPServer.new(HOST, PORT, MyConnection, method(:on_message))
77
+ reactor.attach(server)
78
+ running = true
79
+ thread = Thread.new { reactor.run_once(timeout) }
80
+ sleep timeout
81
+ server.detach
82
+ thread.join
83
+ @data
84
+ ensure
85
+ server.close
86
+ end
87
+
88
+ def test_run_timeout(data = nil, timeout = TIMEOUT)
89
+ reactor = Coolio::Loop.new
90
+ server = Cool.io::TCPServer.new(HOST, PORT, MyConnection, method(:on_message))
91
+ reactor.attach(server)
92
+ running = true
93
+ thread = Thread.new do
94
+ while running and reactor.has_active_watchers?
95
+ reactor.run_once(timeout)
96
+ end
97
+ end
98
+ send_data(data) if data
99
+ sleep timeout
100
+ server.detach
101
+ running = false # another send is not required
102
+ thread.join
103
+ @data
104
+ ensure
105
+ server.close
106
+ end
107
+
108
+ # This test should work on Windows
109
+ describe Coolio::TCPServer, :env => :win do
110
+
111
+ it '#run' do
112
+ test_run("hello").should == "hello"
113
+ end
114
+
115
+ it '#run_once' do
116
+ test_run_once("hello").should == "hello"
117
+ end
118
+
119
+ it '#run_once(timeout)' do
120
+ test_run_once_timeout # should not block
121
+ end
122
+
123
+ it '#run_once(-timeout)' do
124
+ expect { test_run_once_timeout(-0.1) }.to raise_error(ArgumentError)
125
+ end
126
+
127
+ it '#run(timeout)' do
128
+ test_run_timeout("hello").should == "hello"
129
+ end
130
+
131
+ end
132
+
@@ -11,13 +11,15 @@ describe Cool.io::UNIXServer, :env => :win do
11
11
 
12
12
  it "creates a new Cool.io::UNIXServer" do
13
13
  listener = Cool.io::UNIXListener.new(@tmp.path)
14
+ listener.listen(24)
14
15
  File.socket?(@tmp.path).should == true
15
16
  end
16
17
 
17
18
  it "builds off an existing ::UNIXServer" do
18
19
  unix_server = ::UNIXServer.new(@tmp.path)
19
20
  File.socket?(@tmp.path).should == true
20
- listener = Cool.io::UNIXServer.new(unix_server)
21
+ listener = Cool.io::UNIXServer.new(unix_server, Coolio::UNIXSocket)
22
+ listener.listen(24)
21
23
  File.socket?(@tmp.path).should == true
22
24
  listener.fileno.should == unix_server.fileno
23
25
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cool.io
3
3
  version: !ruby/object:Gem::Version
4
- hash: 31
4
+ hash: 25
5
5
  prerelease:
6
6
  segments:
7
7
  - 1
8
8
  - 2
9
- - 0
10
- version: 1.2.0
9
+ - 3
10
+ version: 1.2.3
11
11
  platform: x86-mingw32
12
12
  authors:
13
13
  - Tony Arcieri
@@ -16,7 +16,7 @@ autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
18
 
19
- date: 2013-05-27 00:00:00 -07:00
19
+ date: 2014-04-26 00:00:00 -07:00
20
20
  default_executable:
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency
@@ -130,6 +130,7 @@ files:
130
130
  - ext/libev/ev_vars.h
131
131
  - ext/libev/ev_win32.c
132
132
  - ext/libev/ev_wrap.h
133
+ - ext/libev/ruby_gil.patch
133
134
  - ext/libev/test_libev_win32.c
134
135
  - lib/.gitignore
135
136
  - lib/cool.io.rb
@@ -153,6 +154,7 @@ files:
153
154
  - spec/dns_spec.rb
154
155
  - spec/spec_helper.rb
155
156
  - spec/stat_watcher_spec.rb
157
+ - spec/tcp_server_spec.rb
156
158
  - spec/timer_watcher_spec.rb
157
159
  - spec/unix_listener_spec.rb
158
160
  - spec/unix_server_spec.rb