cool.io 1.9.4 → 1.10.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f15a89de41069e6ccb37eb28befd01d601851ae1bb9ee2a5081e1c26e0bcc8da
4
- data.tar.gz: de69fe197c029bbb4438e2e1c751580712ecc1f1b8326803d2089b738d3f74cc
3
+ metadata.gz: 9494578f6a02a987a84805bca37919a7c0e63c4c585a7bdf8c68e18e38d21e8d
4
+ data.tar.gz: 0cae209afee54a8f6f59014afb71ee2357dabdc350cfc60dfe2a6212062dbf1e
5
5
  SHA512:
6
- metadata.gz: c88454501a2b042515b0571a729bac9a829ed7af72be377caf5ab6db08f90bb48adb1e19a69dee1b38eb1c77df9c084c88faa19d0077503c85a965e6c0c556ac
7
- data.tar.gz: 8fd2af4f085bbb253df79cb8c9c685df97c5dc233f4be4891cef796120e9b265676e6fa4fe7c39b0b3b77447c000e41cbb1bc66b8939869e8747ac270619c436
6
+ metadata.gz: ccb23719b38180b44cd6485901a35a15710daf77f4079a7fbdbc1d77fcfe86b75787d82c8cd8f455f5199a7f390125d6d62320eeff2785b2193c6fe5612c7653
7
+ data.tar.gz: 2ab5356bc8ab69fb93c6f3c6532ada9c67b63c774e7a952f20526b1bf3660fde681a3b480130deaf436212b4b0d77b51859fefcb9d4f0a6dc983b0a46de70f2c
@@ -17,6 +17,7 @@ jobs:
17
17
  os:
18
18
  - ubuntu
19
19
  - macos
20
+ - windows
20
21
 
21
22
  ruby:
22
23
  - "3.2"
@@ -0,0 +1,41 @@
1
+ name: Memcheck
2
+
3
+ on: [push, pull_request]
4
+
5
+ permissions:
6
+ contents: read
7
+
8
+ jobs:
9
+ test:
10
+ name: ${{matrix.ruby}} on ${{matrix.os}}
11
+ runs-on: ${{matrix.os}}-latest
12
+ continue-on-error: ${{matrix.experimental}}
13
+
14
+ strategy:
15
+ matrix:
16
+ os:
17
+ - ubuntu
18
+
19
+ ruby:
20
+ - "4.0"
21
+
22
+ experimental: [false]
23
+
24
+ include:
25
+ - os: ubuntu
26
+ ruby: head
27
+ experimental: true
28
+
29
+ steps:
30
+ - uses: actions/checkout@v6
31
+ - uses: ruby/setup-ruby@v1
32
+ with:
33
+ ruby-version: ${{matrix.ruby}}
34
+ bundler-cache: true
35
+ - name: Install Valgrind
36
+ run: |
37
+ sudo apt-get update
38
+ sudo apt-get install -y valgrind
39
+ - name: Run tests
40
+ timeout-minutes: 10
41
+ run: bundle exec rake spec:valgrind
data/Rakefile CHANGED
@@ -22,7 +22,7 @@ end
22
22
 
23
23
  require 'rake/extensiontask'
24
24
 
25
- spec = eval(File.read("cool.io.gemspec"))
25
+ spec = Gem::Specification.load("cool.io.gemspec")
26
26
 
27
27
  def configure_cross_compilation(ext)
28
28
  unless RUBY_PLATFORM =~ /mswin|mingw/
@@ -48,24 +48,12 @@ namespace :build do
48
48
  end
49
49
  end
50
50
 
51
- # adapted from http://flavoriffic.blogspot.com/2009/06/easily-valgrind-gdb-your-ruby-c.html
52
- def specs_command
53
- require "find"
54
- files = []
55
- Find.find("spec") do |f|
56
- files << f if File.basename(f) =~ /.*spec.*\.rb$/
57
- end
58
- cmdline = "#{RUBY} -I.:lib:ext:spec \
59
- -e '%w[#{files.join(' ')}].each { |f| require f }'"
60
- end
61
-
62
- namespace :spec do
63
- desc "run specs with valgrind"
64
- task :valgrind => :compile do
65
- system "valgrind --num-callers=15 \
66
- --partial-loads-ok=yes --undef-value-errors=no \
67
- --tool=memcheck --leak-check=yes --track-fds=yes \
68
- --show-reachable=yes #{specs_command}"
51
+ if RUBY_PLATFORM.include?('linux')
52
+ require 'ruby_memcheck'
53
+ require 'ruby_memcheck/rspec/rake_task'
54
+ RubyMemcheck.config(binary_name: 'cool.io_ext')
55
+ namespace :spec do
56
+ RubyMemcheck::RSpec::RakeTask.new(valgrind: :compile)
69
57
  end
70
58
  end
71
59
 
data/cool.io.gemspec CHANGED
@@ -1,6 +1,4 @@
1
- # -*- encoding: utf-8 -*-
2
- $:.push File.expand_path("../lib", __FILE__)
3
- require "cool.io/version"
1
+ require_relative "lib/cool.io/version"
4
2
 
5
3
  Gem::Specification.new do |s|
6
4
  s.name = "cool.io"
data/ext/cool.io/buffer.c CHANGED
@@ -15,6 +15,7 @@
15
15
  #include <unistd.h>
16
16
  #endif
17
17
  #include <errno.h>
18
+ #include <limits.h>
18
19
 
19
20
  /* 1 GiB maximum buffer size */
20
21
  #define MAX_BUFFER_SIZE 0x40000000
@@ -28,17 +29,22 @@
28
29
 
29
30
  /* Default number of bytes in each node's buffer. Should be >= MTU */
30
31
  #define DEFAULT_NODE_SIZE 16384
31
- static unsigned default_node_size = DEFAULT_NODE_SIZE;
32
+ static size_t default_node_size = DEFAULT_NODE_SIZE;
32
33
 
34
+ /*
35
+ * Byte counts are kept in size_t so the accounting cannot wrap while the
36
+ * buffer itself still holds the data. node_size is capped at MAX_BUFFER_SIZE,
37
+ * but size grows with whatever the buffer is asked to hold.
38
+ */
33
39
  struct buffer {
34
- unsigned size, node_size;
40
+ size_t size, node_size;
35
41
  struct buffer_node *head, *tail;
36
42
  struct buffer_node *pool_head, *pool_tail;
37
43
 
38
44
  };
39
45
 
40
46
  struct buffer_node {
41
- unsigned start, end;
47
+ size_t start, end;
42
48
  struct buffer_node *next;
43
49
  unsigned char data[0];
44
50
  };
@@ -68,13 +74,13 @@ static struct buffer *buffer_init(struct buffer *);
68
74
  static void buffer_clear(struct buffer * buf);
69
75
  static void buffer_free(struct buffer * buf);
70
76
  static void buffer_free_pool(struct buffer * buf);
71
- static void buffer_prepend(struct buffer * buf, char *str, unsigned len);
72
- static void buffer_append(struct buffer * buf, char *str, unsigned len);
73
- static void buffer_read(struct buffer * buf, char *str, unsigned len);
77
+ static void buffer_prepend(struct buffer * buf, char *str, size_t len);
78
+ static void buffer_append(struct buffer * buf, char *str, size_t len);
79
+ static void buffer_read(struct buffer * buf, char *str, size_t len);
74
80
  static int buffer_read_frame(struct buffer * buf, VALUE str, char frame_mark);
75
- static void buffer_copy(struct buffer * buf, char *str, unsigned len);
76
- static int buffer_read_from(struct buffer * buf, int fd);
77
- static int buffer_write_to(struct buffer * buf, int fd);
81
+ static void buffer_copy(struct buffer * buf, char *str, size_t len);
82
+ static ssize_t buffer_read_from(struct buffer * buf, int fd);
83
+ static ssize_t buffer_write_to(struct buffer * buf, int fd);
78
84
 
79
85
  /*
80
86
  * High-performance I/O buffer intended for use in non-blocking programs
@@ -145,21 +151,21 @@ Coolio_Buffer_free(void * buf)
145
151
 
146
152
  /**
147
153
  * call-seq:
148
- * Coolio::Buffer.default_node_size -> 4096
154
+ * Coolio::Buffer.default_node_size -> 16384
149
155
  *
150
156
  * Retrieves the current value of the default node size.
151
157
  */
152
158
  static VALUE
153
159
  Coolio_Buffer_default_node_size(VALUE klass)
154
160
  {
155
- return UINT2NUM(default_node_size);
161
+ return SIZET2NUM(default_node_size);
156
162
  }
157
163
 
158
164
  /*
159
165
  * safely converts node sizes from Ruby numerics to C and raising
160
166
  * ArgumentError or RangeError on invalid sizes
161
167
  */
162
- static unsigned
168
+ static size_t
163
169
  convert_node_size(VALUE size)
164
170
  {
165
171
  if (
@@ -168,7 +174,7 @@ convert_node_size(VALUE size)
168
174
  )
169
175
  rb_raise(rb_eArgError, "invalid buffer size");
170
176
 
171
- return (unsigned) NUM2INT(size);
177
+ return NUM2SIZET(size);
172
178
  }
173
179
 
174
180
  /**
@@ -241,7 +247,7 @@ Coolio_Buffer_size(VALUE self)
241
247
  struct buffer *buf;
242
248
  TypedData_Get_Struct(self, struct buffer, &Coolio_Buffer_type, buf);
243
249
 
244
- return INT2NUM(buf->size);
250
+ return SIZET2NUM(buf->size);
245
251
  }
246
252
 
247
253
  /**
@@ -298,37 +304,47 @@ Coolio_Buffer_prepend(VALUE self, VALUE data)
298
304
 
299
305
  /**
300
306
  * call-seq:
301
- * Coolio::Buffer#read(length = nil) -> String
307
+ * Coolio::Buffer#read -> String
308
+ * Coolio::Buffer#read(length) -> String
302
309
  *
303
- * Read the specified abount of data from the buffer. If no value
304
- * is given the entire contents of the buffer are returned. Any data
305
- * read from the buffer is cleared.
306
- * The given length must be greater than 0 or an exception would raise.
307
- * If the buffer size is zero then an empty string is returned (regardless
308
- * the given length).
310
+ * Read data from the buffer. Any data read from the buffer is cleared.
311
+ * With no argument, the entire contents of the buffer are returned. With
312
+ * length, at most length bytes are returned.
313
+ * At most LONG_MAX bytes are returned per call; on platforms where a String
314
+ * cannot hold the whole buffer the remainder is kept for later reads.
315
+ * length is converted with to_int, so nil raises TypeError. A length less
316
+ * than 1 raises ArgumentError, even when the buffer is empty.
317
+ * Otherwise, if the buffer is empty, an empty string is returned.
309
318
  */
310
319
  static VALUE
311
320
  Coolio_Buffer_read(int argc, VALUE * argv, VALUE self)
312
321
  {
313
322
  VALUE length_obj, str;
314
- int length;
323
+ size_t length;
315
324
  struct buffer *buf;
316
325
 
317
326
  TypedData_Get_Struct(self, struct buffer, &Coolio_Buffer_type, buf);
318
327
 
319
328
  if (rb_scan_args(argc, argv, "01", &length_obj) == 1) {
320
- length = NUM2INT(length_obj);
321
- if(length < 1)
329
+ /* Clamp before converting to C: long is only 32 bits on 64-bit Windows,
330
+ * and a request larger than the buffer never needs to fit in C at all.
331
+ * Keep the integer coercion and ArgumentError for non-positive lengths. */
332
+ VALUE requested = rb_to_int(length_obj);
333
+ if(rb_funcall(requested, rb_intern("<"), 1, INT2NUM(1)) == Qtrue)
322
334
  rb_raise(rb_eArgError, "length must be greater than zero");
323
- if(length > buf->size)
324
- length = buf->size;
335
+ length = buf->size;
336
+ if(rb_funcall(requested, rb_intern("<"), 1, SIZET2NUM(length)) == Qtrue)
337
+ length = NUM2SIZET(requested);
325
338
  } else
326
339
  length = buf->size;
327
340
 
328
341
  if(buf->size == 0)
329
342
  return rb_str_new2("");
330
343
 
331
- str = rb_str_new(0, length);
344
+ if(length > (size_t) LONG_MAX)
345
+ length = (size_t) LONG_MAX;
346
+
347
+ str = rb_str_new(0, (long) length);
332
348
  buffer_read(buf, RSTRING_PTR(str), length);
333
349
 
334
350
  return str;
@@ -352,6 +368,9 @@ Coolio_Buffer_read_frame(VALUE self, VALUE data, VALUE mark)
352
368
 
353
369
  TypedData_Get_Struct(self, struct buffer, &Coolio_Buffer_type, buf);
354
370
 
371
+ StringValue(data);
372
+ rb_str_modify(data);
373
+
355
374
  if (buffer_read_frame(buf, data, mark_c)) {
356
375
  return Qtrue;
357
376
  } else {
@@ -373,7 +392,10 @@ Coolio_Buffer_to_str(VALUE self)
373
392
 
374
393
  TypedData_Get_Struct(self, struct buffer, &Coolio_Buffer_type, buf);
375
394
 
376
- str = rb_str_new(0, buf->size);
395
+ if(buf->size > (size_t) LONG_MAX)
396
+ rb_raise(rb_eRangeError, "buffer is too large to convert to a String");
397
+
398
+ str = rb_str_new(0, (long) buf->size);
377
399
  buffer_copy(buf, RSTRING_PTR(str), buf->size);
378
400
 
379
401
  return str;
@@ -391,7 +413,7 @@ static VALUE
391
413
  Coolio_Buffer_read_from(VALUE self, VALUE io)
392
414
  {
393
415
  struct buffer *buf;
394
- int ret;
416
+ ssize_t ret;
395
417
  #if defined(HAVE_RB_IO_T) || defined(HAVE_RB_IO_DESCRIPTOR)
396
418
  rb_io_t *fptr;
397
419
  #else
@@ -408,7 +430,7 @@ Coolio_Buffer_read_from(VALUE self, VALUE io)
408
430
  #else
409
431
  ret = buffer_read_from(buf, FPTR_TO_FD(fptr));
410
432
  #endif
411
- return ret == -1 ? Qnil : INT2NUM(ret);
433
+ return ret == -1 ? Qnil : SSIZET2NUM(ret);
412
434
  }
413
435
 
414
436
  /**
@@ -435,9 +457,9 @@ Coolio_Buffer_write_to(VALUE self, VALUE io)
435
457
  rb_io_set_nonblock(fptr);
436
458
 
437
459
  #ifdef HAVE_RB_IO_DESCRIPTOR
438
- return INT2NUM(buffer_write_to(buf, rb_io_descriptor(io)));
460
+ return SSIZET2NUM(buffer_write_to(buf, rb_io_descriptor(io)));
439
461
  #else
440
- return INT2NUM(buffer_write_to(buf, FPTR_TO_FD(fptr)));
462
+ return SSIZET2NUM(buffer_write_to(buf, FPTR_TO_FD(fptr)));
441
463
  #endif
442
464
  }
443
465
 
@@ -462,10 +484,13 @@ static void
462
484
  buffer_clear(struct buffer * buf)
463
485
  {
464
486
  /* Move everything into the buffer pool */
465
- if (!buf->pool_tail) {
466
- buf->pool_head = buf->pool_tail = buf->head;
467
- } else {
468
- buf->pool_tail->next = buf->head;
487
+ if (buf->head) {
488
+ if (!buf->pool_tail) {
489
+ buf->pool_head = buf->head;
490
+ } else {
491
+ buf->pool_tail->next = buf->head;
492
+ }
493
+ buf->pool_tail = buf->tail;
469
494
  }
470
495
 
471
496
  buf->head = buf->tail = 0;
@@ -535,7 +560,7 @@ buffer_node_free(struct buffer * buf, struct buffer_node * node)
535
560
 
536
561
  /* Prepend data to the front of the buffer */
537
562
  static void
538
- buffer_prepend(struct buffer * buf, char *str, unsigned len)
563
+ buffer_prepend(struct buffer * buf, char *str, size_t len)
539
564
  {
540
565
  struct buffer_node *node, *tmp;
541
566
  buf->size += len;
@@ -576,9 +601,9 @@ buffer_prepend(struct buffer * buf, char *str, unsigned len)
576
601
 
577
602
  /* Append data to the front of the buffer */
578
603
  static void
579
- buffer_append(struct buffer * buf, char *str, unsigned len)
604
+ buffer_append(struct buffer * buf, char *str, size_t len)
580
605
  {
581
- unsigned nbytes;
606
+ size_t nbytes;
582
607
  buf->size += len;
583
608
 
584
609
  /* If it fits in the remaining space in the tail */
@@ -613,9 +638,9 @@ buffer_append(struct buffer * buf, char *str, unsigned len)
613
638
 
614
639
  /* Read data from the buffer (and clear what we've read) */
615
640
  static void
616
- buffer_read(struct buffer * buf, char *str, unsigned len)
641
+ buffer_read(struct buffer * buf, char *str, size_t len)
617
642
  {
618
- unsigned nbytes;
643
+ size_t nbytes;
619
644
  struct buffer_node *tmp;
620
645
 
621
646
  while (buf->size > 0 && len > 0) {
@@ -649,7 +674,7 @@ buffer_read(struct buffer * buf, char *str, unsigned len)
649
674
  static int
650
675
  buffer_read_frame(struct buffer * buf, VALUE str, char frame_mark)
651
676
  {
652
- unsigned nbytes = 0;
677
+ size_t nbytes = 0;
653
678
  struct buffer_node *tmp;
654
679
 
655
680
  while (buf->size > 0) {
@@ -664,7 +689,7 @@ buffer_read_frame(struct buffer * buf, VALUE str, char frame_mark)
664
689
  }
665
690
 
666
691
  /* Copy less than everything if we found a frame byte */
667
- rb_str_cat(str, s, nbytes);
692
+ rb_str_cat(str, s, (long) nbytes);
668
693
 
669
694
  /* Fixup the buffer pointers to indicate the bytes were consumed */
670
695
  head->start += nbytes;
@@ -688,9 +713,9 @@ buffer_read_frame(struct buffer * buf, VALUE str, char frame_mark)
688
713
 
689
714
  /* Copy data from the buffer without clearing it */
690
715
  static void
691
- buffer_copy(struct buffer * buf, char *str, unsigned len)
716
+ buffer_copy(struct buffer * buf, char *str, size_t len)
692
717
  {
693
- unsigned nbytes;
718
+ size_t nbytes;
694
719
  struct buffer_node *node;
695
720
 
696
721
  node = buf->head;
@@ -709,10 +734,10 @@ buffer_copy(struct buffer * buf, char *str, unsigned len)
709
734
  }
710
735
 
711
736
  /* Write data from the buffer to a file descriptor */
712
- static int
737
+ static ssize_t
713
738
  buffer_write_to(struct buffer * buf, int fd)
714
739
  {
715
- int bytes_written, total_bytes_written = 0;
740
+ ssize_t bytes_written, total_bytes_written = 0;
716
741
  struct buffer_node *tmp;
717
742
 
718
743
  while (buf->head) {
@@ -730,7 +755,7 @@ buffer_write_to(struct buffer * buf, int fd)
730
755
  buf->size -= bytes_written;
731
756
 
732
757
  /* If the write blocked... */
733
- if (bytes_written < buf->head->end - buf->head->start) {
758
+ if ((size_t) bytes_written < buf->head->end - buf->head->start) {
734
759
  buf->head->start += bytes_written;
735
760
  return total_bytes_written;
736
761
  }
@@ -748,11 +773,11 @@ buffer_write_to(struct buffer * buf, int fd)
748
773
 
749
774
  /* Read data from a file descriptor to a buffer */
750
775
  /* Append data to the front of the buffer */
751
- static int
776
+ static ssize_t
752
777
  buffer_read_from(struct buffer * buf, int fd)
753
778
  {
754
- int bytes_read, total_bytes_read = 0;
755
- unsigned nbytes;
779
+ ssize_t bytes_read, total_bytes_read = 0;
780
+ size_t nbytes;
756
781
 
757
782
  /* Empty list needs initialized */
758
783
  if (!buf->head) {
@@ -782,7 +807,7 @@ buffer_read_from(struct buffer * buf, int fd)
782
807
  buf->tail->next = buffer_node_new(buf);
783
808
  buf->tail = buf->tail->next;
784
809
  }
785
- } while (bytes_read == nbytes);
810
+ } while ((size_t) bytes_read == nbytes);
786
811
 
787
812
  return total_bytes_read;
788
813
  }
@@ -55,6 +55,12 @@ struct Coolio_Watcher
55
55
  int enabled;
56
56
  VALUE loop;
57
57
 
58
+ /* Stable copy of the watched path for ev_stat watchers. libev retains the
59
+ * path pointer passed to ev_stat_init() for the lifetime of the watcher, so
60
+ * it must not point into a Ruby String whose buffer GC.compact may relocate.
61
+ * NULL for watcher types that don't use it. */
62
+ char *stat_path;
63
+
58
64
  void (*dispatch_callback)(VALUE self, int revents);
59
65
  };
60
66
 
@@ -4,6 +4,7 @@
4
4
  #define EV_SELECT_IS_WINSOCKET 1 /* configure libev for windows select */
5
5
  #define EV_USE_MONOTONIC 0
6
6
  #define EV_USE_REALTIME 0
7
+ #define EV_WIN32_STAT_CODEPAGE CP_UTF8 /* Ruby paths are UTF-8. */
7
8
  #endif
8
9
 
9
10
  #include "../libev/ev.h"
@@ -11,7 +11,6 @@ have_func('rb_thread_alone')
11
11
  have_func('rb_str_set_len')
12
12
  have_library('rt', 'clock_gettime')
13
13
 
14
- have_func("rb_io_descriptor")
15
14
  have_library("c", "main")
16
15
  if have_macro("HAVE_RB_IO_T", "ruby/io.h")
17
16
  have_struct_member("rb_io_t", "fd", "ruby/io.h")
@@ -111,7 +111,24 @@ static VALUE Coolio_IOWatcher_initialize(int argc, VALUE *argv, VALUE self)
111
111
  */
112
112
  static VALUE Coolio_IOWatcher_attach(VALUE self, VALUE loop)
113
113
  {
114
- Watcher_Attach(io, Coolio_IOWatcher_detach, self, loop);
114
+ struct Coolio_Watcher *watcher_data;
115
+ struct Coolio_Loop *loop_data;
116
+
117
+ if(!rb_obj_is_kind_of(loop, cCoolio_Loop))
118
+ rb_raise(rb_eArgError, "expected loop to be an instance of Coolio::Loop, not %s", RSTRING_PTR(rb_inspect(loop)));
119
+
120
+ watcher_data = Coolio_Watcher_ptr(self);
121
+ loop_data = Coolio_Loop_ptr(loop);
122
+
123
+ if(watcher_data->loop != Qnil)
124
+ rb_funcall(self, rb_intern("detach"), 0);
125
+
126
+ /* A new loop must resolve the Windows socket handle for this fd again. */
127
+ ev_io_set(&watcher_data->event_types.ev_io,
128
+ watcher_data->event_types.ev_io.fd, watcher_data->event_types.ev_io.events);
129
+ watcher_data->loop = loop;
130
+ ev_io_start(loop_data->ev_loop, &watcher_data->event_types.ev_io);
131
+ rb_call_super(1, &loop);
115
132
 
116
133
  return self;
117
134
  }
data/ext/cool.io/loop.c CHANGED
@@ -81,10 +81,9 @@ static void Coolio_Loop_free(void *data)
81
81
  {
82
82
  struct Coolio_Loop *loop = data;
83
83
 
84
- if(!loop->ev_loop)
85
- return;
86
-
87
- ev_loop_destroy(loop->ev_loop);
84
+ if(loop->ev_loop) {
85
+ ev_loop_destroy(loop->ev_loop);
86
+ }
88
87
 
89
88
  xfree(loop->eventbuf);
90
89
  xfree(loop);
@@ -100,6 +99,9 @@ static VALUE Coolio_Loop_ev_loop_new(VALUE self, VALUE flags)
100
99
  rb_raise(rb_eRuntimeError, "loop already initialized");
101
100
 
102
101
  loop_data->ev_loop = ev_loop_new(NUM2INT(flags));
102
+ if(!loop_data->ev_loop) {
103
+ rb_raise(rb_eRuntimeError, "unable to initialize the event loop (no usable backend)");
104
+ }
103
105
 
104
106
  return Qnil;
105
107
  }
@@ -110,8 +112,7 @@ void Coolio_Loop_process_event(VALUE watcher, int revents)
110
112
  struct Coolio_Loop *loop_data;
111
113
  struct Coolio_Watcher *watcher_data;
112
114
 
113
- /* The Global VM lock isn't held right now, but hopefully
114
- * we can still do this safely */
115
+ /* The Global VM Lock is held here, see the explanation below */
115
116
  watcher_data = Coolio_Watcher_ptr(watcher);
116
117
 
117
118
  if (watcher_data->enabled == 0) {
@@ -126,13 +127,14 @@ void Coolio_Loop_process_event(VALUE watcher, int revents)
126
127
  *
127
128
  * Our call path up to here looks a little something like:
128
129
  *
129
- * -> release GVL -> event syscall -> libev callback
130
- * (GVL = Global VM Lock) ^^^ You are here
130
+ * -> release GVL -> event syscall -> reacquire GVL -> libev callback
131
+ * (GVL = Global VM Lock) ^^^ You are here
131
132
  *
132
- * We released the GVL in the Coolio_Loop_run_once() function
133
- * so other Ruby threads can run while we make a blocking
134
- * system call (one of epoll, kqueue, port, poll, or select,
135
- * depending on the platform).
133
+ * libev is patched (see ev.c) to release the GVL around the blocking
134
+ * system call (one of epoll, kqueue, port, poll, or select, depending
135
+ * on the platform) so other Ruby threads can run while we wait there.
136
+ * Only that call runs without the GVL: libev invokes the events it
137
+ * collected after the call has returned, so we hold the GVL here.
136
138
  *
137
139
  * More specifically, this is a libev callback abstraction
138
140
  * called from a real libev callback in every watcher,
@@ -150,19 +152,19 @@ void Coolio_Loop_process_event(VALUE watcher, int revents)
150
152
  * event fired, why the hell is it telling the loop? Why
151
153
  * doesn't it just rb_funcall() the appropriate callback?
152
154
  *
153
- * Well, the problem is the Global VM Lock isn't held right
154
- * now, so we can't rb_funcall() anything. In order to get
155
- * it back we have to:
155
+ * Because Ruby code doesn't run from inside libev's own event
156
+ * invocation. Instead:
156
157
  *
157
- * stash event and return -> acquire GVL -> dispatch to Ruby
158
+ * stash event and return -> ev_loop() returns -> dispatch to Ruby
158
159
  *
159
- * Which is kinda ugly and confusing, but still gives us
160
+ * Which is kinda ugly and confusing, but still gives us
160
161
  * an O(1) event loop whose heart is in the kernel itself. w00t!
161
162
  *
162
163
  * So, stash the event in the loop's data struct. When we return
163
164
  * the ev_loop() call being made in the Coolio_Loop_run_once_blocking()
164
- * function below will also return, at which point the GVL is
165
- * reacquired and we can call out to Ruby */
165
+ * function below will also return, and Coolio_Loop_dispatch_events()
166
+ * walks what we stashed and calls out to Ruby. A watcher detached
167
+ * along the way nils its own stashed entries, which that walk skips */
166
168
 
167
169
  /* Grow the event buffer if it's too small */
168
170
  if(loop_data->events_received >= loop_data->eventbuf_size) {