io-like 0.3.1 → 0.4.0.pre1

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 (71) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +1 -1
  3. data/NEWS.md +14 -1
  4. data/README.md +75 -94
  5. data/lib/io/like.rb +1916 -1314
  6. data/lib/io/like_helpers/abstract_io.rb +512 -0
  7. data/lib/io/like_helpers/blocking_io.rb +86 -0
  8. data/lib/io/like_helpers/buffered_io.rb +555 -0
  9. data/lib/io/like_helpers/character_io/basic_reader.rb +122 -0
  10. data/lib/io/like_helpers/character_io/converter_reader.rb +252 -0
  11. data/lib/io/like_helpers/character_io.rb +529 -0
  12. data/lib/io/like_helpers/delegated_io.rb +250 -0
  13. data/lib/io/like_helpers/duplexed_io.rb +259 -0
  14. data/lib/io/like_helpers/io.rb +21 -0
  15. data/lib/io/like_helpers/io_wrapper.rb +290 -0
  16. data/lib/io/like_helpers/pipeline.rb +77 -0
  17. data/lib/io/like_helpers/ruby_facts.rb +33 -0
  18. data/lib/io/like_helpers.rb +14 -0
  19. metadata +107 -224
  20. data/.yardopts +0 -1
  21. data/Rakefile +0 -228
  22. data/ruby.1.8.mspec +0 -7
  23. data/spec/binmode_spec.rb +0 -29
  24. data/spec/close_read_spec.rb +0 -64
  25. data/spec/close_spec.rb +0 -36
  26. data/spec/close_write_spec.rb +0 -61
  27. data/spec/closed_spec.rb +0 -16
  28. data/spec/each_byte_spec.rb +0 -38
  29. data/spec/each_line_spec.rb +0 -11
  30. data/spec/each_spec.rb +0 -11
  31. data/spec/eof_spec.rb +0 -11
  32. data/spec/fixtures/classes.rb +0 -96
  33. data/spec/fixtures/gets.txt +0 -9
  34. data/spec/fixtures/numbered_lines.txt +0 -5
  35. data/spec/fixtures/one_byte.txt +0 -1
  36. data/spec/fixtures/paragraphs.txt +0 -7
  37. data/spec/fixtures/readlines.txt +0 -6
  38. data/spec/flush_spec.rb +0 -8
  39. data/spec/getc_spec.rb +0 -44
  40. data/spec/gets_spec.rb +0 -212
  41. data/spec/isatty_spec.rb +0 -6
  42. data/spec/lineno_spec.rb +0 -84
  43. data/spec/output_spec.rb +0 -47
  44. data/spec/pos_spec.rb +0 -53
  45. data/spec/print_spec.rb +0 -97
  46. data/spec/printf_spec.rb +0 -24
  47. data/spec/putc_spec.rb +0 -57
  48. data/spec/puts_spec.rb +0 -99
  49. data/spec/read_spec.rb +0 -162
  50. data/spec/readchar_spec.rb +0 -49
  51. data/spec/readline_spec.rb +0 -60
  52. data/spec/readlines_spec.rb +0 -140
  53. data/spec/readpartial_spec.rb +0 -92
  54. data/spec/rewind_spec.rb +0 -56
  55. data/spec/seek_spec.rb +0 -72
  56. data/spec/shared/each.rb +0 -204
  57. data/spec/shared/eof.rb +0 -116
  58. data/spec/shared/pos.rb +0 -39
  59. data/spec/shared/tty.rb +0 -12
  60. data/spec/shared/write.rb +0 -53
  61. data/spec/sync_spec.rb +0 -56
  62. data/spec/sysread_spec.rb +0 -87
  63. data/spec/sysseek_spec.rb +0 -68
  64. data/spec/syswrite_spec.rb +0 -60
  65. data/spec/tell_spec.rb +0 -7
  66. data/spec/to_io_spec.rb +0 -19
  67. data/spec/tty_spec.rb +0 -6
  68. data/spec/ungetc_spec.rb +0 -118
  69. data/spec/write_spec.rb +0 -61
  70. data/spec_helper.rb +0 -49
  71. /data/{LICENSE-rubyspec → rubyspec/LICENSE} +0 -0
@@ -0,0 +1,512 @@
1
+ # frozen_string_literal: true
2
+
3
+ class IO; module LikeHelpers
4
+
5
+ ##
6
+ # @abstract It defines the structure expected of low level streams and largely
7
+ # reflects the structure of the IO class, but most methods raise
8
+ # NotImplementedError. Only the most basic semantics for stream opening and
9
+ # closing are provided.
10
+ class AbstractIO
11
+ ##
12
+ # @overload open(*args, **kwargs)
13
+ # Equivalent to {#initialize}.
14
+ #
15
+ # @return a new instances of this class
16
+ #
17
+ # @overload open(*args, **kwargs)
18
+ # Yields the new instance of this class to the block, ensures the instance
19
+ # is closed once the block completes, and returns the result of the block.
20
+ #
21
+ # @yieldparam stream an instance of this class
22
+ #
23
+ # @return [block result]
24
+ #
25
+ # @param args a list of arguments passed to the initializer of this class
26
+ # @param kwargs a list of keyword arguments passed to the initializer of this
27
+ # class
28
+ def self.open(*args, **kwargs)
29
+ io = new(*args, **kwargs)
30
+ return io unless block_given?
31
+
32
+ begin
33
+ yield(io)
34
+ ensure
35
+ while Symbol === io.close do
36
+ warn 'warning: waiting for nonblocking close to complete at the end of the open method'
37
+ # A wait timeout is used in order to allow a retry in case the stream
38
+ # was closed in another thread while waiting.
39
+ io.wait(IO::READABLE | IO::WRITABLE, 1)
40
+ end
41
+ end
42
+ end
43
+
44
+ ##
45
+ # Creates a new instance of this class.
46
+ #
47
+ # @param kwargs [Hash] only provided for compatibility with .open on Ruby 2.6
48
+ #
49
+ # @todo Remove explicit `kwargs` parameter when Ruby 2.6 support is dropped.
50
+ def initialize(**kwargs)
51
+ @closed = false
52
+ end
53
+
54
+ ##
55
+ # Announces an intention to access data from the stream in a specific pattern.
56
+ #
57
+ # This method is a no-op if not implemented. If `offset` and `len` are both
58
+ # `0`, then the entire stream is affected.
59
+ #
60
+ # | `advice` | Meaning |
61
+ # | -------- | ------- |
62
+ # | `:normal` | No advice given; default assumption for the stream. |
63
+ # | `:sequential` | The data will be read sequentially from lower offsets to higher ones. |
64
+ # | `:random` | The data will be accessed in random order. |
65
+ # | `:willneed` | The data will be accessed in the near future. |
66
+ # | `:dontneed` | The data will not be accessed in the near future. |
67
+ # | `:noreuse` | The data will only be accessed once. |
68
+ #
69
+ # See `posix_fadvise(2)` for more details.
70
+ #
71
+ # @param advice [Symbol] the access pattern
72
+ # @param offset [Integer] the starting location of the data that will be
73
+ # accessed
74
+ # @param len [Integer] the length of the data that will be accessed
75
+ #
76
+ # @return [nil]
77
+ #
78
+ # @raise [IOError] if the stream is closed
79
+ def advise(advice, offset = 0, len = 0)
80
+ nil
81
+ end
82
+
83
+ ##
84
+ # Closes the stream.
85
+ #
86
+ # Most operations on the stream after this method is called will result in
87
+ # IOErrors being raised.
88
+ #
89
+ # @return [nil] on success
90
+ # @return [:wait_readable, :wait_writable] if the stream is non-blocking and
91
+ # the operation would block
92
+ def close
93
+ @closed = true
94
+ nil
95
+ end
96
+
97
+ ##
98
+ # Returns `true` if this stream is closed and `false` otherwise.
99
+ #
100
+ # @return [Boolean]
101
+ def closed?
102
+ @closed
103
+ end
104
+
105
+ ##
106
+ # Sets the close-on-exec flag for the underlying file descriptor.
107
+ #
108
+ # Note that setting this to `false` can lead to file descriptor leaks in
109
+ # multithreaded applications that fork and exec or use the `system` method.
110
+ #
111
+ # @return [Boolean]
112
+ def close_on_exec=(close_on_exec)
113
+ raise NotImplementedError
114
+ end
115
+
116
+ ##
117
+ # Returns `true` if the close-on-exec flag is set for this stream and `false`
118
+ # otherwise.
119
+ #
120
+ # @return [Boolean]
121
+ def close_on_exec?
122
+ raise NotImplementedError
123
+ end
124
+
125
+ ##
126
+ # Issues low level commands to control or query the file-oriented stream upon
127
+ # which this stream is based.
128
+ #
129
+ # @param integer_cmd [Integer] passed directly to `fcntl(2)` as an operation
130
+ # identifier
131
+ # @param arg [Integer, String] passed directly to `fcntl(2)` if an Integer and
132
+ # as a binary sequence of bytes otherwise
133
+ #
134
+ # @return [Integer] the return value of `fcntl(2)` when not an error
135
+ #
136
+ # @raise [IOError] if the stream is closed
137
+ # @raise [NotImplementedError] on platforms without the `fcntl(2)` function
138
+ # @raise [SystemCallError] on system level errors
139
+ def fcntl(integer_cmd, arg)
140
+ raise NotImplementedError
141
+ end
142
+
143
+ ##
144
+ # Triggers the operating system to write any buffered metadata to disk
145
+ # immediately.
146
+ #
147
+ # The default implementation calls {#fsync}, but override this if the stream
148
+ # natively supports the equivalent of `fdatasync(2)` to offer better
149
+ # performance when only metadata needs to be flushed to disk.
150
+ #
151
+ # @return [0, nil]
152
+ def fdatasync
153
+ fsync
154
+ end
155
+
156
+ ##
157
+ # @return [Integer] the numeric file descriptor for the stream
158
+ def fileno
159
+ raise NotImplementedError
160
+ end
161
+
162
+ ##
163
+ # Triggers the operating system to write any buffered data to disk
164
+ # immediately.
165
+ #
166
+ # @return [0, nil]
167
+ def fsync
168
+ raise NotImplementedError
169
+ end
170
+
171
+ ##
172
+ # Issues low level commands to control or query the device upon which this
173
+ # stream is based.
174
+ #
175
+ # @param integer_cmd [Integer] passed directly to `ioctl(2)` as a device
176
+ # dependent request code
177
+ # @param arg [Integer, String] passed directly to `ioctl(2)` if an Integer and
178
+ # as a binary sequence of bytes otherwise
179
+ #
180
+ # @return [Integer] the return value of `ioctl(2)` when not an error
181
+ #
182
+ # @raise [IOError] if the stream is closed
183
+ # @raise [NotImplementedError] on platforms without the `ioctl(2)` function
184
+ # @raise [SystemCallError] on system level errors
185
+ def ioctl(integer_cmd, arg)
186
+ raise NotImplementedError
187
+ end
188
+
189
+ ##
190
+ # Yields `self` to the given block after setting the blocking mode as dictated
191
+ # by `nonblock`.
192
+ #
193
+ # Ensures that the original blocking mode is reinstated after yielding.
194
+ #
195
+ # @param nonblock [Boolean] sets the stream to non-blocking mode if `true` and
196
+ # blocking mode otherwise
197
+ #
198
+ # @yieldparam self [Like] this stream
199
+ #
200
+ # @return [self]
201
+ #
202
+ # @raise [IOError] if the stream is closed
203
+ def nonblock(nonblock = true)
204
+ assert_open
205
+ begin
206
+ orig_nonblock = nonblock?
207
+ self.nonblock = nonblock
208
+ yield(self)
209
+ ensure
210
+ self.nonblock = orig_nonblock
211
+ end
212
+ end
213
+
214
+ ##
215
+ # Sets the stream into either blocking or non-blocking mode.
216
+ #
217
+ # @param nonblock [Boolean] `true` for non-blocking mode, `false` otherwise
218
+ #
219
+ # @raise [IOError] if the stream is closed
220
+ def nonblock=(nonblock)
221
+ raise NotImplementedError
222
+ end
223
+
224
+ ##
225
+ # Returns whether or not the stream is in non-blocking mode.
226
+ #
227
+ # @return [true] if the stream is in non-blocking mode
228
+ # @return [false] if the stream is in blocking mode
229
+ #
230
+ # @raise [IOError] if the stream is closed
231
+ def nonblock?
232
+ raise NotImplementedError
233
+ end
234
+
235
+ ##
236
+ # @return [Integer] the number of bytes that can be read without blocking or
237
+ # `0` if unknown
238
+ #
239
+ # @raise [IOError] if the stream is not open for reading
240
+ def nread
241
+ assert_readable
242
+ 0
243
+ end
244
+
245
+ ##
246
+ # Returns the path of the file associated with this stream.
247
+ #
248
+ # @return [String]
249
+ def path
250
+ raise NotImplementedError
251
+ end
252
+
253
+ ##
254
+ # Returns the pid of the process associated with this stream.
255
+ #
256
+ # @return [Integer] if the stream is associated with a process
257
+ # @return [nil] if the stream is not associated with a process
258
+ #
259
+ # @raise [IOError] if the stream is closed
260
+ def pid
261
+ assert_open
262
+ nil
263
+ end
264
+
265
+ ##
266
+ # Reads at most `length` bytes from the stream starting at `offset` without
267
+ # modifying the read position in the stream.
268
+ #
269
+ # Note that a partial read will occur if the stream is in non-blocking mode
270
+ # and reading more bytes would block.
271
+ #
272
+ # @param length [Integer] the maximum number of bytes to read
273
+ # @param offset [Integer] the offset from the beginning of the stream at which
274
+ # to begin reading
275
+ # @param buffer [String] if provided, a buffer into which the bytes should be
276
+ # placed
277
+ # @param buffer_offset [Integer] the index at which to insert bytes into
278
+ # `buffer`
279
+ #
280
+ # @return [Integer] the number of bytes read if `buffer` is not `nil`
281
+ # @return [String] a new String containing the bytes read if `buffer` is `nil`
282
+ # or `buffer` if provided
283
+ # @return [:wait_readable, :wait_writable] if the stream is non-blocking and
284
+ # the operation would block
285
+ #
286
+ # @raise [EOFError] when reading at the end of the stream
287
+ # @raise [IOError] if the stream is not readable
288
+ def pread(length, offset, buffer: nil, buffer_offset: 0)
289
+ position = seek(0, IO::SEEK_CUR)
290
+
291
+ seek(offset, IO::SEEK_SET)
292
+ begin
293
+ read(length, buffer: buffer, buffer_offset: buffer_offset)
294
+ ensure
295
+ seek(position, IO::SEEK_SET)
296
+ end
297
+ end
298
+
299
+ ##
300
+ # Writes at most `length` bytes to the stream starting at `offset` without
301
+ # modifying the write position in the stream.
302
+ #
303
+ # Note that a partial write will occur if the stream is in non-blocking mode
304
+ # and writing more bytes would block.
305
+ #
306
+ # @param buffer [String] the bytes to write (encoding assumed to be binary)
307
+ # @param offset [Integer] the offset from the beginning of the stream at which
308
+ # to begin writing
309
+ # @param length [Integer] the number of bytes to write from `buffer`
310
+ #
311
+ # @return [Integer] the number of bytes written
312
+ # @return [:wait_readable, :wait_writable] if the stream is non-blocking and
313
+ # the operation would block
314
+ #
315
+ # @raise [IOError] if the stream is not writable
316
+ def pwrite(buffer, offset, length: buffer.bytesize)
317
+ position = seek(0, IO::SEEK_CUR)
318
+
319
+ seek(offset, IO::SEEK_SET)
320
+ begin
321
+ write(buffer, length: length)
322
+ ensure
323
+ seek(position, IO::SEEK_SET)
324
+ end
325
+ end
326
+
327
+ ##
328
+ # Reads bytes from the stream.
329
+ #
330
+ # Note that a partial read will occur if the stream is in non-blocking mode
331
+ # and reading more bytes would block.
332
+ #
333
+ # @param length [Integer] the number of bytes to read
334
+ # @param buffer [String] the buffer into which bytes will be read (encoding
335
+ # assumed to be binary)
336
+ # @param buffer_offset [Integer] the index at which to insert bytes into
337
+ # `buffer`
338
+ #
339
+ # @return [Integer] the number of bytes read if `buffer` is not `nil`
340
+ # @return [String] a buffer containing the bytes read if `buffer` is `nil`
341
+ # @return [:wait_readable, :wait_writable] if the stream is non-blocking and
342
+ # the operation would block
343
+ #
344
+ # @raise [EOFError] when reading at the end of the stream
345
+ # @raise [IOError] if the stream is not readable
346
+ def read(length, buffer: nil, buffer_offset: 0)
347
+ assert_readable
348
+ end
349
+
350
+ ##
351
+ # Returns `true` if the stream is readable and `false` otherwise.
352
+ #
353
+ # @return [Boolean]
354
+ def readable?
355
+ false
356
+ end
357
+
358
+ ##
359
+ # Returns whether or not the stream has input available.
360
+ #
361
+ # @return [true] if input is available
362
+ # @return [false] if input is not available
363
+ def ready?
364
+ assert_open
365
+ false
366
+ end
367
+
368
+ ##
369
+ # Sets the current stream position to `amount` based on the setting of
370
+ # `whence`.
371
+ #
372
+ # | `whence` | `amount` Interpretation |
373
+ # | -------- | ----------------------- |
374
+ # | `:CUR` or `IO::SEEK_CUR` | `amount` added to current stream position |
375
+ # | `:END` or `IO::SEEK_END` | `amount` added to end of stream position (`amount` will usually be negative here) |
376
+ # | `:SET` or `IO::SEEK_SET` | `amount` used as absolute position |
377
+ #
378
+ # @param amount [Integer] the amount to move the position in bytes
379
+ # @param whence [Integer, Symbol] the position alias from which to consider
380
+ # `amount`
381
+ #
382
+ # @return [Integer] the new stream position
383
+ #
384
+ # @raise [IOError] if the stream is closed
385
+ # @raise [Errno::ESPIPE] if the stream is not seekable
386
+ def seek(amount, whence)
387
+ assert_open
388
+ raise Errno::ESPIPE
389
+ end
390
+
391
+ ##
392
+ # Returns status information for the stream.
393
+ #
394
+ # @return [File::Stat]
395
+ def stat
396
+ raise NotImplementedError
397
+ end
398
+
399
+ ##
400
+ # Returns the native IO object upon which this stream is based.
401
+ #
402
+ # @return [IO]
403
+ def to_io
404
+ raise NotImplementedError
405
+ end
406
+
407
+ ##
408
+ # Returns whether or not the stream is a tty.
409
+ #
410
+ # @return [true] if the stream is a tty
411
+ # @return [false] if the stream is not a tty
412
+ #
413
+ # @raise [IOError] if the stream is closed
414
+ def tty?
415
+ assert_open
416
+ false
417
+ end
418
+
419
+ ##
420
+ # Waits until the stream becomes ready for at least 1 of the specified events.
421
+ #
422
+ # @param events [Integer] a bit mask of `IO::READABLE`, `IO::WRITABLE`, or
423
+ # `IO::PRIORITY`
424
+ # @param timeout [Numeric, nil] the timeout in seconds or no timeout if `nil`
425
+ #
426
+ # @return [true] if the stream becomes ready for at least one of the given
427
+ # events
428
+ # @return [false] if the IO does not become ready before the timeout
429
+ def wait(events, timeout = nil)
430
+ raise NotImplementedError
431
+ end
432
+
433
+ ##
434
+ # Writes bytes to the stream.
435
+ #
436
+ # Note that a partial write will occur if the stream is in non-blocking mode
437
+ # and writing more bytes would block.
438
+ #
439
+ # @param buffer [String] the bytes to write (encoding assumed to be binary)
440
+ # @param length [Integer] the number of bytes to write from `buffer`
441
+ #
442
+ # @return [Integer] the number of bytes written
443
+ # @return [:wait_readable, :wait_writable] if the stream is non-blocking and
444
+ # the operation would block
445
+ #
446
+ # @raise [IOError] if the stream is not writable
447
+ def write(buffer, length: buffer.bytesize)
448
+ assert_writable
449
+ end
450
+
451
+ ##
452
+ # Returns `true` if the stream is writable and `false` otherwise.
453
+ #
454
+ # @return [Boolean]
455
+ def writable?
456
+ false
457
+ end
458
+
459
+ private
460
+
461
+ ##
462
+ # Raises an exception if the stream is closed.
463
+ #
464
+ # @return [nil]
465
+ #
466
+ # @raise IOError if the stream is closed
467
+ def assert_open
468
+ raise IOError, 'uninitialized stream' unless defined? @closed
469
+ raise IOError, 'closed stream' if closed?
470
+ end
471
+
472
+ ##
473
+ # Raises an exception if the stream is closed or not open for reading.
474
+ #
475
+ # @return [nil]
476
+ #
477
+ # @raise IOError if the stream is closed or not open for reading
478
+ def assert_readable
479
+ assert_open
480
+ raise IOError, 'not opened for reading' unless readable?
481
+ end
482
+
483
+ ##
484
+ # Raises an exception if the stream is closed or not open for writing.
485
+ #
486
+ # @return [nil]
487
+ #
488
+ # @raise IOError if the stream is closed or not open for writing
489
+ def assert_writable
490
+ assert_open
491
+ raise IOError, 'not opened for writing' unless writable?
492
+ end
493
+
494
+ ##
495
+ # Creates an instance of this class that copies state from `other`.
496
+ #
497
+ # @param other [AbstractIO] the instance to copy
498
+ #
499
+ # @return [nil]
500
+ #
501
+ # @raise [IOError] if `other` is closed
502
+ def initialize_copy(other)
503
+ assert_open
504
+
505
+ super
506
+
507
+ nil
508
+ end
509
+ end
510
+ end; end
511
+
512
+ # vim: ts=2 sw=2 et
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'io/like_helpers/delegated_io'
4
+
5
+ class IO; module LikeHelpers
6
+
7
+ ##
8
+ # This class implements a stream that always blocks regardless of the blocking
9
+ # state of the delegate.
10
+ class BlockingIO < DelegatedIO
11
+ ##
12
+ # Reads bytes from the stream.
13
+ #
14
+ # Note that a partial read will occur if the stream is in non-blocking mode
15
+ # and reading more bytes would block. If no bytes can be read, however, the
16
+ # read will block until at least 1 byte can be read.
17
+ #
18
+ # @param length [Integer] the number of bytes to read
19
+ # @param buffer [String] the buffer into which bytes will be read (encoding
20
+ # assumed to be binary)
21
+ # @param buffer_offset [Integer] the index at which to insert bytes into
22
+ # `buffer`
23
+ #
24
+ # @return [Integer] the number of bytes read if `buffer` is not `nil`
25
+ # @return [String] a buffer containing the bytes read if `buffer` is `nil`
26
+ #
27
+ # @raise [EOFError] when reading at the end of the stream
28
+ # @raise [IOError] if the stream is not readable
29
+ def read(length, buffer: nil, buffer_offset: 0)
30
+ ensure_blocking { super }
31
+ end
32
+
33
+ ##
34
+ # Writes bytes to the stream.
35
+ #
36
+ # Note that a partial write will occur if the stream is in non-blocking mode
37
+ # and writing more bytes would block. If no bytes can be written, however,
38
+ # the write will block until at least 1 byte can be written.
39
+ #
40
+ # @param buffer [String] the bytes to write (encoding assumed to be binary)
41
+ # @param length [Integer] the number of bytes to write from `buffer`
42
+ #
43
+ # @return [Integer] the number of bytes written
44
+ #
45
+ # @raise [IOError] if the stream is not writable
46
+ def write(buffer, length: buffer.bytesize)
47
+ ensure_blocking { super }
48
+ end
49
+
50
+ private
51
+
52
+ ##
53
+ # Runs the given block in a wait loop that exits only when the block returns a
54
+ # non-Symbol value.
55
+ #
56
+ # This method is intended to wrap an IO operation that may be non-blocking and
57
+ # effectively turn it into a blocking operation without changing underlying
58
+ # settings of the stream itself.
59
+ #
60
+ # @return the return value of the block if not a Symbol
61
+ #
62
+ # @raise [RuntimeError] if the Symbol returned by the block is neither
63
+ # `:wait_readable` nor `:wait_writable`
64
+ def ensure_blocking
65
+ begin
66
+ while Symbol === (result = yield) do
67
+ # A wait timeout is used in order to allow a retry in case the stream was
68
+ # closed in another thread while waiting.
69
+ case result
70
+ when :wait_readable
71
+ wait(IO::READABLE, 1)
72
+ when :wait_writable
73
+ wait(IO::WRITABLE, 1)
74
+ else
75
+ raise "Unexpected result: #{result}"
76
+ end
77
+ end
78
+ rescue Errno::EINTR
79
+ retry
80
+ end
81
+
82
+ result
83
+ end
84
+ end
85
+
86
+ end; end