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 +4 -4
- data/.github/workflows/test.yaml +1 -0
- data/.github/workflows/valgrind.yaml +41 -0
- data/Rakefile +7 -19
- data/cool.io.gemspec +1 -3
- data/ext/cool.io/buffer.c +77 -52
- data/ext/cool.io/cool.io.h +6 -0
- data/ext/cool.io/ev_wrap.h +1 -0
- data/ext/cool.io/extconf.rb +0 -1
- data/ext/cool.io/iowatcher.c +18 -1
- data/ext/cool.io/loop.c +21 -19
- data/ext/cool.io/stat_watcher.c +44 -19
- data/ext/cool.io/timer_watcher.c +1 -1
- data/ext/cool.io/watcher.c +13 -1
- data/ext/cool.io/watcher.h +0 -17
- data/ext/libev/ev.c +80 -12
- data/ext/libev/ev_select.c +10 -0
- data/ext/libev/ev_vars.h +4 -0
- data/ext/libev/ev_win32_stat.c +851 -0
- data/ext/libev/ev_wrap.h +4 -0
- data/gems.rb +4 -0
- data/lib/cool.io/dns_resolver.rb +53 -8
- data/lib/cool.io/loop.rb +5 -1
- 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 +59 -25
- data/spec/dns_spec.rb +197 -0
- data/spec/fixtures/windows_stat_native.c +1305 -0
- data/spec/iobuffer_spec.rb +88 -1
- data/spec/loop_spec.rb +31 -0
- data/spec/stat_watcher_spec.rb +86 -1
- data/spec/tcp_server_spec.rb +34 -10
- data/spec/watcher_reattach_spec.rb +54 -0
- data/spec/windows_stat_native_spec.rb +193 -0
- data/spec/windows_stat_watcher_spec.rb +363 -0
- metadata +14 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9494578f6a02a987a84805bca37919a7c0e63c4c585a7bdf8c68e18e38d21e8d
|
|
4
|
+
data.tar.gz: 0cae209afee54a8f6f59014afb71ee2357dabdc350cfc60dfe2a6212062dbf1e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ccb23719b38180b44cd6485901a35a15710daf77f4079a7fbdbc1d77fcfe86b75787d82c8cd8f455f5199a7f390125d6d62320eeff2785b2193c6fe5612c7653
|
|
7
|
+
data.tar.gz: 2ab5356bc8ab69fb93c6f3c6532ada9c67b63c774e7a952f20526b1bf3660fde681a3b480130deaf436212b4b0d77b51859fefcb9d4f0a6dc983b0a46de70f2c
|
data/.github/workflows/test.yaml
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
require
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
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
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
72
|
-
static void buffer_append(struct buffer * buf, char *str,
|
|
73
|
-
static void buffer_read(struct buffer * buf, char *str,
|
|
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,
|
|
76
|
-
static
|
|
77
|
-
static
|
|
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 ->
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
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
|
|
307
|
+
* Coolio::Buffer#read -> String
|
|
308
|
+
* Coolio::Buffer#read(length) -> String
|
|
302
309
|
*
|
|
303
|
-
* Read
|
|
304
|
-
*
|
|
305
|
-
*
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
321
|
-
|
|
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
|
-
|
|
324
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 :
|
|
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
|
|
460
|
+
return SSIZET2NUM(buffer_write_to(buf, rb_io_descriptor(io)));
|
|
439
461
|
#else
|
|
440
|
-
return
|
|
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 (
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
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,
|
|
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,
|
|
604
|
+
buffer_append(struct buffer * buf, char *str, size_t len)
|
|
580
605
|
{
|
|
581
|
-
|
|
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,
|
|
641
|
+
buffer_read(struct buffer * buf, char *str, size_t len)
|
|
617
642
|
{
|
|
618
|
-
|
|
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
|
-
|
|
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,
|
|
716
|
+
buffer_copy(struct buffer * buf, char *str, size_t len)
|
|
692
717
|
{
|
|
693
|
-
|
|
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
|
|
737
|
+
static ssize_t
|
|
713
738
|
buffer_write_to(struct buffer * buf, int fd)
|
|
714
739
|
{
|
|
715
|
-
|
|
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
|
|
776
|
+
static ssize_t
|
|
752
777
|
buffer_read_from(struct buffer * buf, int fd)
|
|
753
778
|
{
|
|
754
|
-
|
|
755
|
-
|
|
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
|
}
|
data/ext/cool.io/cool.io.h
CHANGED
|
@@ -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
|
|
data/ext/cool.io/ev_wrap.h
CHANGED
data/ext/cool.io/extconf.rb
CHANGED
data/ext/cool.io/iowatcher.c
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
85
|
-
|
|
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
|
|
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)
|
|
130
|
+
* -> release GVL -> event syscall -> reacquire GVL -> libev callback
|
|
131
|
+
* (GVL = Global VM Lock) ^^^ You are here
|
|
131
132
|
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
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
|
-
*
|
|
154
|
-
*
|
|
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 ->
|
|
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,
|
|
165
|
-
*
|
|
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) {
|