nio4r 2.1.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 72ea8743f4a16dc03e219310a3cd15321cfda343
4
- data.tar.gz: 7145084d017ff49d5356a76db8a666702fd080f8
2
+ SHA256:
3
+ metadata.gz: 1b801ac687bb5098220b7d2b870f84d172cdcd0683c50175006196e7ab9f5021
4
+ data.tar.gz: 93301d044d3bb5fe7b7e9db9b1ab7d0b683770a22adda61b6e6ccb60bbe1a137
5
5
  SHA512:
6
- metadata.gz: 1fd8b916c17bc23d47c20413c4d829d7ee0cf3e6f5b0e00ca36fd40475750668a2cf46545f17e0096893ce67dbdf99b675621c73a99cf3ffc3b22380586846fd
7
- data.tar.gz: af5f4bc6ee1c657e9092b1eafcb7c4ee8d39bccee51e1bf24394f8ce989ef7e713a12fb90a298810317351128119f9e2c0ab87386b0c60809c77202a0d1e2e0d
6
+ metadata.gz: 000f6a6ebe2acb74581d0c4976c0fc46ed2ae2baeaea6b18622c7b7105567e3734517429867e9b1bed9df5e8b320430d4dff505f98e817d9c6248eca6a7ccdba
7
+ data.tar.gz: 988ee869b08ea7d91c5383ee28c505db0e595eec35c98513b3468e1ecec66224894514e352db810cc1143b797c9c6af714ebd3d3eee90291f036e09ad4a28ccd
@@ -1,4 +1,5 @@
1
1
  AllCops:
2
+ TargetRubyVersion: 2.2
2
3
  DisplayCopNames: true
3
4
 
4
5
  #
@@ -18,6 +19,9 @@ Lint/Loop:
18
19
  Metrics/AbcSize:
19
20
  Max: 35
20
21
 
22
+ Metrics/BlockLength:
23
+ Max: 128
24
+
21
25
  Metrics/ClassLength:
22
26
  Max: 128
23
27
 
@@ -34,12 +38,22 @@ Metrics/CyclomaticComplexity:
34
38
  Metrics/PerceivedComplexity:
35
39
  Max: 15
36
40
 
41
+ #
42
+ # Performance
43
+ #
44
+
45
+ Performance/RegexpMatch:
46
+ Enabled: false
47
+
37
48
  #
38
49
  # Style
39
50
  #
40
51
 
41
- Style/StringLiterals:
42
- EnforcedStyle: double_quotes
52
+ Style/FormatStringToken:
53
+ Enabled: false
54
+
55
+ Style/FrozenStringLiteralComment:
56
+ Enabled: true
43
57
 
44
58
  Style/GlobalVars:
45
59
  Enabled: false
@@ -50,5 +64,11 @@ Style/NumericPredicate:
50
64
  Style/RescueModifier:
51
65
  Enabled: false
52
66
 
67
+ Style/SafeNavigation:
68
+ Enabled: false
69
+
70
+ Style/StringLiterals:
71
+ EnforcedStyle: double_quotes
72
+
53
73
  Style/TrivialAccessors:
54
74
  Enabled: false
@@ -3,7 +3,7 @@ sudo: false
3
3
  cache: bundler
4
4
 
5
5
  before_install:
6
- - gem update --system 2.6.10
6
+ - gem update --system
7
7
  - gem --version
8
8
 
9
9
  bundler_args: --without development
@@ -13,10 +13,11 @@ branches:
13
13
  - master
14
14
 
15
15
  rvm:
16
- - jruby-9.1.10.0 # latest stable
16
+ - jruby-9.1.15.0 # latest stable
17
17
  - 2.2.7
18
18
  - 2.3.4
19
19
  - 2.4.1
20
+ - 2.5.0
20
21
  - ruby-head
21
22
 
22
23
  env:
data/CHANGES.md CHANGED
@@ -1,3 +1,41 @@
1
+ ## 2.2.0 (2017-12-27)
2
+
3
+ * [#151](https://github.com/socketry/nio4r/pull/151)
4
+ `NIO::Selector`: Support for enumerating and configuring backend
5
+ ([@tarcieri])
6
+
7
+ * [#153](https://github.com/socketry/nio4r/pull/153)
8
+ Fix builds on Windows
9
+ ([@unak])
10
+
11
+ * [#157](https://github.com/socketry/nio4r/pull/157)
12
+ Windows / MinGW test failure - fix spec_helper.rb
13
+ ([@MSP-Greg])
14
+
15
+ * [#162](https://github.com/socketry/nio4r/pull/162)
16
+ Don't build the C extension on Windows
17
+ ([@larskanis])
18
+
19
+ * [#164](https://github.com/socketry/nio4r/pull/164)
20
+ Fix NIO::ByteBuffer leak
21
+ ([@HoneyryderChuck])
22
+
23
+ * [#170](https://github.com/socketry/nio4r/pull/170)
24
+ Avoid CancelledKeyExceptions on JRuby
25
+ ([@HoneyryderChuck])
26
+
27
+ * [#177](https://github.com/socketry/nio4r/pull/177)
28
+ Fix `NIO::ByteBuffer` string conversions on JRuby
29
+ ([@tarcieri])
30
+
31
+ * [#179](https://github.com/socketry/nio4r/pull/179)
32
+ Fix argument error when running on ruby 2.5.0
33
+ ([@tompng])
34
+
35
+ * [#180](https://github.com/socketry/nio4r/pull/180)
36
+ ext/nio4r/extconf.rb: check for port_event_t in port.h (fixes #178)
37
+ ([@tarcieri])
38
+
1
39
  ## 2.1.0 (2017-05-28)
2
40
 
3
41
  * [#130](https://github.com/socketry/nio4r/pull/130)
@@ -163,3 +201,8 @@
163
201
  [@johnnyt]: https://github.com/johnnyt
164
202
  [@UpeksheJay]: https://github.com/UpeksheJay
165
203
  [@junaruga]: https://github.com/junaruga
204
+ [@unak]: https://github.com/unak
205
+ [@MSP-Greg]: https://github.com/MSP-Greg
206
+ [@larskanis]: https://github.com/larskanis
207
+ [@HoneyryderChuck]: https://github.com/HoneyryderChuck
208
+ [@tompng]: https://github.com/tompng
data/Gemfile CHANGED
@@ -14,7 +14,7 @@ end
14
14
  group :development, :test do
15
15
  gem "coveralls", require: false
16
16
  gem "rake-compiler", require: false
17
- gem "rspec", "~> 3", require: false
17
+ gem "rspec", "~> 3.7", require: false
18
18
  gem "rspec-retry", require: false
19
- gem "rubocop", "0.46.0", require: false
19
+ gem "rubocop", "0.52.1", require: false
20
20
  end
data/Guardfile CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- directories %w(lib spec)
3
+ directories %w[lib spec]
4
4
  clearing :on
5
5
 
6
6
  guard :rspec, cmd: "bundle exec rspec" do
data/README.md CHANGED
@@ -1,9 +1,11 @@
1
1
  # ![nio4r](https://raw.github.com/socketry/nio4r/master/logo.png)
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/nio4r.svg)](http://rubygems.org/gems/nio4r)
4
- [![Build Status](https://secure.travis-ci.org/socketry/nio4r.svg?branch=master)](http://travis-ci.org/socketry/nio4r)
4
+ [![Travis CI Status](https://secure.travis-ci.org/socketry/nio4r.svg?branch=master)](http://travis-ci.org/socketry/nio4r)
5
+ [![Appveyor Status](https://ci.appveyor.com/api/projects/status/1ru8x81v91vaewax/branch/master?svg=true)](https://ci.appveyor.com/project/tarcieri/nio4r/branch/master)
5
6
  [![Code Climate](https://codeclimate.com/github/socketry/nio4r.svg)](https://codeclimate.com/github/socketry/nio4r)
6
7
  [![Coverage Status](https://coveralls.io/repos/socketry/nio4r/badge.svg?branch=master)](https://coveralls.io/r/socketry/nio4r)
8
+ [![Yard Docs](https://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/gems/nio4r/2.2.0)
7
9
  [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/socketry/nio4r/blob/master/LICENSE.txt)
8
10
 
9
11
  _NOTE: This is the 2.x **stable** branch of nio4r. For the 1.x **legacy** branch,
data/Rakefile CHANGED
@@ -5,6 +5,6 @@ require "rake/clean"
5
5
 
6
6
  Dir[File.expand_path("../tasks/**/*.rake", __FILE__)].each { |task| load task }
7
7
 
8
- task default: %w(compile spec rubocop)
8
+ task default: %w[compile spec rubocop]
9
9
 
10
10
  CLEAN.include "**/*.o", "**/*.so", "**/*.bundle", "**/*.jar", "pkg", "tmp"
@@ -0,0 +1,27 @@
1
+ branches:
2
+ only:
3
+ - master
4
+
5
+ environment:
6
+ PATH: C:\Ruby%RUBY_VERSION%\DevKit\mingw\bin;C:\Ruby%RUBY_VERSION%\bin;C:\Ruby%RUBY_VERSION%\DevKit\bin;%PATH%
7
+ matrix:
8
+ - RUBY_VERSION: "24-x64"
9
+ - RUBY_VERSION: "23-x64"
10
+ - RUBY_VERSION: "23"
11
+ - RUBY_VERSION: "22"
12
+
13
+ install:
14
+ - SET RAKEOPT=-rdevkit
15
+ - ruby -v
16
+ - gem -v
17
+ - bundle -v
18
+ - bundle install
19
+
20
+ build: off
21
+
22
+ before_build:
23
+ - gem update --system
24
+
25
+ test_script:
26
+ - echo %PATH%
27
+ - bundle exec rake spec
@@ -75,6 +75,7 @@ void Init_NIO_ByteBuffer()
75
75
  static VALUE NIO_ByteBuffer_allocate(VALUE klass)
76
76
  {
77
77
  struct NIO_ByteBuffer *bytebuffer = (struct NIO_ByteBuffer *)xmalloc(sizeof(struct NIO_ByteBuffer));
78
+ bytebuffer->buffer = NULL;
78
79
  return Data_Wrap_Struct(klass, NIO_ByteBuffer_gc_mark, NIO_ByteBuffer_free, bytebuffer);
79
80
  }
80
81
 
@@ -84,6 +85,8 @@ static void NIO_ByteBuffer_gc_mark(struct NIO_ByteBuffer *buffer)
84
85
 
85
86
  static void NIO_ByteBuffer_free(struct NIO_ByteBuffer *buffer)
86
87
  {
88
+ if(buffer->buffer)
89
+ xfree(buffer->buffer);
87
90
  xfree(buffer);
88
91
  }
89
92
 
@@ -124,10 +127,11 @@ static VALUE NIO_ByteBuffer_get_position(VALUE self)
124
127
 
125
128
  static VALUE NIO_ByteBuffer_set_position(VALUE self, VALUE new_position)
126
129
  {
130
+ int pos;
127
131
  struct NIO_ByteBuffer *buffer;
128
132
  Data_Get_Struct(self, struct NIO_ByteBuffer, buffer);
129
133
 
130
- int pos = NUM2INT(new_position);
134
+ pos = NUM2INT(new_position);
131
135
 
132
136
  if(pos < 0) {
133
137
  rb_raise(rb_eArgError, "negative position given");
@@ -156,10 +160,11 @@ static VALUE NIO_ByteBuffer_get_limit(VALUE self)
156
160
 
157
161
  static VALUE NIO_ByteBuffer_set_limit(VALUE self, VALUE new_limit)
158
162
  {
163
+ int lim;
159
164
  struct NIO_ByteBuffer *buffer;
160
165
  Data_Get_Struct(self, struct NIO_ByteBuffer, buffer);
161
166
 
162
- int lim = NUM2INT(new_limit);
167
+ lim = NUM2INT(new_limit);
163
168
 
164
169
  if(lim < 0) {
165
170
  rb_raise(rb_eArgError, "negative limit given");
@@ -237,10 +242,11 @@ static VALUE NIO_ByteBuffer_get(int argc, VALUE *argv, VALUE self)
237
242
 
238
243
  static VALUE NIO_ByteBuffer_fetch(VALUE self, VALUE index)
239
244
  {
245
+ int i;
240
246
  struct NIO_ByteBuffer *buffer;
241
247
  Data_Get_Struct(self, struct NIO_ByteBuffer, buffer);
242
248
 
243
- int i = NUM2INT(index);
249
+ i = NUM2INT(index);
244
250
 
245
251
  if(i < 0) {
246
252
  rb_raise(rb_eArgError, "negative index given");
@@ -255,10 +261,12 @@ static VALUE NIO_ByteBuffer_fetch(VALUE self, VALUE index)
255
261
 
256
262
  static VALUE NIO_ByteBuffer_put(VALUE self, VALUE string)
257
263
  {
264
+ long length;
258
265
  struct NIO_ByteBuffer *buffer;
259
266
  Data_Get_Struct(self, struct NIO_ByteBuffer, buffer);
260
267
 
261
- long length = RSTRING_LEN(string);
268
+ StringValue(string);
269
+ length = RSTRING_LEN(string);
262
270
 
263
271
  if(length > buffer->limit - buffer->position) {
264
272
  rb_raise(cNIO_ByteBuffer_OverflowError, "buffer is full");
@@ -1,35 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "mkmf"
4
-
5
- have_header("unistd.h")
6
-
7
- $defs << "-DEV_USE_SELECT" if have_header("sys/select.h")
3
+ require "rubygems"
8
4
 
9
- $defs << "-DEV_USE_POLL" if have_header("poll.h")
10
-
11
- $defs << "-DEV_USE_EPOLL" if have_header("sys/epoll.h")
12
-
13
- if have_header("sys/event.h") && have_header("sys/queue.h")
14
- $defs << "-DEV_USE_KQUEUE"
5
+ # Write a dummy Makefile on Windows because we use the pure Ruby implementation there
6
+ if Gem.win_platform?
7
+ File.write("Makefile", "all install::\n")
8
+ File.write("nio4r_ext.so", "")
9
+ exit
15
10
  end
16
11
 
17
- $defs << "-DEV_USE_PORT" if have_header("port.h")
12
+ require "mkmf"
18
13
 
14
+ have_header("unistd.h")
15
+
16
+ $defs << "-DEV_USE_SELECT" if have_header("sys/select.h")
17
+ $defs << "-DEV_USE_POLL" if have_type("port_event_t", "poll.h")
18
+ $defs << "-DEV_USE_EPOLL" if have_header("sys/epoll.h")
19
+ $defs << "-DEV_USE_KQUEUE" if have_header("sys/event.h") && have_header("sys/queue.h")
20
+ $defs << "-DEV_USE_PORT" if have_type("port_event_t", "port.h")
19
21
  $defs << "-DHAVE_SYS_RESOURCE_H" if have_header("sys/resource.h")
20
22
 
21
- CONFIG["optflags"] << " -fno-strict-aliasing"
23
+ CONFIG["optflags"] << " -fno-strict-aliasing" unless RUBY_PLATFORM =~ /mswin/
22
24
 
23
25
  dir_config "nio4r_ext"
24
26
  create_makefile "nio4r_ext"
25
-
26
- # win32 needs to link in "just the right order" for some reason or
27
- # ioctlsocket will be mapped to an [inverted] ruby specific version.
28
- if RUBY_PLATFORM =~ /mingw|win32/
29
- makefile_contents = File.read "Makefile"
30
-
31
- makefile_contents.gsub! "DLDFLAGS = ", "DLDFLAGS = -export-all "
32
-
33
- makefile_contents.gsub! "LIBS = $(LIBRUBYARG_SHARED)", "LIBS = -lws2_32 $(LIBRUBYARG_SHARED)"
34
- File.open("Makefile", "w") { |f| f.write makefile_contents }
35
- end
@@ -92,7 +92,7 @@ static VALUE NIO_Monitor_initialize(VALUE self, VALUE io, VALUE interests, VALUE
92
92
  monitor->interests = EV_READ | EV_WRITE;
93
93
  } else {
94
94
  rb_raise(rb_eArgError, "invalid event type %s (must be :r, :w, or :rw)",
95
- RSTRING_PTR(rb_funcall(interests, rb_intern("inspect"), 0, 0)));
95
+ RSTRING_PTR(rb_funcall(interests, rb_intern("inspect"), 0)));
96
96
  }
97
97
 
98
98
  GetOpenFile(rb_convert_type(io, T_FILE, "IO", "to_io"), fptr);
@@ -258,7 +258,7 @@ static int NIO_Monitor_symbol2interest(VALUE interests)
258
258
  return EV_READ | EV_WRITE;
259
259
  } else {
260
260
  rb_raise(rb_eArgError, "invalid interest type %s (must be :r, :w, or :rw)",
261
- RSTRING_PTR(rb_funcall(interests, rb_intern("inspect"), 0, 0)));
261
+ RSTRING_PTR(rb_funcall(interests, rb_intern("inspect"), 0)));
262
262
  }
263
263
  }
264
264
 
@@ -163,10 +163,8 @@ public class ByteBuffer extends RubyObject {
163
163
 
164
164
  @JRubyMethod(name = "<<")
165
165
  public IRubyObject put(ThreadContext context, IRubyObject str) {
166
- String string = str.asJavaString();
167
-
168
166
  try {
169
- this.byteBuffer.put(string.getBytes());
167
+ this.byteBuffer.put(str.convertToString().getByteList().bytes());
170
168
  } catch(BufferOverflowException e) {
171
169
  throw ByteBuffer.newOverflowError(context, "buffer is full");
172
170
  }
@@ -102,12 +102,16 @@ public class Monitor extends RubyObject {
102
102
 
103
103
  @JRubyMethod
104
104
  public IRubyObject readiness(ThreadContext context) {
105
+ if(!key.isValid())
106
+ return this.interests;
105
107
  return Nio4r.interestOpsToSymbol(context.getRuntime(), key.readyOps());
106
108
  }
107
109
 
108
110
  @JRubyMethod(name = "readable?")
109
111
  public IRubyObject isReadable(ThreadContext context) {
110
112
  Ruby runtime = context.getRuntime();
113
+ if (!this.key.isValid())
114
+ return runtime.getTrue();
111
115
  int readyOps = this.key.readyOps();
112
116
 
113
117
  if((readyOps & SelectionKey.OP_READ) != 0 || (readyOps & SelectionKey.OP_ACCEPT) != 0) {
@@ -120,6 +124,8 @@ public class Monitor extends RubyObject {
120
124
  @JRubyMethod(name = {"writable?", "writeable?"})
121
125
  public IRubyObject writable(ThreadContext context) {
122
126
  Ruby runtime = context.getRuntime();
127
+ if (!this.key.isValid())
128
+ return runtime.getTrue();
123
129
  int readyOps = this.key.readyOps();
124
130
 
125
131
  if((readyOps & SelectionKey.OP_WRITE) != 0 || (readyOps & SelectionKey.OP_CONNECT) != 0) {
@@ -7,6 +7,7 @@ import java.io.IOException;
7
7
  import java.nio.channels.Channel;
8
8
  import java.nio.channels.SelectableChannel;
9
9
  import java.nio.channels.SelectionKey;
10
+ import java.nio.channels.CancelledKeyException;
10
11
 
11
12
  import org.jruby.Ruby;
12
13
  import org.jruby.RubyArray;
@@ -30,8 +31,23 @@ public class Selector extends RubyObject {
30
31
  super(ruby, rubyClass);
31
32
  }
32
33
 
34
+ @JRubyMethod(meta = true)
35
+ public static IRubyObject backends(ThreadContext context, IRubyObject self) {
36
+ return context.runtime.newArray(context.runtime.newSymbol("java"));
37
+ }
38
+
33
39
  @JRubyMethod
34
40
  public IRubyObject initialize(ThreadContext context) {
41
+ initialize(context, context.runtime.newSymbol("java"));
42
+ return context.nil;
43
+ }
44
+
45
+ @JRubyMethod
46
+ public IRubyObject initialize(ThreadContext context, IRubyObject backend) {
47
+ if(backend != context.runtime.newSymbol("java")) {
48
+ throw context.runtime.newArgumentError(":java is the only supported backend");
49
+ }
50
+
35
51
  this.cancelledKeys = new HashMap<SelectableChannel,SelectionKey>();
36
52
  this.wakeupFired = false;
37
53
 
@@ -193,6 +209,7 @@ public class Selector extends RubyObject {
193
209
  while(selectedKeys.hasNext()) {
194
210
  SelectionKey key = (SelectionKey)selectedKeys.next();
195
211
  processKey(key);
212
+
196
213
  selectedKeys.remove();
197
214
 
198
215
  if(block.isGiven()) {
@@ -254,7 +271,7 @@ public class Selector extends RubyObject {
254
271
  // Remove connect interest from connected sockets
255
272
  // See: http://stackoverflow.com/questions/204186/java-nio-select-returns-without-selected-keys-why
256
273
  private void processKey(SelectionKey key) {
257
- if((key.readyOps() & SelectionKey.OP_CONNECT) != 0) {
274
+ if(key.isValid() && (key.readyOps() & SelectionKey.OP_CONNECT) != 0) {
258
275
  int interestOps = key.interestOps();
259
276
 
260
277
  interestOps &= ~SelectionKey.OP_CONNECT;
@@ -27,8 +27,11 @@ static void NIO_Selector_mark(struct NIO_Selector *loop);
27
27
  static void NIO_Selector_shutdown(struct NIO_Selector *selector);
28
28
  static void NIO_Selector_free(struct NIO_Selector *loop);
29
29
 
30
- /* Methods */
31
- static VALUE NIO_Selector_initialize(VALUE self);
30
+ /* Class methods */
31
+ static VALUE NIO_Selector_supported_backends(VALUE klass);
32
+
33
+ /* Instance methods */
34
+ static VALUE NIO_Selector_initialize(int argc, VALUE *argv, VALUE self);
32
35
  static VALUE NIO_Selector_backend(VALUE self);
33
36
  static VALUE NIO_Selector_register(VALUE self, VALUE selectable, VALUE interest);
34
37
  static VALUE NIO_Selector_deregister(VALUE self, VALUE io);
@@ -65,7 +68,8 @@ void Init_NIO_Selector()
65
68
  cNIO_Selector = rb_define_class_under(mNIO, "Selector", rb_cObject);
66
69
  rb_define_alloc_func(cNIO_Selector, NIO_Selector_allocate);
67
70
 
68
- rb_define_method(cNIO_Selector, "initialize", NIO_Selector_initialize, 0);
71
+ rb_define_singleton_method(cNIO_Selector, "backends", NIO_Selector_supported_backends, 0);
72
+ rb_define_method(cNIO_Selector, "initialize", NIO_Selector_initialize, -1);
69
73
  rb_define_method(cNIO_Selector, "backend", NIO_Selector_backend, 0);
70
74
  rb_define_method(cNIO_Selector, "register", NIO_Selector_register, 2);
71
75
  rb_define_method(cNIO_Selector, "deregister", NIO_Selector_deregister, 1);
@@ -102,7 +106,9 @@ static VALUE NIO_Selector_allocate(VALUE klass)
102
106
  }
103
107
 
104
108
  selector = (struct NIO_Selector *)xmalloc(sizeof(struct NIO_Selector));
105
- selector->ev_loop = ev_loop_new(0);
109
+
110
+ /* Defer initializing the loop to #initialize */
111
+ selector->ev_loop = 0;
106
112
 
107
113
  ev_init(&selector->timer, NIO_Selector_timeout_callback);
108
114
 
@@ -112,8 +118,6 @@ static VALUE NIO_Selector_allocate(VALUE klass)
112
118
  ev_io_init(&selector->wakeup, NIO_Selector_wakeup_callback, selector->wakeup_reader, EV_READ);
113
119
  selector->wakeup.data = (void *)selector;
114
120
 
115
- ev_io_start(selector->ev_loop, &selector->wakeup);
116
-
117
121
  selector->closed = selector->selecting = selector->wakeup_fired = selector->ready_count = 0;
118
122
  selector->ready_array = Qnil;
119
123
 
@@ -154,12 +158,83 @@ static void NIO_Selector_free(struct NIO_Selector *selector)
154
158
  xfree(selector);
155
159
  }
156
160
 
161
+ /* Return an array of symbols for supported backends */
162
+ static VALUE NIO_Selector_supported_backends(VALUE klass) {
163
+ unsigned int backends = ev_supported_backends();
164
+ VALUE result = rb_ary_new();
165
+
166
+ if(backends & EVBACKEND_EPOLL) {
167
+ rb_ary_push(result, ID2SYM(rb_intern("epoll")));
168
+ }
169
+
170
+ if(backends & EVBACKEND_POLL) {
171
+ rb_ary_push(result, ID2SYM(rb_intern("poll")));
172
+ }
173
+
174
+ if(backends & EVBACKEND_KQUEUE) {
175
+ rb_ary_push(result, ID2SYM(rb_intern("kqueue")));
176
+ }
177
+
178
+ if(backends & EVBACKEND_SELECT) {
179
+ rb_ary_push(result, ID2SYM(rb_intern("select")));
180
+ }
181
+
182
+ if(backends & EVBACKEND_PORT) {
183
+ rb_ary_push(result, ID2SYM(rb_intern("port")));
184
+ }
185
+
186
+ return result;
187
+ }
188
+
157
189
  /* Create a new selector. This is more or less the pure Ruby version
158
190
  translated into an MRI cext */
159
- static VALUE NIO_Selector_initialize(VALUE self)
191
+ static VALUE NIO_Selector_initialize(int argc, VALUE *argv, VALUE self)
160
192
  {
193
+ ID backend_id;
194
+ VALUE backend;
161
195
  VALUE lock;
162
196
 
197
+ struct NIO_Selector *selector;
198
+ unsigned int flags = 0;
199
+
200
+ Data_Get_Struct(self, struct NIO_Selector, selector);
201
+
202
+ rb_scan_args(argc, argv, "01", &backend);
203
+
204
+ if(backend != Qnil) {
205
+ if(!rb_ary_includes(NIO_Selector_supported_backends(CLASS_OF(self)), backend)) {
206
+ rb_raise(rb_eArgError, "unsupported backend: %s",
207
+ RSTRING_PTR(rb_funcall(backend, rb_intern("inspect"), 0)));
208
+ }
209
+
210
+ backend_id = SYM2ID(backend);
211
+
212
+ if(backend_id == rb_intern("epoll")) {
213
+ flags = EVBACKEND_EPOLL;
214
+ } else if(backend_id == rb_intern("poll")) {
215
+ flags = EVBACKEND_POLL;
216
+ } else if(backend_id == rb_intern("kqueue")) {
217
+ flags = EVBACKEND_KQUEUE;
218
+ } else if(backend_id == rb_intern("select")) {
219
+ flags = EVBACKEND_SELECT;
220
+ } else if(backend_id == rb_intern("port")) {
221
+ flags = EVBACKEND_PORT;
222
+ } else {
223
+ rb_raise(rb_eArgError, "unsupported backend: %s",
224
+ RSTRING_PTR(rb_funcall(backend, rb_intern("inspect"), 0)));
225
+ }
226
+ }
227
+
228
+ /* Ensure the selector loop has not yet been initialized */
229
+ assert(!selector->ev_loop);
230
+
231
+ selector->ev_loop = ev_loop_new(flags);
232
+ if(!selector->ev_loop) {
233
+ rb_raise(rb_eIOError, "error initializing event loop");
234
+ }
235
+
236
+ ev_io_start(selector->ev_loop, &selector->wakeup);
237
+
163
238
  rb_ivar_set(self, rb_intern("selectables"), rb_hash_new());
164
239
  rb_ivar_set(self, rb_intern("lock_holder"), Qnil);
165
240
 
@@ -204,7 +279,7 @@ static VALUE NIO_Selector_synchronize(VALUE self, VALUE (*func)(VALUE *args), VA
204
279
 
205
280
  if(lock_holder != current_thread) {
206
281
  lock = rb_ivar_get(self, rb_intern("lock"));
207
- rb_funcall(lock, rb_intern("lock"), 0, 0);
282
+ rb_funcall(lock, rb_intern("lock"), 0);
208
283
  rb_ivar_set(self, rb_intern("lock_holder"), current_thread);
209
284
 
210
285
  /* We've acquired the lock, so ensure we unlock it */
@@ -223,7 +298,7 @@ static VALUE NIO_Selector_unlock(VALUE self)
223
298
  rb_ivar_set(self, rb_intern("lock_holder"), Qnil);
224
299
 
225
300
  lock = rb_ivar_get(self, rb_intern("lock"));
226
- rb_funcall(lock, rb_intern("unlock"), 0, 0);
301
+ rb_funcall(lock, rb_intern("unlock"), 0);
227
302
 
228
303
  return Qnil;
229
304
  }
data/lib/nio.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "thread"
4
3
  require "socket"
5
4
  require "nio/version"
6
5
 
@@ -19,7 +18,7 @@ if ENV["NIO4R_PURE"] == "true" || (Gem.win_platform? && !defined?(JRUBY_VERSION)
19
18
  require "nio/monitor"
20
19
  require "nio/selector"
21
20
  require "nio/bytebuffer"
22
- NIO::ENGINE = "ruby"
21
+ NIO::ENGINE = "ruby".freeze
23
22
  else
24
23
  require "nio4r_ext"
25
24
 
@@ -27,8 +26,8 @@ else
27
26
  require "java"
28
27
  require "jruby"
29
28
  org.nio4r.Nio4r.new.load(JRuby.runtime, false)
30
- NIO::ENGINE = "java"
29
+ NIO::ENGINE = "java".freeze
31
30
  else
32
- NIO::ENGINE = "libev"
31
+ NIO::ENGINE = "libev".freeze
33
32
  end
34
33
  end
@@ -48,10 +48,8 @@ module NIO
48
48
  raise ArgumentError, "negative position given" if new_position < 0
49
49
  raise ArgumentError, "specified position exceeds capacity" if new_position > @capacity
50
50
 
51
+ @mark = nil if @mark && @mark > new_position
51
52
  @position = new_position
52
- @mark = nil if @mark && @mark > @position
53
-
54
- new_position
55
53
  end
56
54
 
57
55
  # Set the limit to the given value. New limit must be less than capacity.
@@ -65,11 +63,9 @@ module NIO
65
63
  raise ArgumentError, "negative limit given" if new_limit < 0
66
64
  raise ArgumentError, "specified limit exceeds capacity" if new_limit > @capacity
67
65
 
66
+ @position = new_limit if @position > new_limit
67
+ @mark = nil if @mark && @mark > new_limit
68
68
  @limit = new_limit
69
- @position = new_limit if @position > @limit
70
- @mark = nil if @mark && @mark > @limit
71
-
72
- new_limit
73
69
  end
74
70
 
75
71
  # Number of bytes remaining in the buffer before the limit
@@ -115,15 +111,22 @@ module NIO
115
111
 
116
112
  # Add a String to the buffer
117
113
  #
114
+ # @param str [#to_str] data to add to the buffer
115
+ #
116
+ # @raise [TypeError] given a non-string type
118
117
  # @raise [NIO::ByteBuffer::OverflowError] buffer is full
119
118
  #
120
119
  # @return [self]
121
- def <<(str)
120
+ def put(str)
121
+ raise TypeError, "expected String, got #{str.class}" unless str.respond_to?(:to_str)
122
+ str = str.to_str
123
+
122
124
  raise OverflowError, "buffer is full" if str.length > @limit - @position
123
125
  @buffer[@position...str.length] = str
124
126
  @position += str.length
125
127
  self
126
128
  end
129
+ alias << put
127
130
 
128
131
  # Perform a non-blocking read from the given IO object into the buffer
129
132
  # Reads as much data as is immediately available and returns
@@ -31,7 +31,7 @@ module NIO
31
31
  # @return [Symbol] new interests
32
32
  def interests=(interests)
33
33
  raise EOFError, "monitor is closed" if closed?
34
- raise ArgumentError, "bad interests: #{interests}" unless [:r, :w, :rw].include?(interests)
34
+ raise ArgumentError, "bad interests: #{interests}" unless %i[r w rw].include?(interests)
35
35
 
36
36
  @interests = interests
37
37
  end
@@ -5,8 +5,17 @@ require "set"
5
5
  module NIO
6
6
  # Selectors monitor IO objects for events of interest
7
7
  class Selector
8
+ # Return supported backends as symbols
9
+ #
10
+ # See `#backend` method definition for all possible backends
11
+ def self.backends
12
+ [:ruby]
13
+ end
14
+
8
15
  # Create a new NIO::Selector
9
- def initialize
16
+ def initialize(backend = :ruby)
17
+ raise ArgumentError, "unsupported backend: #{backend}" unless backend == :ruby
18
+
10
19
  @selectables = {}
11
20
  @lock = Mutex.new
12
21
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module NIO
4
- VERSION = "2.1.0"
4
+ VERSION = "2.2.0".freeze
5
5
  end
@@ -29,6 +29,6 @@ Gem::Specification.new do |spec|
29
29
  spec.extensions = ["ext/nio4r/extconf.rb"]
30
30
  end
31
31
 
32
- spec.add_development_dependency "rake"
33
32
  spec.add_development_dependency "bundler"
33
+ spec.add_development_dependency "rake"
34
34
  end
@@ -20,13 +20,13 @@ RSpec.describe "NIO acceptables" do
20
20
  let(:port) { next_available_tcp_port }
21
21
 
22
22
  let :acceptable_subject do
23
- server = TCPServer.new("localhost", port)
24
- TCPSocket.open("localhost", port)
23
+ server = TCPServer.new("127.0.0.1", port)
24
+ TCPSocket.open("127.0.0.1", port)
25
25
  server
26
26
  end
27
27
 
28
28
  let :unacceptable_subject do
29
- TCPServer.new("localhost", port + 1)
29
+ TCPServer.new("127.0.0.1", port + 1)
30
30
  end
31
31
 
32
32
  it_behaves_like "an NIO acceptable"
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "spec_helper"
4
4
 
5
+ # rubocop:disable Metrics/BlockLength
5
6
  RSpec.describe NIO::ByteBuffer do
6
7
  let(:capacity) { 256 }
7
8
  let(:example_string) { "Testing 1 2 3..." }
@@ -179,6 +180,11 @@ RSpec.describe NIO::ByteBuffer do
179
180
  expect(bytebuffer.limit).to eq capacity
180
181
  end
181
182
 
183
+ it "raises TypeError if given a non-String type" do
184
+ expect { bytebuffer << 42 }.to raise_error(TypeError)
185
+ expect { bytebuffer << nil }.to raise_error(TypeError)
186
+ end
187
+
182
188
  it "raises NIO::ByteBuffer::OverflowError if the buffer is full" do
183
189
  bytebuffer << "X" * (capacity - 1)
184
190
  expect { bytebuffer << "X" }.not_to raise_error
@@ -281,7 +287,7 @@ RSpec.describe NIO::ByteBuffer do
281
287
  end
282
288
 
283
289
  context "I/O" do
284
- let(:addr) { "localhost" }
290
+ let(:addr) { "127.0.0.1" }
285
291
  let(:port) { next_available_tcp_port }
286
292
  let(:server) { TCPServer.new(addr, port) }
287
293
  let(:client) { TCPSocket.new(addr, port) }
@@ -347,3 +353,4 @@ RSpec.describe NIO::ByteBuffer do
347
353
  end
348
354
  end
349
355
  end
356
+ # rubocop:enable Metrics/BlockLength
@@ -4,7 +4,7 @@ require "spec_helper"
4
4
  require "socket"
5
5
 
6
6
  RSpec.describe NIO::Monitor do
7
- let(:addr) { "localhost" }
7
+ let(:addr) { "127.0.0.1" }
8
8
  let(:port) { next_available_tcp_port }
9
9
 
10
10
  let(:reader) { TCPServer.new(addr, port) }
@@ -27,12 +27,17 @@ RSpec.describe "IO.pipe" do
27
27
  # will throw EAGAIN if there is too little space to write the string
28
28
  # TODO: Use FFI to lookup the platform-specific size of PIPE_BUF
29
29
  str = "JUNK IN THE TUBES" * 10_000
30
+ cntr = 0
30
31
  begin
31
32
  pipe.write_nonblock str
32
- _, writers = select [], [pipe], [], 0
33
+ cntr += 1
34
+ t = select [], [pipe], [], 0
33
35
  rescue Errno::EPIPE
34
36
  break
35
- end while writers && writers.include?(pipe)
37
+ rescue IO::EWOULDBLOCKWaitWritable
38
+ skip "windows - can't test due to 'select' not showing correct status"
39
+ break
40
+ end while t && t[1].include?(pipe) && cntr < 20
36
41
 
37
42
  pipe
38
43
  end
@@ -4,23 +4,23 @@ require "spec_helper"
4
4
  require "openssl"
5
5
 
6
6
  RSpec.describe OpenSSL::SSL::SSLSocket do
7
- let(:addr) { "localhost" }
7
+ let(:addr) { "127.0.0.1" }
8
8
  let(:port) { next_available_tcp_port }
9
9
 
10
10
  let(:ssl_key) { OpenSSL::PKey::RSA.new(1024) }
11
11
 
12
12
  let(:ssl_cert) do
13
- name = OpenSSL::X509::Name.new([%w(CN localhost)])
13
+ name = OpenSSL::X509::Name.new([%w[CN 127.0.0.1]])
14
14
  OpenSSL::X509::Certificate.new.tap do |cert|
15
15
  cert.version = 2
16
16
  cert.serial = 1
17
17
  cert.issuer = name
18
18
  cert.subject = name
19
19
  cert.not_before = Time.now
20
- cert.not_after = Time.now + (365 * 24 * 60 * 60)
20
+ cert.not_after = Time.now + (7 * 24 * 60 * 60)
21
21
  cert.public_key = ssl_key.public_key
22
22
 
23
- cert.sign(ssl_key, OpenSSL::Digest::SHA1.new)
23
+ cert.sign(ssl_key, OpenSSL::Digest::SHA256.new)
24
24
  end
25
25
  end
26
26
 
@@ -111,14 +111,15 @@ RSpec.describe OpenSSL::SSL::SSLSocket do
111
111
  ssl_peer.accept
112
112
  thread.join
113
113
 
114
+ cntr = 0
114
115
  begin
115
- _, writers = select [], [ssl_client], [], 0
116
116
  count = ssl_client.write_nonblock "X" * 1024
117
117
  expect(count).not_to eq(0)
118
+ cntr += 1
119
+ t = select [], [ssl_client], [], 0
118
120
  rescue IO::WaitReadable, IO::WaitWritable
119
121
  pending "SSL will report writable but not accept writes"
120
- raise if writers.include? ssl_client
121
- end while writers && writers.include?(ssl_client)
122
+ end while t && t[1].include?(ssl_client) && cntr < 30
122
123
 
123
124
  # I think the kernel might manage to drain its buffer a bit even after
124
125
  # the socket first goes unwritable. Attempt to sleep past this and then
@@ -141,8 +142,6 @@ RSpec.describe OpenSSL::SSL::SSLSocket do
141
142
  end
142
143
 
143
144
  let :pair do
144
- pending "figure out why newly created sockets are selecting readable immediately"
145
-
146
145
  server = TCPServer.new(addr, port)
147
146
  client = TCPSocket.new(addr, port)
148
147
  peer = server.accept
@@ -19,9 +19,7 @@ RSpec.describe TCPSocket do
19
19
  sock = TCPSocket.new(addr, port)
20
20
 
21
21
  # Sanity check to make sure we actually produced an unreadable socket
22
- if select([sock], [], [], 0)
23
- pending "Failed to produce an unreadable socket"
24
- end
22
+ pending "Failed to produce an unreadable socket" if select([sock], [], [], 0)
25
23
 
26
24
  sock
27
25
  end
@@ -57,9 +55,7 @@ RSpec.describe TCPSocket do
57
55
  end
58
56
 
59
57
  # Sanity check to make sure we actually produced an unwritable socket
60
- if select([], [sock], [], 0)
61
- pending "Failed to produce an unwritable socket"
62
- end
58
+ pending "Failed to produce an unwritable socket" if select([], [sock], [], 0)
63
59
 
64
60
  sock
65
61
  end
@@ -2,27 +2,37 @@
2
2
 
3
3
  require "spec_helper"
4
4
 
5
- RSpec.describe UDPSocket do
5
+ RSpec.describe UDPSocket, if: !defined?(JRUBY_VERSION) do
6
6
  let(:udp_port) { 23_456 }
7
7
 
8
8
  let :readable_subject do
9
9
  sock = UDPSocket.new
10
- sock.bind("localhost", udp_port)
10
+ sock.bind("127.0.0.1", udp_port)
11
11
 
12
12
  peer = UDPSocket.new
13
- peer.send("hi there", 0, "localhost", udp_port)
13
+ peer.send("hi there", 0, "127.0.0.1", udp_port)
14
14
 
15
15
  sock
16
16
  end
17
17
 
18
18
  let :unreadable_subject do
19
19
  sock = UDPSocket.new
20
- sock.bind("localhost", udp_port + 1)
20
+ sock.bind("127.0.0.1", udp_port + 1)
21
21
  sock
22
22
  end
23
23
 
24
24
  let :writable_subject do
25
- pending "come up with a writable UDPSocket example"
25
+ peer = UDPSocket.new
26
+ peer.connect "127.0.0.1", udp_port
27
+ cntr = 0
28
+ begin
29
+ peer.send("X" * 1024, 0)
30
+ cntr += 1
31
+ t = select [], [peer], [], 0
32
+ rescue Errno::ECONNREFUSED => ex
33
+ skip "Couln't make writable UDPSocket subject: #{ex.class}: #{ex}"
34
+ end while t && t[1].include?(peer) && cntr < 5
35
+ peer
26
36
  end
27
37
 
28
38
  let :unwritable_subject do
@@ -8,11 +8,35 @@ require "timeout"
8
8
  # the tests
9
9
  TIMEOUT_PRECISION = 0.1
10
10
 
11
+ # rubocop:disable Metrics/BlockLength
11
12
  RSpec.describe NIO::Selector do
12
13
  let(:pair) { IO.pipe }
13
14
  let(:reader) { pair.first }
14
15
  let(:writer) { pair.last }
15
16
 
17
+ context ".backends" do
18
+ it "knows all supported backends" do
19
+ expect(described_class.backends).to be_a Array
20
+ expect(described_class.backends.first).to be_a Symbol
21
+ end
22
+ end
23
+
24
+ context "#initialize" do
25
+ it "allows explicitly specifying a backend" do
26
+ backend = described_class.backends.first
27
+ selector = described_class.new(backend)
28
+ expect(selector.backend).to eq backend
29
+ end
30
+
31
+ it "raises ArgumentError if given an invalid backend" do
32
+ expect { described_class.new(:derp) }.to raise_error ArgumentError
33
+ end
34
+
35
+ it "raises TypeError if given a non-Symbol parameter" do
36
+ expect { described_class.new(42).to raise_error TypeError }
37
+ end
38
+ end
39
+
16
40
  context "backend" do
17
41
  it "knows its backend" do
18
42
  expect(subject.backend).to be_a Symbol
@@ -191,3 +215,4 @@ RSpec.describe NIO::Selector do
191
215
  expect(subject).to be_closed
192
216
  end
193
217
  end
218
+ # rubocop:enable Metrics/BlockLength
@@ -20,8 +20,8 @@ def next_available_tcp_port
20
20
  $current_tcp_port += 1
21
21
 
22
22
  begin
23
- sock = Timeout.timeout(1) { TCPSocket.new("localhost", $current_tcp_port) }
24
- rescue Errno::ECONNREFUSED
23
+ sock = Timeout.timeout(0.5) { TCPSocket.new("127.0.0.1", $current_tcp_port) }
24
+ rescue Errno::ECONNREFUSED, Timeout::Error
25
25
  break $current_tcp_port
26
26
  end
27
27
 
@@ -34,9 +34,6 @@ RSpec.shared_context "an NIO selectable stream" do
34
34
  let(:peer) { pair.last }
35
35
 
36
36
  it "selects readable when the other end closes" do
37
- # hax: this test is broken for OpenSSL sockets
38
- skip "broken for SSL ;_;" if peer.is_a? OpenSSL::SSL::SSLSocket
39
-
40
37
  monitor = selector.register(stream, :r)
41
38
  expect(selector.select(0)).to be_nil
42
39
 
@@ -57,4 +54,12 @@ RSpec.shared_context "an NIO bidirectional stream" do
57
54
  expect(m.readiness).to eq(:rw)
58
55
  end
59
56
  end
57
+ it "keeps readiness after the selectable has been closed" do
58
+ selector.register(readable_subject, :rw)
59
+ selector.select(0) do |m|
60
+ expect(m.readiness).to eq(:rw)
61
+ readable_subject.close
62
+ expect(m.readiness).to eq(:rw)
63
+ end
64
+ end
60
65
  end
metadata CHANGED
@@ -1,17 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nio4r
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tony Arcieri
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-05-28 00:00:00.000000000 Z
11
+ date: 2017-12-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: rake
14
+ name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">="
@@ -25,7 +25,7 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: bundler
28
+ name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
@@ -50,7 +50,6 @@ files:
50
50
  - ".gitignore"
51
51
  - ".rspec"
52
52
  - ".rubocop.yml"
53
- - ".ruby-version"
54
53
  - ".travis.yml"
55
54
  - CHANGES.md
56
55
  - Gemfile
@@ -58,6 +57,7 @@ files:
58
57
  - LICENSE.txt
59
58
  - README.md
60
59
  - Rakefile
60
+ - appveyor.yml
61
61
  - examples/echo_server.rb
62
62
  - ext/libev/Changes
63
63
  - ext/libev/LICENSE
@@ -125,7 +125,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
125
125
  version: '0'
126
126
  requirements: []
127
127
  rubyforge_project:
128
- rubygems_version: 2.6.11
128
+ rubygems_version: 2.7.3
129
129
  signing_key:
130
130
  specification_version: 4
131
131
  summary: New IO for Ruby
@@ -1 +0,0 @@
1
- 2.4.0