nio4r 2.1.0 → 2.2.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
- 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