mirror of
				https://github.com/ruby/ruby.git
				synced 2022-11-09 12:17:21 -05:00 
			
		
		
		
	IO::Buffer for scheduler interface.
This commit is contained in:
		
							parent
							
								
									56b90cf944
								
							
						
					
					
						commit
						4b89034218
					
				
				
				Notes:
				
					git
				
				2021-11-10 15:21:29 +09:00 
				
			
			
			
		
		
					 14 changed files with 1747 additions and 177 deletions
				
			
		
							
								
								
									
										5
									
								
								NEWS.md
									
										
									
									
									
								
							
							
						
						
									
										5
									
								
								NEWS.md
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -204,6 +204,9 @@ Outstanding ones only.
 | 
			
		|||
    * Introduce non-blocking `Timeout.timeout` using `timeout_after` hook.
 | 
			
		||||
      [[Feature #17470]]
 | 
			
		||||
 | 
			
		||||
    * Introduce new scheduler hooks `io_read` and `io_write` along with a
 | 
			
		||||
      low level `IO::Buffer` for zero-copy read/write. [[Feature #18020]]
 | 
			
		||||
 | 
			
		||||
    * IO hooks `io_wait`, `io_read`, `io_write`, receive the original IO object
 | 
			
		||||
      where possible. [[Bug #18003]]
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -424,9 +427,11 @@ See [the repository](https://github.com/ruby/error_highlight) in detail.
 | 
			
		|||
[Bug #18003]:     https://bugs.ruby-lang.org/issues/18003
 | 
			
		||||
[Feature #18008]: https://bugs.ruby-lang.org/issues/18008
 | 
			
		||||
[Feature #18015]: https://bugs.ruby-lang.org/issues/18015
 | 
			
		||||
[Feature #18020]: https://bugs.ruby-lang.org/issues/18020
 | 
			
		||||
[Feature #18029]: https://bugs.ruby-lang.org/issues/18029
 | 
			
		||||
[Feature #18172]: https://bugs.ruby-lang.org/issues/18172
 | 
			
		||||
[Feature #18229]: https://bugs.ruby-lang.org/issues/18229
 | 
			
		||||
[Feature #18290]: https://bugs.ruby-lang.org/issues/18290
 | 
			
		||||
[GH-1509]: https://github.com/ruby/ruby/pull/1509
 | 
			
		||||
[GH-4815]: https://github.com/ruby/ruby/pull/4815
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										9
									
								
								benchmark/buffer_get.yml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								benchmark/buffer_get.yml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
benchmark:
 | 
			
		||||
  - name: buffer.get
 | 
			
		||||
    prelude: buffer = IO::Buffer.new(32, IO::Buffer::MAPPED)
 | 
			
		||||
    script: buffer.get(:U32, 0)
 | 
			
		||||
    loop_count: 20000000
 | 
			
		||||
  - name: string.unpack
 | 
			
		||||
    prelude: string = "\0" * 32
 | 
			
		||||
    script: string.unpack("C")
 | 
			
		||||
    loop_count: 20000000
 | 
			
		||||
							
								
								
									
										178
									
								
								common.mk
									
										
									
									
									
								
							
							
						
						
									
										178
									
								
								common.mk
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -104,6 +104,7 @@ COMMONOBJS    = array.$(OBJEXT) \
 | 
			
		|||
		hash.$(OBJEXT) \
 | 
			
		||||
		inits.$(OBJEXT) \
 | 
			
		||||
		io.$(OBJEXT) \
 | 
			
		||||
		io_buffer.$(OBJEXT) \
 | 
			
		||||
		iseq.$(OBJEXT) \
 | 
			
		||||
		load.$(OBJEXT) \
 | 
			
		||||
		marshal.$(OBJEXT) \
 | 
			
		||||
| 
						 | 
				
			
			@ -6972,6 +6973,7 @@ io.$(OBJEXT): {$(VPATH)}internal/xmalloc.h
 | 
			
		|||
io.$(OBJEXT): {$(VPATH)}io.c
 | 
			
		||||
io.$(OBJEXT): {$(VPATH)}io.h
 | 
			
		||||
io.$(OBJEXT): {$(VPATH)}io.rbinc
 | 
			
		||||
io.$(OBJEXT): {$(VPATH)}io/buffer.h
 | 
			
		||||
io.$(OBJEXT): {$(VPATH)}method.h
 | 
			
		||||
io.$(OBJEXT): {$(VPATH)}missing.h
 | 
			
		||||
io.$(OBJEXT): {$(VPATH)}node.h
 | 
			
		||||
| 
						 | 
				
			
			@ -6988,6 +6990,181 @@ io.$(OBJEXT): {$(VPATH)}thread_native.h
 | 
			
		|||
io.$(OBJEXT): {$(VPATH)}util.h
 | 
			
		||||
io.$(OBJEXT): {$(VPATH)}vm_core.h
 | 
			
		||||
io.$(OBJEXT): {$(VPATH)}vm_opts.h
 | 
			
		||||
io_buffer.$(OBJEXT): $(hdrdir)/ruby/ruby.h
 | 
			
		||||
io_buffer.$(OBJEXT): $(top_srcdir)/internal/bits.h
 | 
			
		||||
io_buffer.$(OBJEXT): $(top_srcdir)/internal/compilers.h
 | 
			
		||||
io_buffer.$(OBJEXT): $(top_srcdir)/internal/static_assert.h
 | 
			
		||||
io_buffer.$(OBJEXT): $(top_srcdir)/internal/string.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}assert.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}backward/2/assume.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}backward/2/attributes.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}backward/2/bool.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}backward/2/gcc_version_since.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}backward/2/inttypes.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}backward/2/limits.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}backward/2/long_long.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}config.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}defines.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}encoding.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}intern.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/anyargs.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/arithmetic.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/arithmetic/char.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/arithmetic/double.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/arithmetic/fixnum.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/arithmetic/gid_t.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/arithmetic/int.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/arithmetic/intptr_t.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/arithmetic/long.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/arithmetic/long_long.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/arithmetic/mode_t.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/arithmetic/off_t.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/arithmetic/pid_t.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/arithmetic/short.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/arithmetic/size_t.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/arithmetic/st_data_t.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/arithmetic/uid_t.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/assume.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/attr/alloc_size.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/attr/artificial.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/attr/cold.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/attr/const.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/attr/constexpr.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/attr/deprecated.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/attr/diagnose_if.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/attr/enum_extensibility.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/attr/error.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/attr/flag_enum.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/attr/forceinline.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/attr/format.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/attr/maybe_unused.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/attr/noalias.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/attr/nodiscard.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/attr/noexcept.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/attr/noinline.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/attr/nonnull.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/attr/noreturn.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/attr/pure.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/attr/restrict.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/attr/returns_nonnull.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/attr/warning.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/attr/weakref.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/cast.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/compiler_is.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/compiler_is/apple.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/compiler_is/clang.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/compiler_is/gcc.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/compiler_is/intel.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/compiler_is/msvc.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/compiler_is/sunpro.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/compiler_since.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/config.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/constant_p.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/core.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/core/rarray.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/core/rbasic.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/core/rbignum.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/core/rclass.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/core/rdata.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/core/rfile.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/core/rhash.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/core/robject.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/core/rregexp.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/core/rstring.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/core/rstruct.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/core/rtypeddata.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/ctype.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/dllexport.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/dosish.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/encoding/coderange.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/encoding/ctype.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/encoding/encoding.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/encoding/pathname.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/encoding/re.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/encoding/sprintf.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/encoding/string.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/encoding/symbol.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/encoding/transcode.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/error.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/eval.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/event.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/fl_type.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/gc.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/glob.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/globals.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/has/attribute.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/has/builtin.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/has/c_attribute.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/has/cpp_attribute.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/has/declspec_attribute.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/has/extension.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/has/feature.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/has/warning.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/array.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/bignum.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/class.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/compar.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/complex.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/cont.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/dir.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/enum.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/enumerator.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/error.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/eval.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/file.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/gc.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/hash.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/io.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/load.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/marshal.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/numeric.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/object.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/parse.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/proc.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/process.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/random.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/range.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/rational.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/re.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/select.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/signal.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/string.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/struct.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/thread.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/time.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/variable.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/intern/vm.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/interpreter.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/iterator.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/memory.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/method.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/module.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/newobj.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/rgengc.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/scan_args.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/special_consts.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/static_assert.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/stdalign.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/stdbool.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/symbol.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/value.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/value_type.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/variable.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/warning_push.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}internal/xmalloc.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}io.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}io/buffer.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}io_buffer.c
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}missing.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}onigmo.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}oniguruma.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}st.h
 | 
			
		||||
io_buffer.$(OBJEXT): {$(VPATH)}subst.h
 | 
			
		||||
iseq.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h
 | 
			
		||||
iseq.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h
 | 
			
		||||
iseq.$(OBJEXT): $(CCAN_DIR)/list/list.h
 | 
			
		||||
| 
						 | 
				
			
			@ -12922,6 +13099,7 @@ scheduler.$(OBJEXT): {$(VPATH)}internal/variable.h
 | 
			
		|||
scheduler.$(OBJEXT): {$(VPATH)}internal/warning_push.h
 | 
			
		||||
scheduler.$(OBJEXT): {$(VPATH)}internal/xmalloc.h
 | 
			
		||||
scheduler.$(OBJEXT): {$(VPATH)}io.h
 | 
			
		||||
scheduler.$(OBJEXT): {$(VPATH)}io/buffer.h
 | 
			
		||||
scheduler.$(OBJEXT): {$(VPATH)}method.h
 | 
			
		||||
scheduler.$(OBJEXT): {$(VPATH)}missing.h
 | 
			
		||||
scheduler.$(OBJEXT): {$(VPATH)}node.h
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										24
									
								
								doc/fiber.md
									
										
									
									
									
								
							
							
						
						
									
										24
									
								
								doc/fiber.md
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -48,6 +48,14 @@ When the thread exits, there is an implicit call to `set_scheduler`:
 | 
			
		|||
Fiber.set_scheduler(nil)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Design
 | 
			
		||||
 | 
			
		||||
The scheduler interface is designed to be a un-opinionated light-weight layer
 | 
			
		||||
between user code and blocking operations. The scheduler hooks should avoid
 | 
			
		||||
translating or converting arguments or return values. Ideally, the exact same
 | 
			
		||||
arguments from the user code are provided directly to the scheduler hook with
 | 
			
		||||
no changes.
 | 
			
		||||
 | 
			
		||||
### Interface
 | 
			
		||||
 | 
			
		||||
This is the interface you need to implement.
 | 
			
		||||
| 
						 | 
				
			
			@ -65,7 +73,7 @@ class Scheduler
 | 
			
		|||
    end.value
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Wait for the given file descriptor to match the specified events within
 | 
			
		||||
  # Wait for the given io readiness to match the specified events within
 | 
			
		||||
  # the specified timeout.
 | 
			
		||||
  # @parameter event [Integer] A bit mask of `IO::READABLE`,
 | 
			
		||||
  #   `IO::WRITABLE` and `IO::PRIORITY`.
 | 
			
		||||
| 
						 | 
				
			
			@ -74,6 +82,20 @@ class Scheduler
 | 
			
		|||
  def io_wait(io, events, timeout)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Read from the given io into the specified buffer.
 | 
			
		||||
  # @parameter io [IO] The io to read from.
 | 
			
		||||
  # @parameter buffer [IO::Buffer] The buffer to read into.
 | 
			
		||||
  # @parameter length [Integer] The minimum amount to read.
 | 
			
		||||
  def io_read(io, buffer, length)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Write from the given buffer into the specified IO.
 | 
			
		||||
  # @parameter io [IO] The io to write to.
 | 
			
		||||
  # @parameter buffer [IO::Buffer] The buffer to write from.
 | 
			
		||||
  # @parameter length [Integer] The minimum amount to write.
 | 
			
		||||
  def io_write(io, buffer, length)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Sleep the current task for the specified duration, or forever if not
 | 
			
		||||
  # specified.
 | 
			
		||||
  # @parameter duration [Numeric] The amount of time to sleep in seconds.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										21
									
								
								file.c
									
										
									
									
									
								
							
							
						
						
									
										21
									
								
								file.c
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -2515,20 +2515,27 @@ rb_file_birthtime(VALUE obj)
 | 
			
		|||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
static VALUE
 | 
			
		||||
rb_file_size(VALUE obj)
 | 
			
		||||
size_t rb_file_size(VALUE file)
 | 
			
		||||
{
 | 
			
		||||
    rb_io_t *fptr;
 | 
			
		||||
    struct stat st;
 | 
			
		||||
 | 
			
		||||
    GetOpenFile(obj, fptr);
 | 
			
		||||
    RB_IO_POINTER(file, fptr);
 | 
			
		||||
    if (fptr->mode & FMODE_WRITABLE) {
 | 
			
		||||
	rb_io_flush_raw(obj, 0);
 | 
			
		||||
        rb_io_flush_raw(file, 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (fstat(fptr->fd, &st) == -1) {
 | 
			
		||||
	rb_sys_fail_path(fptr->pathv);
 | 
			
		||||
        rb_sys_fail_path(fptr->pathv);
 | 
			
		||||
    }
 | 
			
		||||
    return OFFT2NUM(st.st_size);
 | 
			
		||||
 | 
			
		||||
    return st.st_size;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static VALUE
 | 
			
		||||
file_size(VALUE self)
 | 
			
		||||
{
 | 
			
		||||
    return RB_SIZE2NUM(rb_file_size(self));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int
 | 
			
		||||
| 
						 | 
				
			
			@ -6780,7 +6787,7 @@ Init_File(void)
 | 
			
		|||
    rb_define_method(rb_cFile, "mtime", rb_file_mtime, 0);
 | 
			
		||||
    rb_define_method(rb_cFile, "ctime", rb_file_ctime, 0);
 | 
			
		||||
    rb_define_method(rb_cFile, "birthtime", rb_file_birthtime, 0);
 | 
			
		||||
    rb_define_method(rb_cFile, "size", rb_file_size, 0);
 | 
			
		||||
    rb_define_method(rb_cFile, "size", file_size, 0);
 | 
			
		||||
 | 
			
		||||
    rb_define_method(rb_cFile, "chmod", rb_file_chmod, 1);
 | 
			
		||||
    rb_define_method(rb_cFile, "chown", rb_file_chown, 2);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -193,12 +193,11 @@ VALUE rb_fiber_scheduler_io_wait_writable(VALUE scheduler, VALUE io);
 | 
			
		|||
 * @param[in]   scheduler    Target scheduler.
 | 
			
		||||
 * @param[out]  io           An io object to read from.
 | 
			
		||||
 * @param[out]  buffer       Return buffer.
 | 
			
		||||
 * @param[in]   offset       Offset inside of `buffer`.
 | 
			
		||||
 * @param[in]   length       Requested number of bytes to read.
 | 
			
		||||
 * @retval      RUBY_Qundef  `scheduler` doesn't have `#io_read`.
 | 
			
		||||
 * @return      otherwise    What `scheduler.io_read` returns.
 | 
			
		||||
 */
 | 
			
		||||
VALUE rb_fiber_scheduler_io_read(VALUE scheduler, VALUE io, VALUE buffer, size_t offset, size_t length);
 | 
			
		||||
VALUE rb_fiber_scheduler_io_read(VALUE scheduler, VALUE io, VALUE buffer, size_t length);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Nonblocking write to the passed IO.
 | 
			
		||||
| 
						 | 
				
			
			@ -206,12 +205,45 @@ VALUE rb_fiber_scheduler_io_read(VALUE scheduler, VALUE io, VALUE buffer, size_t
 | 
			
		|||
 * @param[in]   scheduler    Target scheduler.
 | 
			
		||||
 * @param[out]  io           An io object to write to.
 | 
			
		||||
 * @param[in]   buffer       What to write.
 | 
			
		||||
 * @param[in]   offset       Offset inside of `buffer`.
 | 
			
		||||
 * @param[in]   length       Number of bytes to write.
 | 
			
		||||
 * @retval      RUBY_Qundef  `scheduler` doesn't have `#io_write`.
 | 
			
		||||
 * @return      otherwise    What `scheduler.io_write` returns.
 | 
			
		||||
 */
 | 
			
		||||
VALUE rb_fiber_scheduler_io_write(VALUE scheduler, VALUE io, VALUE buffer, size_t offset, size_t length);
 | 
			
		||||
VALUE rb_fiber_scheduler_io_write(VALUE scheduler, VALUE io, VALUE buffer, size_t length);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Nonblocking read from the passed IO using a native buffer.
 | 
			
		||||
 *
 | 
			
		||||
 * @param[in]   scheduler    Target scheduler.
 | 
			
		||||
 * @param[out]  io           An io object to read from.
 | 
			
		||||
 * @param[out]  buffer       Return buffer.
 | 
			
		||||
 * @param[in]   length       Requested number of bytes to read.
 | 
			
		||||
 * @retval      RUBY_Qundef  `scheduler` doesn't have `#io_read`.
 | 
			
		||||
 * @return      otherwise    What `scheduler.io_read` returns.
 | 
			
		||||
 */
 | 
			
		||||
VALUE rb_fiber_scheduler_io_read_memory(VALUE scheduler, VALUE io, void *buffer, size_t size, size_t length);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Nonblocking write to the passed IO using a native buffer.
 | 
			
		||||
 *
 | 
			
		||||
 * @param[in]   scheduler    Target scheduler.
 | 
			
		||||
 * @param[out]  io           An io object to write to.
 | 
			
		||||
 * @param[in]   buffer       What to write.
 | 
			
		||||
 * @param[in]   length       Number of bytes to write.
 | 
			
		||||
 * @retval      RUBY_Qundef  `scheduler` doesn't have `#io_write`.
 | 
			
		||||
 * @return      otherwise    What `scheduler.io_write` returns.
 | 
			
		||||
 */
 | 
			
		||||
VALUE rb_fiber_scheduler_io_write_memory(VALUE scheduler, VALUE io, const void *buffer, size_t size, size_t length);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Nonblocking close the given IO.
 | 
			
		||||
 *
 | 
			
		||||
 * @param[in]  scheduler    Target scheduler.
 | 
			
		||||
 * @param[in]  io           An io object to close.
 | 
			
		||||
 * @retval     RUBY_Qundef  `scheduler` doesn't have `#io_close`.
 | 
			
		||||
 * @return     otherwise    What `scheduler.io_close` returns.
 | 
			
		||||
 */
 | 
			
		||||
VALUE rb_fiber_scheduler_io_close(VALUE scheduler, VALUE io);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Nonblocking DNS lookup.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -187,6 +187,8 @@ RBIMPL_ATTR_PURE()
 | 
			
		|||
 */
 | 
			
		||||
int rb_is_absolute_path(const char *path);
 | 
			
		||||
 | 
			
		||||
size_t rb_file_size(VALUE file);
 | 
			
		||||
 | 
			
		||||
RBIMPL_SYMBOL_EXPORT_END()
 | 
			
		||||
 | 
			
		||||
#endif /* RBIMPL_INTERN_FILE_H */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -670,6 +670,15 @@ VALUE rb_io_set_write_io(VALUE io, VALUE w);
 | 
			
		|||
 */
 | 
			
		||||
void rb_io_set_nonblock(rb_io_t *fptr);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns an integer representing the numeric file descriptor for
 | 
			
		||||
 * <em>io</em>.
 | 
			
		||||
 *
 | 
			
		||||
 * @param[in]   io         An IO.
 | 
			
		||||
 * @retval      int        A file descriptor.
 | 
			
		||||
 */
 | 
			
		||||
int rb_io_descriptor(VALUE io);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This function  breaks down the  option hash that `IO#initialize`  takes into
 | 
			
		||||
 * components.   This is  an implementation  detail of  rb_io_extract_modeenc()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										71
									
								
								include/ruby/io/buffer.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								include/ruby/io/buffer.h
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,71 @@
 | 
			
		|||
/**
 | 
			
		||||
 * @file
 | 
			
		||||
 * @author     Samuel Williams
 | 
			
		||||
 * @date       Fri  2 Jul 2021 16:29:01 NZST
 | 
			
		||||
 * @copyright  Copyright (C) 2021 Samuel Williams
 | 
			
		||||
 * @copyright  This  file  is   a  part  of  the   programming  language  Ruby.
 | 
			
		||||
 *             Permission  is hereby  granted,  to  either redistribute  and/or
 | 
			
		||||
 *             modify this file, provided that  the conditions mentioned in the
 | 
			
		||||
 *             file COPYING are met.  Consult the file for details.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "ruby/ruby.h"
 | 
			
		||||
#include "ruby/internal/config.h"
 | 
			
		||||
 | 
			
		||||
RUBY_SYMBOL_EXPORT_BEGIN
 | 
			
		||||
 | 
			
		||||
RUBY_EXTERN VALUE rb_cIOBuffer;
 | 
			
		||||
RUBY_EXTERN size_t RUBY_IO_BUFFER_PAGE_SIZE;
 | 
			
		||||
 | 
			
		||||
enum rb_io_buffer_flags {
 | 
			
		||||
    // The memory in the buffer is owned by someone else.
 | 
			
		||||
    RB_IO_BUFFER_EXTERNAL = 0,
 | 
			
		||||
    // The memory in the buffer is allocated internally.
 | 
			
		||||
    RB_IO_BUFFER_INTERNAL = 1,
 | 
			
		||||
    // The memory in the buffer is mapped.
 | 
			
		||||
    RB_IO_BUFFER_MAPPED = 2,
 | 
			
		||||
 | 
			
		||||
    // The buffer is locked and cannot be resized.
 | 
			
		||||
    RB_IO_BUFFER_LOCKED = 16,
 | 
			
		||||
 | 
			
		||||
    // The buffer mapping is private and will not impact other processes or the underlying file.
 | 
			
		||||
    RB_IO_BUFFER_PRIVATE = 32,
 | 
			
		||||
 | 
			
		||||
    // The buffer is read-only and cannot be modified.
 | 
			
		||||
    RB_IO_BUFFER_IMMUTABLE = 64
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum rb_io_buffer_endian {
 | 
			
		||||
    RB_IO_BUFFER_LITTLE_ENDIAN = 4,
 | 
			
		||||
    RB_IO_BUFFER_BIG_ENDIAN = 8,
 | 
			
		||||
 | 
			
		||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
 | 
			
		||||
    RB_IO_BUFFER_HOST_ENDIAN = RB_IO_BUFFER_LITTLE_ENDIAN,
 | 
			
		||||
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
 | 
			
		||||
    RB_IO_BUFFER_HOST_ENDIAN = RB_IO_BUFFER_BIG_ENDIAN,
 | 
			
		||||
#elif REG_DWORD == REG_DWORD_LITTLE_ENDIAN
 | 
			
		||||
    RB_IO_BUFFER_HOST_ENDIAN = RB_IO_BUFFER_LITTLE_ENDIAN,
 | 
			
		||||
#elif REG_DWORD == REG_DWORD_BIG_ENDIAN
 | 
			
		||||
    RB_IO_BUFFER_HOST_ENDIAN = RB_IO_BUFFER_BIG_ENDIAN,
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
    RB_IO_BUFFER_NETWORK_ENDIAN = RB_IO_BUFFER_BIG_ENDIAN,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
VALUE rb_io_buffer_new(void *base, size_t size, enum rb_io_buffer_flags flags);
 | 
			
		||||
VALUE rb_io_buffer_map(VALUE io, size_t size, off_t offset, enum rb_io_buffer_flags flags);
 | 
			
		||||
 | 
			
		||||
VALUE rb_io_buffer_lock(VALUE self);
 | 
			
		||||
VALUE rb_io_buffer_unlock(VALUE self);
 | 
			
		||||
VALUE rb_io_buffer_free(VALUE self);
 | 
			
		||||
 | 
			
		||||
void rb_io_buffer_get_mutable(VALUE self, void **base, size_t *size);
 | 
			
		||||
void rb_io_buffer_get_immutable(VALUE self, const void **base, size_t *size);
 | 
			
		||||
 | 
			
		||||
size_t rb_io_buffer_copy(VALUE self, VALUE source, size_t offset);
 | 
			
		||||
void rb_io_buffer_resize(VALUE self, size_t size, size_t preserve);
 | 
			
		||||
void rb_io_buffer_clear(VALUE self, uint8_t value, size_t offset, size_t length);
 | 
			
		||||
 | 
			
		||||
RUBY_SYMBOL_EXPORT_END
 | 
			
		||||
							
								
								
									
										1
									
								
								inits.c
									
										
									
									
									
								
							
							
						
						
									
										1
									
								
								inits.c
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -51,6 +51,7 @@ rb_call_inits(void)
 | 
			
		|||
    CALL(marshal);
 | 
			
		||||
    CALL(Range);
 | 
			
		||||
    CALL(IO);
 | 
			
		||||
    CALL(IO_Buffer)
 | 
			
		||||
    CALL(Dir);
 | 
			
		||||
    CALL(Time);
 | 
			
		||||
    CALL(Random);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										360
									
								
								io.c
									
										
									
									
									
								
							
							
						
						
									
										360
									
								
								io.c
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -14,6 +14,7 @@
 | 
			
		|||
#include "ruby/internal/config.h"
 | 
			
		||||
 | 
			
		||||
#include "ruby/fiber/scheduler.h"
 | 
			
		||||
#include "ruby/io/buffer.h"
 | 
			
		||||
 | 
			
		||||
#ifdef _WIN32
 | 
			
		||||
# include "ruby/ruby.h"
 | 
			
		||||
| 
						 | 
				
			
			@ -131,6 +132,7 @@
 | 
			
		|||
#include "internal/transcode.h"
 | 
			
		||||
#include "internal/variable.h"
 | 
			
		||||
#include "ruby/io.h"
 | 
			
		||||
#include "ruby/io/buffer.h"
 | 
			
		||||
#include "ruby/thread.h"
 | 
			
		||||
#include "ruby/util.h"
 | 
			
		||||
#include "ruby_atomic.h"
 | 
			
		||||
| 
						 | 
				
			
			@ -203,7 +205,7 @@ VALUE rb_default_rs;
 | 
			
		|||
 | 
			
		||||
static VALUE argf;
 | 
			
		||||
 | 
			
		||||
static ID id_write, id_read, id_getc, id_flush, id_readpartial, id_set_encoding;
 | 
			
		||||
static ID id_write, id_read, id_getc, id_flush, id_readpartial, id_set_encoding, id_fileno;
 | 
			
		||||
static VALUE sym_mode, sym_perm, sym_flags, sym_extenc, sym_intenc, sym_encoding, sym_open_args;
 | 
			
		||||
static VALUE sym_textmode, sym_binmode, sym_autoclose;
 | 
			
		||||
static VALUE sym_SET, sym_CUR, sym_END;
 | 
			
		||||
| 
						 | 
				
			
			@ -1060,7 +1062,7 @@ io_alloc(VALUE klass)
 | 
			
		|||
 | 
			
		||||
struct io_internal_read_struct {
 | 
			
		||||
    VALUE th;
 | 
			
		||||
    int fd;
 | 
			
		||||
    rb_io_t *fptr;
 | 
			
		||||
    int nonblock;
 | 
			
		||||
    void *buf;
 | 
			
		||||
    size_t capa;
 | 
			
		||||
| 
						 | 
				
			
			@ -1080,18 +1082,18 @@ struct io_internal_writev_struct {
 | 
			
		|||
};
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
static int nogvl_wait_for_single_fd(VALUE th, int fd, short events);
 | 
			
		||||
static int nogvl_wait_for(VALUE th, rb_io_t *fptr, short events);
 | 
			
		||||
static VALUE
 | 
			
		||||
internal_read_func(void *ptr)
 | 
			
		||||
{
 | 
			
		||||
    struct io_internal_read_struct *iis = ptr;
 | 
			
		||||
    ssize_t r;
 | 
			
		||||
retry:
 | 
			
		||||
    r = read(iis->fd, iis->buf, iis->capa);
 | 
			
		||||
    r = read(iis->fptr->fd, iis->buf, iis->capa);
 | 
			
		||||
    if (r < 0 && !iis->nonblock) {
 | 
			
		||||
        int e = errno;
 | 
			
		||||
        if (io_again_p(e)) {
 | 
			
		||||
            if (nogvl_wait_for_single_fd(iis->th, iis->fd, RB_WAITFD_IN) != -1) {
 | 
			
		||||
            if (nogvl_wait_for(iis->th, iis->fptr, RB_WAITFD_IN) != -1) {
 | 
			
		||||
                goto retry;
 | 
			
		||||
            }
 | 
			
		||||
            errno = e;
 | 
			
		||||
| 
						 | 
				
			
			@ -1132,36 +1134,62 @@ internal_writev_func(void *ptr)
 | 
			
		|||
#endif
 | 
			
		||||
 | 
			
		||||
static ssize_t
 | 
			
		||||
rb_read_internal(int fd, void *buf, size_t count)
 | 
			
		||||
rb_read_internal(rb_io_t *fptr, void *buf, size_t count)
 | 
			
		||||
{
 | 
			
		||||
    VALUE scheduler = rb_fiber_scheduler_current();
 | 
			
		||||
    if (scheduler != Qnil) {
 | 
			
		||||
        VALUE result = rb_fiber_scheduler_io_read_memory(scheduler, fptr->self, buf, count, 1);
 | 
			
		||||
 | 
			
		||||
        if (result != Qundef) {
 | 
			
		||||
          ssize_t length = RB_NUM2SSIZE(result);
 | 
			
		||||
 | 
			
		||||
          if (length < 0) rb_sys_fail_path(fptr->pathv);
 | 
			
		||||
 | 
			
		||||
          return length;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    struct io_internal_read_struct iis = {
 | 
			
		||||
        .th = rb_thread_current(),
 | 
			
		||||
        .fd = fd,
 | 
			
		||||
        .fptr = fptr,
 | 
			
		||||
        .nonblock = 0,
 | 
			
		||||
        .buf = buf,
 | 
			
		||||
        .capa = count
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (ssize_t)rb_thread_io_blocking_region(internal_read_func, &iis, fd);
 | 
			
		||||
    return (ssize_t)rb_thread_io_blocking_region(internal_read_func, &iis, fptr->fd);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static ssize_t
 | 
			
		||||
rb_write_internal(int fd, const void *buf, size_t count)
 | 
			
		||||
rb_write_internal(rb_io_t *fptr, const void *buf, size_t count)
 | 
			
		||||
{
 | 
			
		||||
    VALUE scheduler = rb_fiber_scheduler_current();
 | 
			
		||||
    if (scheduler != Qnil) {
 | 
			
		||||
        VALUE result = rb_fiber_scheduler_io_write_memory(scheduler, fptr->self, buf, count, count);
 | 
			
		||||
 | 
			
		||||
        if (result != Qundef) {
 | 
			
		||||
          ssize_t length = RB_NUM2SSIZE(result);
 | 
			
		||||
 | 
			
		||||
          if (length < 0) rb_sys_fail_path(fptr->pathv);
 | 
			
		||||
 | 
			
		||||
          return length;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    struct io_internal_write_struct iis = {
 | 
			
		||||
        .fd = fd,
 | 
			
		||||
        .fd = fptr->fd,
 | 
			
		||||
        .buf = buf,
 | 
			
		||||
        .capa = count
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (ssize_t)rb_thread_io_blocking_region(internal_write_func, &iis, fd);
 | 
			
		||||
    return (ssize_t)rb_thread_io_blocking_region(internal_write_func, &iis, fptr->fd);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static ssize_t
 | 
			
		||||
rb_write_internal2(int fd, const void *buf, size_t count)
 | 
			
		||||
rb_write_internal2(rb_io_t *fptr, const void *buf, size_t count)
 | 
			
		||||
{
 | 
			
		||||
    struct io_internal_write_struct iis = {
 | 
			
		||||
        .fd = fd,
 | 
			
		||||
        .fd = fptr->fd,
 | 
			
		||||
        .buf = buf,
 | 
			
		||||
        .capa = count
 | 
			
		||||
    };
 | 
			
		||||
| 
						 | 
				
			
			@ -1581,7 +1609,7 @@ io_binwrite_string(VALUE arg)
 | 
			
		|||
	}
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
	r = rb_write_internal(fptr->fd, p->ptr, p->length);
 | 
			
		||||
	r = rb_write_internal(fptr, p->ptr, p->length);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return r;
 | 
			
		||||
| 
						 | 
				
			
			@ -1612,7 +1640,7 @@ io_binwrite_string(VALUE arg)
 | 
			
		|||
	    return len;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return rb_write_internal(p->fptr->fd, p->ptr, p->length);
 | 
			
		||||
    return rb_write_internal(p->fptr, p->ptr, p->length);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1628,7 +1656,7 @@ io_binwrite(VALUE str, const char *ptr, long len, rb_io_t *fptr, int nosync)
 | 
			
		|||
 | 
			
		||||
    VALUE scheduler = rb_fiber_scheduler_current();
 | 
			
		||||
    if (scheduler != Qnil) {
 | 
			
		||||
        VALUE result = rb_fiber_scheduler_io_write(scheduler, fptr->self, str, offset, len);
 | 
			
		||||
        VALUE result = rb_fiber_scheduler_io_write_memory(scheduler, fptr->self, ptr, len, len);
 | 
			
		||||
 | 
			
		||||
        if (result != Qundef) {
 | 
			
		||||
          ssize_t length = RB_NUM2SSIZE(result);
 | 
			
		||||
| 
						 | 
				
			
			@ -2316,27 +2344,26 @@ io_fillbuf(rb_io_t *fptr)
 | 
			
		|||
        fptr->rbuf.capa = IO_RBUF_CAPA_FOR(fptr);
 | 
			
		||||
        fptr->rbuf.ptr = ALLOC_N(char, fptr->rbuf.capa);
 | 
			
		||||
#ifdef _WIN32
 | 
			
		||||
	fptr->rbuf.capa--;
 | 
			
		||||
        fptr->rbuf.capa--;
 | 
			
		||||
#endif
 | 
			
		||||
    }
 | 
			
		||||
    if (fptr->rbuf.len == 0) {
 | 
			
		||||
      retry:
 | 
			
		||||
	{
 | 
			
		||||
	    r = rb_read_internal(fptr->fd, fptr->rbuf.ptr, fptr->rbuf.capa);
 | 
			
		||||
	}
 | 
			
		||||
        r = rb_read_internal(fptr, fptr->rbuf.ptr, fptr->rbuf.capa);
 | 
			
		||||
 | 
			
		||||
        if (r < 0) {
 | 
			
		||||
            if (fptr_wait_readable(fptr))
 | 
			
		||||
                goto retry;
 | 
			
		||||
	    {
 | 
			
		||||
		int e = errno;
 | 
			
		||||
		VALUE path = rb_sprintf("fd:%d ", fptr->fd);
 | 
			
		||||
		if (!NIL_P(fptr->pathv)) {
 | 
			
		||||
		    rb_str_append(path, fptr->pathv);
 | 
			
		||||
		}
 | 
			
		||||
		rb_syserr_fail_path(e, path);
 | 
			
		||||
	    }
 | 
			
		||||
 | 
			
		||||
            int e = errno;
 | 
			
		||||
            VALUE path = rb_sprintf("fd:%d ", fptr->fd);
 | 
			
		||||
            if (!NIL_P(fptr->pathv)) {
 | 
			
		||||
                rb_str_append(path, fptr->pathv);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            rb_syserr_fail_path(e, path);
 | 
			
		||||
        }
 | 
			
		||||
	if (r > 0) rb_io_check_closed(fptr);
 | 
			
		||||
        if (r > 0) rb_io_check_closed(fptr);
 | 
			
		||||
        fptr->rbuf.off = 0;
 | 
			
		||||
        fptr->rbuf.len = (int)r; /* r should be <= rbuf_capa */
 | 
			
		||||
        if (r == 0)
 | 
			
		||||
| 
						 | 
				
			
			@ -2557,6 +2584,16 @@ rb_io_fileno(VALUE io)
 | 
			
		|||
    return INT2FIX(fd);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int rb_io_descriptor(VALUE io)
 | 
			
		||||
{
 | 
			
		||||
    if (RB_TYPE_P(io, T_FILE)) {
 | 
			
		||||
        rb_io_t *fptr = RFILE(io)->fptr;
 | 
			
		||||
        rb_io_check_closed(fptr);
 | 
			
		||||
        return fptr->fd;
 | 
			
		||||
    } else {
 | 
			
		||||
        return RB_NUM2INT(rb_funcall(io, id_fileno, 0));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 *  call-seq:
 | 
			
		||||
| 
						 | 
				
			
			@ -2665,7 +2702,7 @@ io_bufread(char *ptr, long len, rb_io_t *fptr)
 | 
			
		|||
        while (n > 0) {
 | 
			
		||||
          again:
 | 
			
		||||
            rb_io_check_closed(fptr);
 | 
			
		||||
            c = rb_read_internal(fptr->fd, ptr+offset, n);
 | 
			
		||||
            c = rb_read_internal(fptr, ptr+offset, n);
 | 
			
		||||
            if (c == 0) break;
 | 
			
		||||
            if (c < 0) {
 | 
			
		||||
                if (fptr_wait_readable(fptr))
 | 
			
		||||
| 
						 | 
				
			
			@ -2711,19 +2748,6 @@ bufread_call(VALUE arg)
 | 
			
		|||
static long
 | 
			
		||||
io_fread(VALUE str, long offset, long size, rb_io_t *fptr)
 | 
			
		||||
{
 | 
			
		||||
    VALUE scheduler = rb_fiber_scheduler_current();
 | 
			
		||||
    if (scheduler != Qnil) {
 | 
			
		||||
        VALUE result = rb_fiber_scheduler_io_read(scheduler, fptr->self, str, offset, size);
 | 
			
		||||
 | 
			
		||||
        if (result != Qundef) {
 | 
			
		||||
          ssize_t length = RB_NUM2SSIZE(result);
 | 
			
		||||
 | 
			
		||||
          if (length < 0) rb_sys_fail_path(fptr->pathv);
 | 
			
		||||
 | 
			
		||||
          return length;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    long len;
 | 
			
		||||
    struct bufread_arg arg;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -3035,7 +3059,16 @@ read_internal_call(VALUE arg)
 | 
			
		|||
{
 | 
			
		||||
    struct io_internal_read_struct *iis = (struct io_internal_read_struct *)arg;
 | 
			
		||||
 | 
			
		||||
    return rb_thread_io_blocking_region(internal_read_func, iis, iis->fd);
 | 
			
		||||
    VALUE scheduler = rb_fiber_scheduler_current();
 | 
			
		||||
    if (scheduler != Qnil) {
 | 
			
		||||
        VALUE result = rb_fiber_scheduler_io_read_memory(scheduler, iis->fptr->self, iis->buf, iis->capa, 1);
 | 
			
		||||
 | 
			
		||||
        if (result != Qundef) {
 | 
			
		||||
          return (VALUE)RB_NUM2SSIZE(result);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return rb_thread_io_blocking_region(internal_read_func, iis, iis->fptr->fd);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static long
 | 
			
		||||
| 
						 | 
				
			
			@ -3079,7 +3112,7 @@ io_getpartial(int argc, VALUE *argv, VALUE io, int no_exception, int nonblock)
 | 
			
		|||
        }
 | 
			
		||||
	io_setstrbuf(&str, len);
 | 
			
		||||
        iis.th = rb_thread_current();
 | 
			
		||||
        iis.fd = fptr->fd;
 | 
			
		||||
        iis.fptr = fptr;
 | 
			
		||||
        iis.nonblock = nonblock;
 | 
			
		||||
        iis.buf = RSTRING_PTR(str);
 | 
			
		||||
        iis.capa = len;
 | 
			
		||||
| 
						 | 
				
			
			@ -3217,7 +3250,7 @@ io_read_nonblock(rb_execution_context_t *ec, VALUE io, VALUE length, VALUE str,
 | 
			
		|||
    if (n <= 0) {
 | 
			
		||||
	rb_io_set_nonblock(fptr);
 | 
			
		||||
	shrinkable |= io_setstrbuf(&str, len);
 | 
			
		||||
        iis.fd = fptr->fd;
 | 
			
		||||
        iis.fptr = fptr;
 | 
			
		||||
        iis.nonblock = 1;
 | 
			
		||||
        iis.buf = RSTRING_PTR(str);
 | 
			
		||||
        iis.capa = len;
 | 
			
		||||
| 
						 | 
				
			
			@ -4726,10 +4759,10 @@ finish_writeconv(rb_io_t *fptr, int noalloc)
 | 
			
		|||
            res = rb_econv_convert(fptr->writeconv, NULL, NULL, &dp, de, 0);
 | 
			
		||||
            while (dp-ds) {
 | 
			
		||||
              retry:
 | 
			
		||||
		if (fptr->write_lock && rb_mutex_owned_p(fptr->write_lock))
 | 
			
		||||
		    r = rb_write_internal2(fptr->fd, ds, dp-ds);
 | 
			
		||||
		else
 | 
			
		||||
		    r = rb_write_internal(fptr->fd, ds, dp-ds);
 | 
			
		||||
                if (fptr->write_lock && rb_mutex_owned_p(fptr->write_lock))
 | 
			
		||||
                    r = rb_write_internal2(fptr, ds, dp-ds);
 | 
			
		||||
                else
 | 
			
		||||
                    r = rb_write_internal(fptr, ds, dp-ds);
 | 
			
		||||
                if (r == dp-ds)
 | 
			
		||||
                    break;
 | 
			
		||||
                if (0 <= r) {
 | 
			
		||||
| 
						 | 
				
			
			@ -4796,7 +4829,7 @@ static int
 | 
			
		|||
maygvl_close(int fd, int keepgvl)
 | 
			
		||||
{
 | 
			
		||||
    if (keepgvl)
 | 
			
		||||
	return close(fd);
 | 
			
		||||
        return close(fd);
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * close() may block for certain file types (NFS, SO_LINGER sockets,
 | 
			
		||||
| 
						 | 
				
			
			@ -4817,7 +4850,7 @@ static int
 | 
			
		|||
maygvl_fclose(FILE *file, int keepgvl)
 | 
			
		||||
{
 | 
			
		||||
    if (keepgvl)
 | 
			
		||||
	return fclose(file);
 | 
			
		||||
        return fclose(file);
 | 
			
		||||
 | 
			
		||||
    return (int)(intptr_t)rb_thread_call_without_gvl(nogvl_fclose, file, RUBY_UBF_IO, 0);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -4835,64 +4868,77 @@ fptr_finalize_flush(rb_io_t *fptr, int noraise, int keepgvl,
 | 
			
		|||
    int mode = fptr->mode;
 | 
			
		||||
 | 
			
		||||
    if (fptr->writeconv) {
 | 
			
		||||
	if (fptr->write_lock && !noraise) {
 | 
			
		||||
        if (fptr->write_lock && !noraise) {
 | 
			
		||||
            struct finish_writeconv_arg arg;
 | 
			
		||||
            arg.fptr = fptr;
 | 
			
		||||
            arg.noalloc = noraise;
 | 
			
		||||
            err = rb_mutex_synchronize(fptr->write_lock, finish_writeconv_sync, (VALUE)&arg);
 | 
			
		||||
	}
 | 
			
		||||
	else {
 | 
			
		||||
	    err = finish_writeconv(fptr, noraise);
 | 
			
		||||
	}
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            err = finish_writeconv(fptr, noraise);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    if (fptr->wbuf.len) {
 | 
			
		||||
	if (noraise) {
 | 
			
		||||
	    io_flush_buffer_sync(fptr);
 | 
			
		||||
	}
 | 
			
		||||
	else {
 | 
			
		||||
	    if (io_fflush(fptr) < 0 && NIL_P(err))
 | 
			
		||||
		err = INT2NUM(errno);
 | 
			
		||||
	}
 | 
			
		||||
        if (noraise) {
 | 
			
		||||
            io_flush_buffer_sync(fptr);
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            if (io_fflush(fptr) < 0 && NIL_P(err))
 | 
			
		||||
        	err = INT2NUM(errno);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    int done = 0;
 | 
			
		||||
 | 
			
		||||
    if (IS_PREP_STDIO(fptr) || fd <= 2) {
 | 
			
		||||
        // Need to keep FILE objects of stdin, stdout and stderr, so we are done:
 | 
			
		||||
        done = 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fptr->fd = -1;
 | 
			
		||||
    fptr->stdio_file = 0;
 | 
			
		||||
    fptr->mode &= ~(FMODE_READABLE|FMODE_WRITABLE);
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * ensure waiting_fd users do not hit EBADF, wait for them
 | 
			
		||||
     * to exit before we call close().
 | 
			
		||||
     */
 | 
			
		||||
    // Ensure waiting_fd users do not hit EBADF.
 | 
			
		||||
    if (busy) {
 | 
			
		||||
        // Wait for them to exit before we call close().
 | 
			
		||||
        do rb_thread_schedule(); while (!list_empty(busy));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (IS_PREP_STDIO(fptr) || fd <= 2) {
 | 
			
		||||
	/* need to keep FILE objects of stdin, stdout and stderr */
 | 
			
		||||
    }
 | 
			
		||||
    else if (stdio_file) {
 | 
			
		||||
	/* stdio_file is deallocated anyway
 | 
			
		||||
         * even if fclose failed.  */
 | 
			
		||||
	if ((maygvl_fclose(stdio_file, noraise) < 0) && NIL_P(err))
 | 
			
		||||
	    if (!noraise) err = INT2NUM(errno);
 | 
			
		||||
    }
 | 
			
		||||
    else if (0 <= fd) {
 | 
			
		||||
	/* fptr->fd may be closed even if close fails.
 | 
			
		||||
         * POSIX doesn't specify it.
 | 
			
		||||
         * We assumes it is closed.  */
 | 
			
		||||
    // Disable for now.
 | 
			
		||||
    // if (!done && fd >= 0) {
 | 
			
		||||
    //     VALUE scheduler = rb_fiber_scheduler_current();
 | 
			
		||||
    //     if (scheduler != Qnil) {
 | 
			
		||||
    //         VALUE result = rb_fiber_scheduler_io_close(scheduler, fptr->self);
 | 
			
		||||
    //         if (result != Qundef) done = 1;
 | 
			
		||||
    //     }
 | 
			
		||||
    // }
 | 
			
		||||
 | 
			
		||||
	/**/
 | 
			
		||||
	keepgvl |= !(mode & FMODE_WRITABLE);
 | 
			
		||||
	keepgvl |= noraise;
 | 
			
		||||
	if ((maygvl_close(fd, keepgvl) < 0) && NIL_P(err))
 | 
			
		||||
	    if (!noraise) err = INT2NUM(errno);
 | 
			
		||||
    if (!done && stdio_file) {
 | 
			
		||||
        // stdio_file is deallocated anyway even if fclose failed.
 | 
			
		||||
        if ((maygvl_fclose(stdio_file, noraise) < 0) && NIL_P(err))
 | 
			
		||||
            if (!noraise) err = INT2NUM(errno);
 | 
			
		||||
 | 
			
		||||
        done = 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!done && fd >= 0) {
 | 
			
		||||
        // fptr->fd may be closed even if close fails. POSIX doesn't specify it.
 | 
			
		||||
        // We assumes it is closed.
 | 
			
		||||
 | 
			
		||||
        keepgvl |= !(mode & FMODE_WRITABLE);
 | 
			
		||||
        keepgvl |= noraise;
 | 
			
		||||
        if ((maygvl_close(fd, keepgvl) < 0) && NIL_P(err))
 | 
			
		||||
            if (!noraise) err = INT2NUM(errno);
 | 
			
		||||
 | 
			
		||||
        done = 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!NIL_P(err) && !noraise) {
 | 
			
		||||
	if (RB_INTEGER_TYPE_P(err))
 | 
			
		||||
	    rb_syserr_fail_path(NUM2INT(err), fptr->pathv);
 | 
			
		||||
	else
 | 
			
		||||
	    rb_exc_raise(err);
 | 
			
		||||
        if (RB_INTEGER_TYPE_P(err))
 | 
			
		||||
            rb_syserr_fail_path(NUM2INT(err), fptr->pathv);
 | 
			
		||||
        else
 | 
			
		||||
            rb_exc_raise(err);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -5333,7 +5379,7 @@ rb_io_syswrite(VALUE io, VALUE str)
 | 
			
		|||
 | 
			
		||||
    tmp = rb_str_tmp_frozen_acquire(str);
 | 
			
		||||
    RSTRING_GETMEM(tmp, ptr, len);
 | 
			
		||||
    n = rb_write_internal(fptr->fd, ptr, len);
 | 
			
		||||
    n = rb_write_internal(fptr, ptr, len);
 | 
			
		||||
    if (n < 0) rb_sys_fail_path(fptr->pathv);
 | 
			
		||||
    rb_str_tmp_frozen_release(str, tmp);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -5385,7 +5431,7 @@ rb_io_sysread(int argc, VALUE *argv, VALUE io)
 | 
			
		|||
 | 
			
		||||
    io_setstrbuf(&str, ilen);
 | 
			
		||||
    iis.th = rb_thread_current();
 | 
			
		||||
    iis.fd = fptr->fd;
 | 
			
		||||
    iis.fptr = fptr;
 | 
			
		||||
    iis.nonblock = 0;
 | 
			
		||||
    iis.buf = RSTRING_PTR(str);
 | 
			
		||||
    iis.capa = ilen;
 | 
			
		||||
| 
						 | 
				
			
			@ -11141,8 +11187,8 @@ struct copy_stream_struct {
 | 
			
		|||
    off_t copy_length; /* (off_t)-1 if not specified */
 | 
			
		||||
    off_t src_offset; /* (off_t)-1 if not specified */
 | 
			
		||||
 | 
			
		||||
    int src_fd;
 | 
			
		||||
    int dst_fd;
 | 
			
		||||
    rb_io_t *src_fptr;
 | 
			
		||||
    rb_io_t *dst_fptr;
 | 
			
		||||
    unsigned close_src : 1;
 | 
			
		||||
    unsigned close_dst : 1;
 | 
			
		||||
    int error_no;
 | 
			
		||||
| 
						 | 
				
			
			@ -11192,18 +11238,18 @@ maygvl_copy_stream_continue_p(int has_gvl, struct copy_stream_struct *stp)
 | 
			
		|||
struct wait_for_single_fd {
 | 
			
		||||
    VALUE scheduler;
 | 
			
		||||
 | 
			
		||||
    int fd;
 | 
			
		||||
    rb_io_t *fptr;
 | 
			
		||||
    short events;
 | 
			
		||||
 | 
			
		||||
    VALUE result;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static void *
 | 
			
		||||
rb_thread_fiber_scheduler_wait_for_single_fd(void * _args)
 | 
			
		||||
rb_thread_fiber_scheduler_wait_for(void * _args)
 | 
			
		||||
{
 | 
			
		||||
    struct wait_for_single_fd *args = (struct wait_for_single_fd *)_args;
 | 
			
		||||
 | 
			
		||||
    args->result = rb_fiber_scheduler_io_wait(args->scheduler, io_from_fd(args->fd), INT2NUM(args->events), Qnil);
 | 
			
		||||
    args->result = rb_fiber_scheduler_io_wait(args->scheduler, args->fptr->self, INT2NUM(args->events), Qnil);
 | 
			
		||||
 | 
			
		||||
    return NULL;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -11213,18 +11259,18 @@ rb_thread_fiber_scheduler_wait_for_single_fd(void * _args)
 | 
			
		|||
STATIC_ASSERT(pollin_expected, POLLIN == RB_WAITFD_IN);
 | 
			
		||||
STATIC_ASSERT(pollout_expected, POLLOUT == RB_WAITFD_OUT);
 | 
			
		||||
static int
 | 
			
		||||
nogvl_wait_for_single_fd(VALUE th, int fd, short events)
 | 
			
		||||
nogvl_wait_for(VALUE th, rb_io_t *fptr, short events)
 | 
			
		||||
{
 | 
			
		||||
    VALUE scheduler = rb_fiber_scheduler_current_for_thread(th);
 | 
			
		||||
    if (scheduler != Qnil) {
 | 
			
		||||
        struct wait_for_single_fd args = {.scheduler = scheduler, .fd = fd, .events = events};
 | 
			
		||||
        rb_thread_call_with_gvl(rb_thread_fiber_scheduler_wait_for_single_fd, &args);
 | 
			
		||||
        struct wait_for_single_fd args = {.scheduler = scheduler, .fptr = fptr, .events = events};
 | 
			
		||||
        rb_thread_call_with_gvl(rb_thread_fiber_scheduler_wait_for, &args);
 | 
			
		||||
        return RTEST(args.result);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    struct pollfd fds;
 | 
			
		||||
 | 
			
		||||
    fds.fd = fd;
 | 
			
		||||
    fds.fd = fptr->fd;
 | 
			
		||||
    fds.events = events;
 | 
			
		||||
 | 
			
		||||
    return poll(&fds, 1, -1);
 | 
			
		||||
| 
						 | 
				
			
			@ -11232,12 +11278,12 @@ nogvl_wait_for_single_fd(VALUE th, int fd, short events)
 | 
			
		|||
#else /* !USE_POLL */
 | 
			
		||||
#  define IOWAIT_SYSCALL "select"
 | 
			
		||||
static int
 | 
			
		||||
nogvl_wait_for_single_fd(VALUE th, int fd, short events)
 | 
			
		||||
nogvl_wait_for(VALUE th, rb_io_t *fptr, short events)
 | 
			
		||||
{
 | 
			
		||||
    VALUE scheduler = rb_fiber_scheduler_current_for_thread(th);
 | 
			
		||||
    if (scheduler != Qnil) {
 | 
			
		||||
        struct wait_for_single_fd args = {.scheduler = scheduler, .fd = fd, .events = events};
 | 
			
		||||
        rb_thread_call_with_gvl(rb_thread_fiber_scheduler_wait_for_single_fd, &args);
 | 
			
		||||
        struct wait_for_single_fd args = {.scheduler = scheduler, .fptr = fptr, .events = events};
 | 
			
		||||
        rb_thread_call_with_gvl(rb_thread_fiber_scheduler_wait_for, &args);
 | 
			
		||||
        return RTEST(args.result);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -11245,17 +11291,17 @@ nogvl_wait_for_single_fd(VALUE th, int fd, short events)
 | 
			
		|||
    int ret;
 | 
			
		||||
 | 
			
		||||
    rb_fd_init(&fds);
 | 
			
		||||
    rb_fd_set(fd, &fds);
 | 
			
		||||
    rb_fd_set(fptr->fd, &fds);
 | 
			
		||||
 | 
			
		||||
    switch (events) {
 | 
			
		||||
      case RB_WAITFD_IN:
 | 
			
		||||
        ret = rb_fd_select(fd + 1, &fds, 0, 0, 0);
 | 
			
		||||
        ret = rb_fd_select(fptr->fd + 1, &fds, 0, 0, 0);
 | 
			
		||||
        break;
 | 
			
		||||
      case RB_WAITFD_OUT:
 | 
			
		||||
        ret = rb_fd_select(fd + 1, 0, &fds, 0, 0);
 | 
			
		||||
        ret = rb_fd_select(fptr->fd + 1, 0, &fds, 0, 0);
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        VM_UNREACHABLE(nogvl_wait_for_single_fd);
 | 
			
		||||
        VM_UNREACHABLE(nogvl_wait_for);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    rb_fd_term(&fds);
 | 
			
		||||
| 
						 | 
				
			
			@ -11273,7 +11319,7 @@ maygvl_copy_stream_wait_read(int has_gvl, struct copy_stream_struct *stp)
 | 
			
		|||
            ret = RB_NUM2INT(rb_io_wait(stp->src, RB_INT2NUM(RUBY_IO_READABLE), Qnil));
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            ret = nogvl_wait_for_single_fd(stp->th, stp->src_fd, RB_WAITFD_IN);
 | 
			
		||||
            ret = nogvl_wait_for(stp->th, stp->src_fptr, RB_WAITFD_IN);
 | 
			
		||||
        }
 | 
			
		||||
    } while (ret < 0 && maygvl_copy_stream_continue_p(has_gvl, stp));
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -11291,7 +11337,7 @@ nogvl_copy_stream_wait_write(struct copy_stream_struct *stp)
 | 
			
		|||
    int ret;
 | 
			
		||||
 | 
			
		||||
    do {
 | 
			
		||||
	ret = nogvl_wait_for_single_fd(stp->th, stp->dst_fd, RB_WAITFD_OUT);
 | 
			
		||||
	ret = nogvl_wait_for(stp->th, stp->dst_fptr, RB_WAITFD_OUT);
 | 
			
		||||
    } while (ret < 0 && maygvl_copy_stream_continue_p(0, stp));
 | 
			
		||||
 | 
			
		||||
    if (ret < 0) {
 | 
			
		||||
| 
						 | 
				
			
			@ -11338,7 +11384,7 @@ nogvl_copy_file_range(struct copy_stream_struct *stp)
 | 
			
		|||
        if (src_offset < (off_t)0) {
 | 
			
		||||
	    off_t current_offset;
 | 
			
		||||
            errno = 0;
 | 
			
		||||
            current_offset = lseek(stp->src_fd, 0, SEEK_CUR);
 | 
			
		||||
            current_offset = lseek(stp->src_fptr->fd, 0, SEEK_CUR);
 | 
			
		||||
            if (current_offset < (off_t)0 && errno) {
 | 
			
		||||
                stp->syserr = "lseek";
 | 
			
		||||
                stp->error_no = errno;
 | 
			
		||||
| 
						 | 
				
			
			@ -11358,7 +11404,7 @@ nogvl_copy_file_range(struct copy_stream_struct *stp)
 | 
			
		|||
# else
 | 
			
		||||
    ss = (ssize_t)copy_length;
 | 
			
		||||
# endif
 | 
			
		||||
    ss = simple_copy_file_range(stp->src_fd, src_offset_ptr, stp->dst_fd, NULL, ss, 0);
 | 
			
		||||
    ss = simple_copy_file_range(stp->src_fptr->fd, src_offset_ptr, stp->dst_fptr->fd, NULL, ss, 0);
 | 
			
		||||
    if (0 < ss) {
 | 
			
		||||
        stp->total += ss;
 | 
			
		||||
        copy_length -= ss;
 | 
			
		||||
| 
						 | 
				
			
			@ -11393,7 +11439,7 @@ nogvl_copy_file_range(struct copy_stream_struct *stp)
 | 
			
		|||
	  case EBADF:
 | 
			
		||||
	    {
 | 
			
		||||
		int e = errno;
 | 
			
		||||
		int flags = fcntl(stp->dst_fd, F_GETFL);
 | 
			
		||||
		int flags = fcntl(stp->dst_fptr->fd, F_GETFL);
 | 
			
		||||
 | 
			
		||||
		if (flags != -1 && flags & O_APPEND) {
 | 
			
		||||
		    return 0;
 | 
			
		||||
| 
						 | 
				
			
			@ -11427,7 +11473,7 @@ nogvl_fcopyfile(struct copy_stream_struct *stp)
 | 
			
		|||
 | 
			
		||||
    if (!S_ISREG(stp->dst_stat.st_mode))
 | 
			
		||||
        return 0;
 | 
			
		||||
    if (lseek(stp->dst_fd, 0, SEEK_CUR) > (off_t)0) /* if dst IO was already written */
 | 
			
		||||
    if (lseek(stp->dst_fptr->fd, 0, SEEK_CUR) > (off_t)0) /* if dst IO was already written */
 | 
			
		||||
        return 0;
 | 
			
		||||
 | 
			
		||||
    if (src_offset > (off_t)0) {
 | 
			
		||||
| 
						 | 
				
			
			@ -11435,14 +11481,14 @@ nogvl_fcopyfile(struct copy_stream_struct *stp)
 | 
			
		|||
 | 
			
		||||
        /* get current offset */
 | 
			
		||||
        errno = 0;
 | 
			
		||||
        cur = lseek(stp->src_fd, 0, SEEK_CUR);
 | 
			
		||||
        cur = lseek(stp->src_fptr->fd, 0, SEEK_CUR);
 | 
			
		||||
        if (cur < (off_t)0 && errno) {
 | 
			
		||||
            stp->error_no = errno;
 | 
			
		||||
            return 1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        errno = 0;
 | 
			
		||||
        r = lseek(stp->src_fd, src_offset, SEEK_SET);
 | 
			
		||||
        r = lseek(stp->src_fptr->fd, src_offset, SEEK_SET);
 | 
			
		||||
        if (r < (off_t)0 && errno) {
 | 
			
		||||
            stp->error_no = errno;
 | 
			
		||||
            return 1;
 | 
			
		||||
| 
						 | 
				
			
			@ -11450,7 +11496,7 @@ nogvl_fcopyfile(struct copy_stream_struct *stp)
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    stp->copyfile_state = copyfile_state_alloc(); /* this will be freed by copy_stream_finalize() */
 | 
			
		||||
    ret = fcopyfile(stp->src_fd, stp->dst_fd, stp->copyfile_state, COPYFILE_DATA);
 | 
			
		||||
    ret = fcopyfile(stp->src_fptr->fd, stp->dst_fptr->fd, stp->copyfile_state, COPYFILE_DATA);
 | 
			
		||||
    copyfile_state_get(stp->copyfile_state, COPYFILE_STATE_COPIED, &ss); /* get copied bytes */
 | 
			
		||||
 | 
			
		||||
    if (ret == 0) { /* success */
 | 
			
		||||
| 
						 | 
				
			
			@ -11459,7 +11505,7 @@ nogvl_fcopyfile(struct copy_stream_struct *stp)
 | 
			
		|||
            off_t r;
 | 
			
		||||
            errno = 0;
 | 
			
		||||
            /* reset offset */
 | 
			
		||||
            r = lseek(stp->src_fd, cur, SEEK_SET);
 | 
			
		||||
            r = lseek(stp->src_fptr->fd, cur, SEEK_SET);
 | 
			
		||||
            if (r < (off_t)0 && errno) {
 | 
			
		||||
                stp->error_no = errno;
 | 
			
		||||
                return 1;
 | 
			
		||||
| 
						 | 
				
			
			@ -11557,7 +11603,7 @@ nogvl_copy_stream_sendfile(struct copy_stream_struct *stp)
 | 
			
		|||
        else {
 | 
			
		||||
            off_t cur;
 | 
			
		||||
            errno = 0;
 | 
			
		||||
            cur = lseek(stp->src_fd, 0, SEEK_CUR);
 | 
			
		||||
            cur = lseek(stp->src_fptr->fd, 0, SEEK_CUR);
 | 
			
		||||
            if (cur < (off_t)0 && errno) {
 | 
			
		||||
                stp->syserr = "lseek";
 | 
			
		||||
                stp->error_no = errno;
 | 
			
		||||
| 
						 | 
				
			
			@ -11575,10 +11621,10 @@ nogvl_copy_stream_sendfile(struct copy_stream_struct *stp)
 | 
			
		|||
    ss = (ssize_t)copy_length;
 | 
			
		||||
# endif
 | 
			
		||||
    if (use_pread) {
 | 
			
		||||
        ss = simple_sendfile(stp->dst_fd, stp->src_fd, &src_offset, ss);
 | 
			
		||||
        ss = simple_sendfile(stp->dst_fptr->fd, stp->src_fptr->fd, &src_offset, ss);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        ss = simple_sendfile(stp->dst_fd, stp->src_fd, NULL, ss);
 | 
			
		||||
        ss = simple_sendfile(stp->dst_fptr->fd, stp->src_fptr->fd, NULL, ss);
 | 
			
		||||
    }
 | 
			
		||||
    if (0 < ss) {
 | 
			
		||||
        stp->total += ss;
 | 
			
		||||
| 
						 | 
				
			
			@ -11609,7 +11655,7 @@ nogvl_copy_stream_sendfile(struct copy_stream_struct *stp)
 | 
			
		|||
                int ret;
 | 
			
		||||
#ifndef __linux__
 | 
			
		||||
               /*
 | 
			
		||||
                * Linux requires stp->src_fd to be a mmap-able (regular) file,
 | 
			
		||||
                * Linux requires stp->src_fptr->fd to be a mmap-able (regular) file,
 | 
			
		||||
                * select() reports regular files to always be "ready", so
 | 
			
		||||
                * there is no need to select() on it.
 | 
			
		||||
                * Other OSes may have the same limitation for sendfile() which
 | 
			
		||||
| 
						 | 
				
			
			@ -11632,12 +11678,12 @@ nogvl_copy_stream_sendfile(struct copy_stream_struct *stp)
 | 
			
		|||
#endif
 | 
			
		||||
 | 
			
		||||
static ssize_t
 | 
			
		||||
maygvl_read(int has_gvl, int fd, void *buf, size_t count)
 | 
			
		||||
maygvl_read(int has_gvl, rb_io_t *fptr, void *buf, size_t count)
 | 
			
		||||
{
 | 
			
		||||
    if (has_gvl)
 | 
			
		||||
        return rb_read_internal(fd, buf, count);
 | 
			
		||||
        return rb_read_internal(fptr, buf, count);
 | 
			
		||||
    else
 | 
			
		||||
        return read(fd, buf, count);
 | 
			
		||||
        return read(fptr->fd, buf, count);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static ssize_t
 | 
			
		||||
| 
						 | 
				
			
			@ -11646,11 +11692,11 @@ maygvl_copy_stream_read(int has_gvl, struct copy_stream_struct *stp, char *buf,
 | 
			
		|||
    ssize_t ss;
 | 
			
		||||
  retry_read:
 | 
			
		||||
    if (offset < (off_t)0) {
 | 
			
		||||
        ss = maygvl_read(has_gvl, stp->src_fd, buf, len);
 | 
			
		||||
        ss = maygvl_read(has_gvl, stp->src_fptr, buf, len);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
#ifdef HAVE_PREAD
 | 
			
		||||
        ss = pread(stp->src_fd, buf, len, offset);
 | 
			
		||||
        ss = pread(stp->src_fptr->fd, buf, len, offset);
 | 
			
		||||
#else
 | 
			
		||||
        stp->notimp = "pread";
 | 
			
		||||
        return -1;
 | 
			
		||||
| 
						 | 
				
			
			@ -11690,7 +11736,7 @@ nogvl_copy_stream_write(struct copy_stream_struct *stp, char *buf, size_t len)
 | 
			
		|||
    ssize_t ss;
 | 
			
		||||
    int off = 0;
 | 
			
		||||
    while (len) {
 | 
			
		||||
        ss = write(stp->dst_fd, buf+off, len);
 | 
			
		||||
        ss = write(stp->dst_fptr->fd, buf+off, len);
 | 
			
		||||
        if (ss < 0) {
 | 
			
		||||
            if (maygvl_copy_stream_continue_p(0, stp))
 | 
			
		||||
                continue;
 | 
			
		||||
| 
						 | 
				
			
			@ -11730,7 +11776,7 @@ nogvl_copy_stream_read_write(struct copy_stream_struct *stp)
 | 
			
		|||
    if (use_pread && stp->close_src) {
 | 
			
		||||
        off_t r;
 | 
			
		||||
	errno = 0;
 | 
			
		||||
        r = lseek(stp->src_fd, src_offset, SEEK_SET);
 | 
			
		||||
        r = lseek(stp->src_fptr->fd, src_offset, SEEK_SET);
 | 
			
		||||
        if (r < (off_t)0 && errno) {
 | 
			
		||||
            stp->syserr = "lseek";
 | 
			
		||||
            stp->error_no = errno;
 | 
			
		||||
| 
						 | 
				
			
			@ -11812,7 +11858,7 @@ copy_stream_fallback_body(VALUE arg)
 | 
			
		|||
    off_t off = stp->src_offset;
 | 
			
		||||
    ID read_method = id_readpartial;
 | 
			
		||||
 | 
			
		||||
    if (stp->src_fd < 0) {
 | 
			
		||||
    if (!stp->src_fptr) {
 | 
			
		||||
	if (!rb_respond_to(stp->src, read_method)) {
 | 
			
		||||
	    read_method = id_read;
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -11831,7 +11877,7 @@ copy_stream_fallback_body(VALUE arg)
 | 
			
		|||
	    }
 | 
			
		||||
            l = buflen < rest ? buflen : (long)rest;
 | 
			
		||||
        }
 | 
			
		||||
        if (stp->src_fd < 0) {
 | 
			
		||||
        if (!stp->src_fptr) {
 | 
			
		||||
            VALUE rc = rb_funcall(stp->src, read_method, 2, INT2FIX(l), buf);
 | 
			
		||||
 | 
			
		||||
            if (read_method == id_read && NIL_P(rc))
 | 
			
		||||
| 
						 | 
				
			
			@ -11864,7 +11910,7 @@ copy_stream_fallback_body(VALUE arg)
 | 
			
		|||
static VALUE
 | 
			
		||||
copy_stream_fallback(struct copy_stream_struct *stp)
 | 
			
		||||
{
 | 
			
		||||
    if (stp->src_fd < 0 && stp->src_offset >= (off_t)0) {
 | 
			
		||||
    if (!stp->src_fptr && stp->src_offset >= (off_t)0) {
 | 
			
		||||
	rb_raise(rb_eArgError, "cannot specify src_offset for non-IO");
 | 
			
		||||
    }
 | 
			
		||||
    rb_rescue2(copy_stream_fallback_body, (VALUE)stp,
 | 
			
		||||
| 
						 | 
				
			
			@ -11878,8 +11924,6 @@ copy_stream_body(VALUE arg)
 | 
			
		|||
{
 | 
			
		||||
    struct copy_stream_struct *stp = (struct copy_stream_struct *)arg;
 | 
			
		||||
    VALUE src_io = stp->src, dst_io = stp->dst;
 | 
			
		||||
    rb_io_t *src_fptr = 0, *dst_fptr = 0;
 | 
			
		||||
    int src_fd, dst_fd;
 | 
			
		||||
    const int common_oflags = 0
 | 
			
		||||
#ifdef O_NOCTTY
 | 
			
		||||
	| O_NOCTTY
 | 
			
		||||
| 
						 | 
				
			
			@ -11894,7 +11938,7 @@ copy_stream_body(VALUE arg)
 | 
			
		|||
	!(RB_TYPE_P(src_io, T_FILE) ||
 | 
			
		||||
	  RB_TYPE_P(src_io, T_STRING) ||
 | 
			
		||||
	  rb_respond_to(src_io, rb_intern("to_path")))) {
 | 
			
		||||
	src_fd = -1;
 | 
			
		||||
        stp->src_fptr = NULL;
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        int stat_ret;
 | 
			
		||||
| 
						 | 
				
			
			@ -11911,24 +11955,22 @@ copy_stream_body(VALUE arg)
 | 
			
		|||
	    stp->src = src_io;
 | 
			
		||||
	    stp->close_src = 1;
 | 
			
		||||
	}
 | 
			
		||||
	GetOpenFile(src_io, src_fptr);
 | 
			
		||||
	rb_io_check_byte_readable(src_fptr);
 | 
			
		||||
	src_fd = src_fptr->fd;
 | 
			
		||||
	RB_IO_POINTER(src_io, stp->src_fptr);
 | 
			
		||||
	rb_io_check_byte_readable(stp->src_fptr);
 | 
			
		||||
 | 
			
		||||
        stat_ret = fstat(src_fd, &stp->src_stat);
 | 
			
		||||
        stat_ret = fstat(stp->src_fptr->fd, &stp->src_stat);
 | 
			
		||||
        if (stat_ret < 0) {
 | 
			
		||||
            stp->syserr = "fstat";
 | 
			
		||||
            stp->error_no = errno;
 | 
			
		||||
            return Qnil;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    stp->src_fd = src_fd;
 | 
			
		||||
 | 
			
		||||
    if (dst_io == argf ||
 | 
			
		||||
	!(RB_TYPE_P(dst_io, T_FILE) ||
 | 
			
		||||
	  RB_TYPE_P(dst_io, T_STRING) ||
 | 
			
		||||
	  rb_respond_to(dst_io, rb_intern("to_path")))) {
 | 
			
		||||
	dst_fd = -1;
 | 
			
		||||
	stp->dst_fptr = NULL;
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        int stat_ret;
 | 
			
		||||
| 
						 | 
				
			
			@ -11950,38 +11992,36 @@ copy_stream_body(VALUE arg)
 | 
			
		|||
	    dst_io = GetWriteIO(dst_io);
 | 
			
		||||
	    stp->dst = dst_io;
 | 
			
		||||
	}
 | 
			
		||||
	GetOpenFile(dst_io, dst_fptr);
 | 
			
		||||
	rb_io_check_writable(dst_fptr);
 | 
			
		||||
	dst_fd = dst_fptr->fd;
 | 
			
		||||
	RB_IO_POINTER(dst_io, stp->dst_fptr);
 | 
			
		||||
	rb_io_check_writable(stp->dst_fptr);
 | 
			
		||||
 | 
			
		||||
        stat_ret = fstat(dst_fd, &stp->dst_stat);
 | 
			
		||||
        stat_ret = fstat(stp->dst_fptr->fd, &stp->dst_stat);
 | 
			
		||||
        if (stat_ret < 0) {
 | 
			
		||||
            stp->syserr = "fstat";
 | 
			
		||||
            stp->error_no = errno;
 | 
			
		||||
            return Qnil;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    stp->dst_fd = dst_fd;
 | 
			
		||||
 | 
			
		||||
#ifdef O_BINARY
 | 
			
		||||
    if (src_fptr)
 | 
			
		||||
	SET_BINARY_MODE_WITH_SEEK_CUR(src_fptr);
 | 
			
		||||
    if (stp->src_fptr)
 | 
			
		||||
	SET_BINARY_MODE_WITH_SEEK_CUR(stp->src_fptr);
 | 
			
		||||
#endif
 | 
			
		||||
    if (dst_fptr)
 | 
			
		||||
	io_ascii8bit_binmode(dst_fptr);
 | 
			
		||||
    if (stp->dst_fptr)
 | 
			
		||||
	io_ascii8bit_binmode(stp->dst_fptr);
 | 
			
		||||
 | 
			
		||||
    if (stp->src_offset < (off_t)0 && src_fptr && src_fptr->rbuf.len) {
 | 
			
		||||
        size_t len = src_fptr->rbuf.len;
 | 
			
		||||
    if (stp->src_offset < (off_t)0 && stp->src_fptr && stp->src_fptr->rbuf.len) {
 | 
			
		||||
        size_t len = stp->src_fptr->rbuf.len;
 | 
			
		||||
        VALUE str;
 | 
			
		||||
        if (stp->copy_length >= (off_t)0 && stp->copy_length < (off_t)len) {
 | 
			
		||||
            len = (size_t)stp->copy_length;
 | 
			
		||||
        }
 | 
			
		||||
        str = rb_str_buf_new(len);
 | 
			
		||||
        rb_str_resize(str,len);
 | 
			
		||||
        read_buffered_data(RSTRING_PTR(str), len, src_fptr);
 | 
			
		||||
        if (dst_fptr) { /* IO or filename */
 | 
			
		||||
            if (io_binwrite(str, RSTRING_PTR(str), RSTRING_LEN(str), dst_fptr, 0) < 0)
 | 
			
		||||
                rb_sys_fail_on_write(dst_fptr);
 | 
			
		||||
        read_buffered_data(RSTRING_PTR(str), len, stp->src_fptr);
 | 
			
		||||
        if (stp->dst_fptr) { /* IO or filename */
 | 
			
		||||
            if (io_binwrite(str, RSTRING_PTR(str), RSTRING_LEN(str), stp->dst_fptr, 0) < 0)
 | 
			
		||||
                rb_sys_fail_on_write(stp->dst_fptr);
 | 
			
		||||
        }
 | 
			
		||||
        else /* others such as StringIO */
 | 
			
		||||
	    rb_io_write(dst_io, str);
 | 
			
		||||
| 
						 | 
				
			
			@ -11991,14 +12031,14 @@ copy_stream_body(VALUE arg)
 | 
			
		|||
            stp->copy_length -= len;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (dst_fptr && io_fflush(dst_fptr) < 0) {
 | 
			
		||||
    if (stp->dst_fptr && io_fflush(stp->dst_fptr) < 0) {
 | 
			
		||||
	rb_raise(rb_eIOError, "flush failed");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (stp->copy_length == 0)
 | 
			
		||||
        return Qnil;
 | 
			
		||||
 | 
			
		||||
    if (src_fd < 0 || dst_fd < 0) {
 | 
			
		||||
    if (stp->src_fptr == NULL || stp->dst_fptr == NULL) {
 | 
			
		||||
        return copy_stream_fallback(stp);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -12077,6 +12117,9 @@ rb_io_s_copy_stream(int argc, VALUE *argv, VALUE io)
 | 
			
		|||
    st.src = src;
 | 
			
		||||
    st.dst = dst;
 | 
			
		||||
 | 
			
		||||
    st.src_fptr = NULL;
 | 
			
		||||
    st.dst_fptr = NULL;
 | 
			
		||||
 | 
			
		||||
    if (NIL_P(length))
 | 
			
		||||
        st.copy_length = (off_t)-1;
 | 
			
		||||
    else
 | 
			
		||||
| 
						 | 
				
			
			@ -13678,6 +13721,7 @@ Init_IO(void)
 | 
			
		|||
    id_flush = rb_intern_const("flush");
 | 
			
		||||
    id_readpartial = rb_intern_const("readpartial");
 | 
			
		||||
    id_set_encoding = rb_intern_const("set_encoding");
 | 
			
		||||
    id_fileno = rb_intern_const("fileno");
 | 
			
		||||
 | 
			
		||||
    rb_define_global_function("syscall", rb_f_syscall, -1);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										1024
									
								
								io_buffer.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1024
									
								
								io_buffer.c
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										49
									
								
								scheduler.c
									
										
									
									
									
								
							
							
						
						
									
										49
									
								
								scheduler.c
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -11,6 +11,8 @@
 | 
			
		|||
#include "vm_core.h"
 | 
			
		||||
#include "ruby/fiber/scheduler.h"
 | 
			
		||||
#include "ruby/io.h"
 | 
			
		||||
#include "ruby/io/buffer.h"
 | 
			
		||||
 | 
			
		||||
#include "internal/thread.h"
 | 
			
		||||
 | 
			
		||||
static ID id_close;
 | 
			
		||||
| 
						 | 
				
			
			@ -26,6 +28,7 @@ static ID id_process_wait;
 | 
			
		|||
static ID id_io_read;
 | 
			
		||||
static ID id_io_write;
 | 
			
		||||
static ID id_io_wait;
 | 
			
		||||
static ID id_io_close;
 | 
			
		||||
 | 
			
		||||
static ID id_address_resolve;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -45,6 +48,7 @@ Init_Fiber_Scheduler(void)
 | 
			
		|||
    id_io_read = rb_intern_const("io_read");
 | 
			
		||||
    id_io_write = rb_intern_const("io_write");
 | 
			
		||||
    id_io_wait = rb_intern_const("io_wait");
 | 
			
		||||
    id_io_close = rb_intern_const("io_close");
 | 
			
		||||
 | 
			
		||||
    id_address_resolve = rb_intern_const("address_resolve");
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -225,24 +229,55 @@ rb_fiber_scheduler_io_wait_writable(VALUE scheduler, VALUE io)
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
VALUE
 | 
			
		||||
rb_fiber_scheduler_io_read(VALUE scheduler, VALUE io, VALUE buffer, size_t offset, size_t length)
 | 
			
		||||
rb_fiber_scheduler_io_read(VALUE scheduler, VALUE io, VALUE buffer, size_t length)
 | 
			
		||||
{
 | 
			
		||||
    VALUE arguments[] = {
 | 
			
		||||
        io, buffer, SIZET2NUM(offset), SIZET2NUM(length)
 | 
			
		||||
        io, buffer, SIZET2NUM(length)
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return rb_check_funcall(scheduler, id_io_read, 4, arguments);
 | 
			
		||||
    return rb_check_funcall(scheduler, id_io_read, 3, arguments);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
VALUE
 | 
			
		||||
rb_fiber_scheduler_io_write(VALUE scheduler, VALUE io, VALUE buffer, size_t offset, size_t length)
 | 
			
		||||
rb_fiber_scheduler_io_write(VALUE scheduler, VALUE io, VALUE buffer, size_t length)
 | 
			
		||||
{
 | 
			
		||||
    VALUE arguments[] = {
 | 
			
		||||
        io, buffer, SIZET2NUM(offset), SIZET2NUM(length)
 | 
			
		||||
        io, buffer, SIZET2NUM(length)
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // We should ensure string has capacity to receive data, and then resize it afterwards.
 | 
			
		||||
    return rb_check_funcall(scheduler, id_io_write, 4, arguments);
 | 
			
		||||
    return rb_check_funcall(scheduler, id_io_write, 3, arguments);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
VALUE
 | 
			
		||||
rb_fiber_scheduler_io_read_memory(VALUE scheduler, VALUE io, void *base, size_t size, size_t length)
 | 
			
		||||
{
 | 
			
		||||
    VALUE buffer = rb_io_buffer_new(base, size, RB_IO_BUFFER_LOCKED);
 | 
			
		||||
 | 
			
		||||
    VALUE result = rb_fiber_scheduler_io_read(scheduler, io, buffer, length);
 | 
			
		||||
 | 
			
		||||
    rb_io_buffer_free(buffer);
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
VALUE
 | 
			
		||||
rb_fiber_scheduler_io_write_memory(VALUE scheduler, VALUE io, const void *base, size_t size, size_t length)
 | 
			
		||||
{
 | 
			
		||||
    VALUE buffer = rb_io_buffer_new((void*)base, size, RB_IO_BUFFER_LOCKED|RB_IO_BUFFER_IMMUTABLE);
 | 
			
		||||
 | 
			
		||||
    VALUE result = rb_fiber_scheduler_io_write(scheduler, io, buffer, length);
 | 
			
		||||
 | 
			
		||||
    rb_io_buffer_free(buffer);
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
VALUE
 | 
			
		||||
rb_fiber_scheduler_io_close(VALUE scheduler, VALUE io)
 | 
			
		||||
{
 | 
			
		||||
    VALUE arguments[] = {io};
 | 
			
		||||
 | 
			
		||||
    return rb_check_funcall(scheduler, id_io_close, 1, arguments);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
VALUE
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										131
									
								
								test/ruby/test_io_buffer.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								test/ruby/test_io_buffer.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,131 @@
 | 
			
		|||
# frozen_string_literal: false
 | 
			
		||||
 | 
			
		||||
class TestIOBuffer < Test::Unit::TestCase
 | 
			
		||||
  def assert_negative(value)
 | 
			
		||||
    assert(value < 0, "Expected #{value} to be negative!")
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def assert_positive(value)
 | 
			
		||||
    assert(value > 0, "Expected #{value} to be positive!")
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def test_flags
 | 
			
		||||
    assert_equal 0, IO::Buffer::EXTERNAL
 | 
			
		||||
    assert_equal 1, IO::Buffer::INTERNAL
 | 
			
		||||
    assert_equal 2, IO::Buffer::MAPPED
 | 
			
		||||
 | 
			
		||||
    assert_equal 16, IO::Buffer::LOCKED
 | 
			
		||||
    assert_equal 32, IO::Buffer::PRIVATE
 | 
			
		||||
 | 
			
		||||
    assert_equal 64, IO::Buffer::IMMUTABLE
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def test_endian
 | 
			
		||||
    assert_equal 4, IO::Buffer::LITTLE_ENDIAN
 | 
			
		||||
    assert_equal 8, IO::Buffer::BIG_ENDIAN
 | 
			
		||||
    assert_equal 8, IO::Buffer::NETWORK_ENDIAN
 | 
			
		||||
 | 
			
		||||
    assert_include [IO::Buffer::LITTLE_ENDIAN, IO::Buffer::BIG_ENDIAN], IO::Buffer::HOST_ENDIAN
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def test_new_internal
 | 
			
		||||
    buffer = IO::Buffer.new(1024, IO::Buffer::INTERNAL)
 | 
			
		||||
    assert_equal 1024, buffer.size
 | 
			
		||||
    refute buffer.external?
 | 
			
		||||
    assert buffer.internal?
 | 
			
		||||
    refute buffer.mapped?
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def test_new_mapped
 | 
			
		||||
    buffer = IO::Buffer.new(1024, IO::Buffer::MAPPED)
 | 
			
		||||
    assert_equal 1024, buffer.size
 | 
			
		||||
    refute buffer.external?
 | 
			
		||||
    refute buffer.internal?
 | 
			
		||||
    assert buffer.mapped?
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def test_file_mapped
 | 
			
		||||
    buffer = File.open(__FILE__) {|file| IO::Buffer.map(file)}
 | 
			
		||||
    assert_include buffer.to_str, "Hello World"
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def test_resize
 | 
			
		||||
    buffer = IO::Buffer.new(1024, IO::Buffer::MAPPED)
 | 
			
		||||
    buffer.resize(2048, 0)
 | 
			
		||||
    assert_equal 2048, buffer.size
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def test_resize_preserve
 | 
			
		||||
    message = "Hello World"
 | 
			
		||||
    buffer = IO::Buffer.new(1024, IO::Buffer::MAPPED)
 | 
			
		||||
    buffer.copy(message, 0)
 | 
			
		||||
    buffer.resize(2048, 1024)
 | 
			
		||||
    assert_equal message, buffer.to_str(0, message.bytesize)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def test_compare_same_size
 | 
			
		||||
    buffer1 = IO::Buffer.new(1)
 | 
			
		||||
    assert_equal buffer1, buffer1
 | 
			
		||||
 | 
			
		||||
    buffer2 = IO::Buffer.new(1)
 | 
			
		||||
    buffer1.set(:U8, 0, 0x10)
 | 
			
		||||
    buffer2.set(:U8, 0, 0x20)
 | 
			
		||||
 | 
			
		||||
    assert_negative buffer1 <=> buffer2
 | 
			
		||||
    assert_positive buffer2 <=> buffer1
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def test_compare_different_size
 | 
			
		||||
    buffer1 = IO::Buffer.new(3)
 | 
			
		||||
    buffer2 = IO::Buffer.new(5)
 | 
			
		||||
 | 
			
		||||
    assert_negative buffer1 <=> buffer2
 | 
			
		||||
    assert_positive buffer2 <=> buffer1
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def test_slice
 | 
			
		||||
    buffer = IO::Buffer.new(128)
 | 
			
		||||
    slice = buffer.slice(8, 32)
 | 
			
		||||
    slice.copy("Hello World", 0)
 | 
			
		||||
    assert_equal("Hello World", buffer.to_str(8, 11))
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def test_slice_bounds
 | 
			
		||||
    buffer = IO::Buffer.new(128)
 | 
			
		||||
 | 
			
		||||
    # What is best exception class?
 | 
			
		||||
    assert_raise RuntimeError do
 | 
			
		||||
      buffer.slice(128, 10)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # assert_raise RuntimeError do
 | 
			
		||||
    #   pp buffer.slice(-10, 10)
 | 
			
		||||
    # end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def test_invalidation
 | 
			
		||||
    input, output = IO.pipe
 | 
			
		||||
 | 
			
		||||
    # (1) rb_write_internal creates IO::Buffer object,
 | 
			
		||||
    buffer = IO::Buffer.new(128)
 | 
			
		||||
 | 
			
		||||
    # (2) it is passed to (malicious) scheduler
 | 
			
		||||
    # (3) scheduler starts a thread which call system call with the buffer object
 | 
			
		||||
    thread = Thread.new{buffer.locked{input.read}}
 | 
			
		||||
 | 
			
		||||
    Thread.pass until thread.stop?
 | 
			
		||||
 | 
			
		||||
    # (4) scheduler returns
 | 
			
		||||
    # (5) rb_write_internal invalidate the buffer object
 | 
			
		||||
    assert_raise RuntimeError do
 | 
			
		||||
      buffer.free
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # (6) the system call access the memory area after invalidation
 | 
			
		||||
    output.write("Hello World")
 | 
			
		||||
    output.close
 | 
			
		||||
    thread.join
 | 
			
		||||
 | 
			
		||||
    input.close
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue