polyphony 0.36 → 0.38

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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -0
  3. data/Gemfile +0 -11
  4. data/Gemfile.lock +1 -3
  5. data/Rakefile +4 -0
  6. data/TODO.md +12 -10
  7. data/docs/index.md +2 -1
  8. data/examples/core/xx-fork-cleanup.rb +22 -0
  9. data/ext/gyro/async.c +27 -13
  10. data/ext/gyro/child.c +29 -15
  11. data/ext/gyro/fiber.c +3 -1
  12. data/ext/gyro/gyro.c +0 -6
  13. data/ext/gyro/gyro.h +6 -0
  14. data/ext/gyro/io.c +24 -9
  15. data/ext/gyro/queue.c +21 -21
  16. data/ext/gyro/selector.c +23 -0
  17. data/ext/gyro/signal.c +24 -9
  18. data/ext/gyro/thread.c +12 -2
  19. data/ext/gyro/timer.c +33 -18
  20. data/lib/polyphony.rb +27 -36
  21. data/lib/polyphony/adapters/fs.rb +1 -4
  22. data/lib/polyphony/adapters/process.rb +29 -25
  23. data/lib/polyphony/adapters/trace.rb +129 -124
  24. data/lib/polyphony/core/channel.rb +36 -36
  25. data/lib/polyphony/core/exceptions.rb +29 -29
  26. data/lib/polyphony/core/global_api.rb +92 -91
  27. data/lib/polyphony/core/resource_pool.rb +84 -84
  28. data/lib/polyphony/core/sync.rb +17 -17
  29. data/lib/polyphony/core/thread_pool.rb +49 -37
  30. data/lib/polyphony/core/throttler.rb +25 -25
  31. data/lib/polyphony/extensions/core.rb +3 -3
  32. data/lib/polyphony/extensions/fiber.rb +269 -267
  33. data/lib/polyphony/extensions/openssl.rb +1 -1
  34. data/lib/polyphony/extensions/socket.rb +2 -1
  35. data/lib/polyphony/extensions/thread.rb +3 -3
  36. data/lib/polyphony/net.rb +71 -67
  37. data/lib/polyphony/version.rb +1 -1
  38. data/polyphony.gemspec +0 -3
  39. data/test/stress.rb +17 -12
  40. data/test/test_thread.rb +1 -0
  41. data/test/test_thread_pool.rb +2 -2
  42. data/test/test_throttler.rb +0 -1
  43. metadata +3 -16
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1baba43b46fa48ea13ec33667390b5b6dafb8323d250f2df5e5ee6930e939680
4
- data.tar.gz: 465c329841226252b1fe7fc3d48904b912323370083b4925d89531e45a4bd34d
3
+ metadata.gz: b952f96f313866e5b61e200093fcb1eb8111b2d48bb2a736e36b62f06772786c
4
+ data.tar.gz: 03ffbc7481d3e20cfd5d50cc0c7e1e28749da15a6c9d65c89e83a0370b61b2d2
5
5
  SHA512:
6
- metadata.gz: d9b863d0231a5ccea65aa015d5a1d3432d0b513be1a60b2d20040bd06ed9d253c6be8391176ceba472300beac8059e27c334aca3bb6bafd51c3dabfd1e105301
7
- data.tar.gz: 29eba3a894084f20d21bab00840cef76a5094f73aee4521947685883dc717811a274ed723deb72433cc0f358715549d79215286aad0ba62d14ee97308ee1415b
6
+ metadata.gz: 8fb34d135a0ab50a3e3ae722dd3f9ae70ba8ee83534c199f98611f5fa273b424fcedab548929a2dedcdaddec73a9aa6b652fa97a924dd5400155cf57a1cdcaf0
7
+ data.tar.gz: caacb6a006f52abf888a9f4a3f1ef2c917a73422abaf1cba7d1ae1b0c7db07a3bc62a5a6b6abbe22e46fd6e679e82d7a048ec2d9ac7ec7dee54453600c688597
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## 0.38 2020-04-13
2
+
3
+ * Fix post-fork segfault if parent process has multiple threads with active watchers
4
+
5
+ ## 0.37 2020-04-07
6
+
7
+ * Explicitly kill threads on exit to prevent possible segfault
8
+ * Remove Modulation dependency
9
+
1
10
  ## 0.36 2020-03-31
2
11
 
3
12
  * More docs
data/Gemfile CHANGED
@@ -1,14 +1,3 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
-
5
- # gem "jekyll", "~> 3.8.6"
6
- # gem "jekyll-remote-theme"
7
- # gem "jekyll-seo-tag"
8
- # gem "just-the-docs"
9
-
10
- # # gem "github-pages", group: :jekyll_plugins
11
-
12
- # group :jekyll_plugins do
13
- # gem "jekyll-feed", "~> 0.6"
14
- # end
data/Gemfile.lock CHANGED
@@ -1,8 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- polyphony (0.36)
5
- modulation (~> 1.0)
4
+ polyphony (0.38)
6
5
 
7
6
  GEM
8
7
  remote: https://rubygems.org/
@@ -73,7 +72,6 @@ GEM
73
72
  builder
74
73
  minitest (>= 5.0)
75
74
  ruby-progressbar
76
- modulation (1.0)
77
75
  multi_xml (0.6.0)
78
76
  parallel (1.19.1)
79
77
  parser (2.7.0.2)
data/Rakefile CHANGED
@@ -15,6 +15,10 @@ task :test do
15
15
  exec 'ruby test/run.rb'
16
16
  end
17
17
 
18
+ task :stress_test do
19
+ exec 'ruby test/stress.rb'
20
+ end
21
+
18
22
  task :docs do
19
23
  exec 'RUBYOPT=-W0 jekyll serve -s docs'
20
24
  end
data/TODO.md CHANGED
@@ -32,7 +32,11 @@
32
32
  Fiber.current.add_child_fiber(t.main_fiber)
33
33
  ```
34
34
 
35
- ## 0.36 Some more API work, more docs
35
+
36
+
37
+
38
+
39
+ ## 0.40 Some more API work, more docs
36
40
 
37
41
  - Debugging
38
42
  - Eat your own dogfood: need a good tool to check what's going on when some
@@ -142,17 +146,15 @@
142
146
  - Docs
143
147
  - landing page:
144
148
  - links to the interesting stuff
145
- - concurrency overview
146
- - faq
147
149
  - benchmarks
148
150
  - explain difference between `sleep` and `suspend`
149
- - add explanation about async vs sync
151
+ - add explanation about async vs sync, blocking vs non-blocking
150
152
  - discuss using `snooze` for ensuring responsiveness when executing CPU-bound work
151
153
 
152
154
  - Check why first call to `#sleep` returns too early in tests. Check the
153
155
  sleep behaviour in a spawned thread.
154
156
 
155
- ## 0.37 Sinatra / Sidekiq
157
+ ## 0.41 Sinatra / Sidekiq
156
158
 
157
159
  - sintra app with database access (postgresql)
158
160
 
@@ -162,13 +164,13 @@
162
164
  - test performance
163
165
  - proceed from there
164
166
 
165
- ## 0.38 Testing && Docs
167
+ ## 0.42 Testing && Docs
166
168
 
167
169
  - Pull out redis/postgres code, put into new `polyphony-xxx` gems
168
170
 
169
- ## 0.39 Integration
171
+ ## 0.43 Integration
170
172
 
171
- ## 0.40 Real IO#gets and IO#read
173
+ ## 0.44 Real IO#gets and IO#read
172
174
 
173
175
  - More tests
174
176
  - Implement some basic stuff missing:
@@ -178,11 +180,11 @@
178
180
  - `IO.foreach`
179
181
  - `Process.waitpid`
180
182
 
181
- ## 0.41 Rails
183
+ ## 0.45 Rails
182
184
 
183
185
  - Rails?
184
186
 
185
- ## 0.42 DNS
187
+ ## 0.46 DNS
186
188
 
187
189
  ### DNS client
188
190
 
data/docs/index.md CHANGED
@@ -14,8 +14,9 @@ implements a comprehensive
14
14
  using [libev](https://github.com/enki/libev) as a high-performance event reactor
15
15
  for I/O, timers, and other asynchronous events.
16
16
 
17
- [FAQ](faq){: .btn .btn-green .text-gamma }
18
17
  [Take the tutorial](getting-started/tutorial){: .btn .btn-blue .text-gamma }
18
+ [Main Concepts](main-concepts/concurrency/){: .btn .btn-green .text-gamma }
19
+ [FAQ](faq){: .btn .btn-green .text-gamma }
19
20
  [Source code](https://github.com/digital-fabric/polyphony){: .btn .btn-purple .text-gamma target="_blank" }
20
21
  {: .mt-6 .h-align-center }
21
22
 
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ Exception.__disable_sanitized_backtrace__ = true
7
+
8
+ t = Thread.new do
9
+ async = Gyro::Async.new
10
+ spin { async.await }
11
+ sleep 100
12
+ end
13
+
14
+ sleep 0.5
15
+
16
+ Polyphony.fork do
17
+ puts "forked #{Process.pid}"
18
+ sleep 1
19
+ puts "done sleeping"
20
+ end
21
+
22
+ sleep 50
data/ext/gyro/async.c CHANGED
@@ -27,11 +27,15 @@ static void Gyro_Async_mark(void *ptr) {
27
27
 
28
28
  static void Gyro_Async_free(void *ptr) {
29
29
  struct Gyro_Async *async = ptr;
30
- if (async->active) {
31
- ev_clear_pending(async->ev_loop, &async->ev_async);
32
- ev_async_stop(async->ev_loop, &async->ev_async);
30
+ switch (async->active) {
31
+ case GYRO_WATCHER_POST_FORK:
32
+ return;
33
+ case 1:
34
+ ev_clear_pending(async->ev_loop, &async->ev_async);
35
+ ev_async_stop(async->ev_loop, &async->ev_async);
36
+ default:
37
+ xfree(async);
33
38
  }
34
- xfree(async);
35
39
  }
36
40
 
37
41
  static size_t Gyro_Async_size(const void *ptr) {
@@ -49,7 +53,7 @@ static VALUE Gyro_Async_allocate(VALUE klass) {
49
53
  return TypedData_Wrap_Struct(klass, &Gyro_Async_type, async);
50
54
  }
51
55
 
52
- inline void Gyro_Async_activate(struct Gyro_Async *async) {
56
+ inline void async_activate(struct Gyro_Async *async) {
53
57
  if (async->active) return;
54
58
 
55
59
  async->active = 1;
@@ -60,7 +64,7 @@ inline void Gyro_Async_activate(struct Gyro_Async *async) {
60
64
  ev_async_start(async->ev_loop, &async->ev_async);
61
65
  }
62
66
 
63
- inline void Gyro_Async_deactivate(struct Gyro_Async *async) {
67
+ inline void async_deactivate(struct Gyro_Async *async) {
64
68
  if (!async->active) return;
65
69
 
66
70
  ev_async_stop(async->ev_loop, &async->ev_async);
@@ -76,7 +80,7 @@ void Gyro_Async_callback(struct ev_loop *ev_loop, struct ev_async *ev_async, int
76
80
  struct Gyro_Async *async = (struct Gyro_Async*)ev_async;
77
81
 
78
82
  Fiber_make_runnable(async->fiber, async->value);
79
- Gyro_Async_deactivate(async);
83
+ async_deactivate(async);
80
84
  }
81
85
 
82
86
  #define GetGyro_Async(obj, async) \
@@ -117,22 +121,31 @@ VALUE Gyro_Async_await(VALUE self) {
117
121
  struct Gyro_Async *async;
118
122
  GetGyro_Async(self, async);
119
123
 
120
- Gyro_Async_activate(async);
124
+ async_activate(async);
121
125
  VALUE ret = Gyro_switchpoint();
122
- Gyro_Async_deactivate(async);
126
+ async_deactivate(async);
123
127
 
124
128
  TEST_RESUME_EXCEPTION(ret);
125
129
  RB_GC_GUARD(ret);
126
130
  return ret;
127
131
  }
128
132
 
133
+ VALUE Gyro_Async_deactivate_post_fork(VALUE self) {
134
+ struct Gyro_Async *async;
135
+ GetGyro_Async(self, async);
136
+
137
+ if (async->active)
138
+ async->active = GYRO_WATCHER_POST_FORK;
139
+ return self;
140
+ }
141
+
129
142
  VALUE Gyro_Async_await_no_raise(VALUE self) {
130
143
  struct Gyro_Async *async;
131
144
  GetGyro_Async(self, async);
132
145
 
133
- Gyro_Async_activate(async);
146
+ async_activate(async);
134
147
  VALUE ret = Gyro_switchpoint();
135
- Gyro_Async_deactivate(async);
148
+ async_deactivate(async);
136
149
 
137
150
  RB_GC_GUARD(ret);
138
151
  return ret;
@@ -143,6 +156,7 @@ void Init_Gyro_Async() {
143
156
  rb_define_alloc_func(cGyro_Async, Gyro_Async_allocate);
144
157
 
145
158
  rb_define_method(cGyro_Async, "initialize", Gyro_Async_initialize, 0);
146
- rb_define_method(cGyro_Async, "signal", Gyro_Async_signal, -1);
147
159
  rb_define_method(cGyro_Async, "await", Gyro_Async_await, 0);
148
- }
160
+ rb_define_method(cGyro_Async, "deactivate_post_fork", Gyro_Async_deactivate_post_fork, 0);
161
+ rb_define_method(cGyro_Async, "signal", Gyro_Async_signal, -1);
162
+ }
data/ext/gyro/child.c CHANGED
@@ -24,11 +24,15 @@ static void Gyro_Child_mark(void *ptr) {
24
24
 
25
25
  static void Gyro_Child_free(void *ptr) {
26
26
  struct Gyro_Child *child = ptr;
27
- if (child->active) {
28
- ev_clear_pending(child->ev_loop, &child->ev_child);
29
- ev_child_stop(child->ev_loop, &child->ev_child);
27
+ switch (child->active) {
28
+ case GYRO_WATCHER_POST_FORK:
29
+ return;
30
+ case 1:
31
+ ev_clear_pending(child->ev_loop, &child->ev_child);
32
+ ev_child_stop(child->ev_loop, &child->ev_child);
33
+ default:
34
+ xfree(child);
30
35
  }
31
- xfree(child);
32
36
  }
33
37
 
34
38
  static size_t Gyro_Child_size(const void *ptr) {
@@ -46,7 +50,7 @@ static VALUE Gyro_Child_allocate(VALUE klass) {
46
50
  return TypedData_Wrap_Struct(klass, &Gyro_Child_type, child);
47
51
  }
48
52
 
49
- inline void Gyro_Child_activate(struct Gyro_Child *child) {
53
+ inline void child_activate(struct Gyro_Child *child) {
50
54
  if (child->active) return;
51
55
 
52
56
  child->active = 1;
@@ -57,7 +61,7 @@ inline void Gyro_Child_activate(struct Gyro_Child *child) {
57
61
  ev_child_start(child->ev_loop, &child->ev_child);
58
62
  }
59
63
 
60
- inline void Gyro_Child_deactivate(struct Gyro_Child *child) {
64
+ inline void child_deactivate(struct Gyro_Child *child) {
61
65
  if (!child->active) return;
62
66
 
63
67
  ev_child_stop(child->ev_loop, &child->ev_child);
@@ -82,7 +86,7 @@ void Gyro_Child_callback(struct ev_loop *ev_loop, struct ev_child *ev_child, int
82
86
  VALUE resume_value = Gyro_Child_resume_value(ev_child);
83
87
  Fiber_make_runnable(child->fiber, resume_value);
84
88
 
85
- Gyro_Child_deactivate(child);
89
+ child_deactivate(child);
86
90
  }
87
91
 
88
92
  #define GetGyro_Child(obj, child) \
@@ -93,12 +97,12 @@ static VALUE Gyro_Child_initialize(VALUE self, VALUE pid) {
93
97
 
94
98
  GetGyro_Child(self, child);
95
99
 
96
- child->self = self;
97
- child->fiber = Qnil;
98
- child->selector = Qnil;
99
- child->pid = NUM2INT(pid);
100
- child->active = 0;
101
- child->ev_loop = 0;
100
+ child->self = self;
101
+ child->fiber = Qnil;
102
+ child->selector = Qnil;
103
+ child->pid = NUM2INT(pid);
104
+ child->active = 0;
105
+ child->ev_loop = 0;
102
106
 
103
107
  ev_child_init(&child->ev_child, Gyro_Child_callback, child->pid, 0);
104
108
 
@@ -109,19 +113,29 @@ static VALUE Gyro_Child_await(VALUE self) {
109
113
  struct Gyro_Child *child;
110
114
  GetGyro_Child(self, child);
111
115
 
112
- Gyro_Child_activate(child);
116
+ child_activate(child);
113
117
  VALUE ret = Gyro_switchpoint();
114
- Gyro_Child_deactivate(child);
118
+ child_deactivate(child);
115
119
 
116
120
  TEST_RESUME_EXCEPTION(ret);
117
121
  RB_GC_GUARD(ret);
118
122
  return ret;
119
123
  }
120
124
 
125
+ VALUE Gyro_Child_deactivate_post_fork(VALUE self) {
126
+ struct Gyro_Child *child;
127
+ GetGyro_Child(self, child);
128
+
129
+ if (child->active)
130
+ child->active = GYRO_WATCHER_POST_FORK;
131
+ return self;
132
+ }
133
+
121
134
  void Init_Gyro_Child() {
122
135
  cGyro_Child = rb_define_class_under(mGyro, "Child", rb_cData);
123
136
  rb_define_alloc_func(cGyro_Child, Gyro_Child_allocate);
124
137
 
125
138
  rb_define_method(cGyro_Child, "initialize", Gyro_Child_initialize, 1);
126
139
  rb_define_method(cGyro_Child, "await", Gyro_Child_await, 0);
140
+ rb_define_method(cGyro_Child, "deactivate_post_fork", Gyro_Child_deactivate_post_fork, 0);
127
141
  }
data/ext/gyro/fiber.c CHANGED
@@ -71,7 +71,9 @@ void Fiber_make_runnable(VALUE fiber, VALUE value) {
71
71
  Thread_schedule_fiber(thread, fiber, value);
72
72
  }
73
73
  else {
74
- rb_warn("No thread set for fiber");
74
+ rb_warn("No thread set for fiber (fiber, value, caller):");
75
+ VALUE caller = rb_funcall(rb_cObject, rb_intern("caller"), 0);
76
+ INSPECT(3, fiber, value, caller);
75
77
  }
76
78
  }
77
79
 
data/ext/gyro/gyro.c CHANGED
@@ -44,11 +44,6 @@ VALUE Gyro_snooze(VALUE self) {
44
44
  return ret;
45
45
  }
46
46
 
47
- static VALUE Gyro_post_fork(VALUE self) {
48
- Thread_post_fork(rb_thread_current());
49
- return Qnil;
50
- }
51
-
52
47
  static VALUE Gyro_ref(VALUE self) {
53
48
  return Thread_ref(rb_thread_current());
54
49
  }
@@ -73,7 +68,6 @@ VALUE Gyro_trace(VALUE self, VALUE enabled) {
73
68
  void Init_Gyro() {
74
69
  mGyro = rb_define_module("Gyro");
75
70
 
76
- rb_define_singleton_method(mGyro, "post_fork", Gyro_post_fork, 0);
77
71
  rb_define_singleton_method(mGyro, "ref", Gyro_ref, 0);
78
72
  rb_define_singleton_method(mGyro, "unref", Gyro_unref, 0);
79
73
  rb_define_singleton_method(mGyro, "trace", Gyro_trace, 1);
data/ext/gyro/gyro.h CHANGED
@@ -59,6 +59,12 @@ enum {
59
59
  FIBER_STATE_SCHEDULED = 2
60
60
  };
61
61
 
62
+ // watcher flags
63
+ enum {
64
+ // a watcher's active field will be set to this after fork
65
+ GYRO_WATCHER_POST_FORK = 0xFF
66
+ };
67
+
62
68
  VALUE Fiber_auto_async(VALUE self);
63
69
  VALUE Fiber_auto_io(VALUE self);
64
70
  void Fiber_make_runnable(VALUE fiber, VALUE value);
data/ext/gyro/io.c CHANGED
@@ -34,11 +34,15 @@ static void Gyro_IO_mark(void *ptr) {
34
34
 
35
35
  static void Gyro_IO_free(void *ptr) {
36
36
  struct Gyro_IO *io = ptr;
37
- if (io->active) {
38
- ev_clear_pending(io->ev_loop, &io->ev_io);
39
- ev_io_stop(io->ev_loop, &io->ev_io);
37
+ switch (io->active) {
38
+ case GYRO_WATCHER_POST_FORK:
39
+ return;
40
+ case 1:
41
+ ev_clear_pending(io->ev_loop, &io->ev_io);
42
+ ev_io_stop(io->ev_loop, &io->ev_io);
43
+ default:
44
+ xfree(io);
40
45
  }
41
- xfree(io);
42
46
  }
43
47
 
44
48
  static size_t Gyro_IO_size(const void *ptr) {
@@ -57,7 +61,7 @@ static VALUE Gyro_IO_allocate(VALUE klass) {
57
61
  return TypedData_Wrap_Struct(klass, &Gyro_IO_type, io);
58
62
  }
59
63
 
60
- inline void Gyro_IO_activate(struct Gyro_IO *io) {
64
+ inline void io_activate(struct Gyro_IO *io) {
61
65
  if (io->active) return;
62
66
 
63
67
  io->active = 1;
@@ -68,7 +72,7 @@ inline void Gyro_IO_activate(struct Gyro_IO *io) {
68
72
  ev_io_start(io->ev_loop, &io->ev_io);
69
73
  }
70
74
 
71
- inline void Gyro_IO_deactivate(struct Gyro_IO *io) {
75
+ inline void io_deactivate(struct Gyro_IO *io) {
72
76
  if (!io->active) return;
73
77
 
74
78
  ev_io_stop(io->ev_loop, &io->ev_io);
@@ -83,7 +87,7 @@ void Gyro_IO_callback(struct ev_loop *ev_loop, struct ev_io *ev_io, int revents)
83
87
  struct Gyro_IO *io = (struct Gyro_IO*)ev_io;
84
88
 
85
89
  Fiber_make_runnable(io->fiber, Qnil);
86
- Gyro_IO_deactivate(io);
90
+ io_deactivate(io);
87
91
  }
88
92
 
89
93
  static int Gyro_IO_symbol2event_mask(VALUE sym) {
@@ -143,15 +147,25 @@ VALUE Gyro_IO_await(VALUE self) {
143
147
  struct Gyro_IO *io;
144
148
  GetGyro_IO(self, io);
145
149
 
146
- Gyro_IO_activate(io);
150
+ io_activate(io);
147
151
  VALUE ret = Gyro_switchpoint();
148
- Gyro_IO_deactivate(io);
152
+ io_deactivate(io);
149
153
 
150
154
  TEST_RESUME_EXCEPTION(ret);
151
155
  RB_GC_GUARD(ret);
152
156
  return ret;
153
157
  }
154
158
 
159
+ VALUE Gyro_IO_deactivate_post_fork(VALUE self) {
160
+ struct Gyro_IO *io;
161
+ GetGyro_IO(self, io);
162
+
163
+ if (io->active)
164
+ io->active = GYRO_WATCHER_POST_FORK;
165
+
166
+ return self;
167
+ }
168
+
155
169
  VALUE Gyro_IO_auto_io(int fd, int events) {
156
170
  VALUE watcher = Fiber_auto_io(rb_fiber_current());
157
171
  struct Gyro_IO *io;
@@ -453,6 +467,7 @@ void Init_Gyro_IO() {
453
467
 
454
468
  rb_define_method(cGyro_IO, "initialize", Gyro_IO_initialize, 2);
455
469
  rb_define_method(cGyro_IO, "await", Gyro_IO_await, 0);
470
+ rb_define_method(cGyro_IO, "deactivate_post_fork", Gyro_IO_deactivate_post_fork, 0);
456
471
 
457
472
  VALUE cIO = rb_const_get(rb_cObject, rb_intern("IO"));
458
473
  // rb_define_method(cIO, "gets", IO_gets, -1);