Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions spec/std/io/file_descriptor_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,24 @@ describe IO::FileDescriptor do
end
{% end %}

it ".set_blocking and .get_blocking" do
File.open(datapath("test_file.txt"), "r") do |file|
fd = file.fd

{% if flag?(:win32) %}
expect_raises(NotImplementedError) { IO::FileDescriptor.set_blocking(fd, false) }
expect_raises(NotImplementedError) { IO::FileDescriptor.set_blocking(fd, true) }
expect_raises(NotImplementedError) { IO::FileDescriptor.get_blocking(fd) }
{% else %}
IO::FileDescriptor.set_blocking(fd, false)
IO::FileDescriptor.get_blocking(fd).should be_false

IO::FileDescriptor.set_blocking(fd, true)
IO::FileDescriptor.get_blocking(fd).should be_true
{% end %}
end
end

typeof(STDIN.noecho { })
typeof(STDIN.noecho!)
typeof(STDIN.echo { })
Expand Down
19 changes: 19 additions & 0 deletions spec/std/socket/socket_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,25 @@ describe Socket, tags: "network" do
end
{% end %}

it ".set_blocking and .get_blocking" do
socket = Socket.tcp(Socket::Family::INET)
fd = socket.fd

Socket.set_blocking(fd, true)
{% if flag?(:win32) %}
expect_raises(NotImplementedError) { IO::FileDescriptor.get_blocking(fd) }
{% else %}
Socket.get_blocking(fd).should be_true
{% end %}

Socket.set_blocking(fd, false)
{% if flag?(:win32) %}
expect_raises(NotImplementedError) { IO::FileDescriptor.get_blocking(fd) }
{% else %}
Socket.get_blocking(fd).should be_false
{% end %}
end

describe "#finalize" do
it "does not flush" do
port = unused_local_tcp_port
Expand Down
6 changes: 5 additions & 1 deletion src/crystal/system/unix/file_descriptor.cr
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ module Crystal::System::FileDescriptor
FileDescriptor.set_blocking(fd, value)
end

protected def self.set_blocking(fd, value)
protected def self.get_blocking(fd : Handle)
fcntl(fd, LibC::F_GETFL) & LibC::O_NONBLOCK == 0
end

protected def self.set_blocking(fd : Handle, value : Bool)
current_flags = fcntl(fd, LibC::F_GETFL)
new_flags = current_flags
if value
Expand Down
2 changes: 1 addition & 1 deletion src/crystal/system/unix/process.cr
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ struct Crystal::System::Process
ret = LibC.dup2(src_io.fd, dst_io.fd)
raise IO::Error.from_errno("dup2") if ret == -1

dst_io.blocking = true
FileDescriptor.set_blocking(dst_io.fd, true)
dst_io.close_on_exec = false
end
end
Expand Down
14 changes: 11 additions & 3 deletions src/crystal/system/unix/socket.cr
Original file line number Diff line number Diff line change
Expand Up @@ -156,17 +156,25 @@ module Crystal::System::Socket
end

private def system_blocking?
fcntl(LibC::F_GETFL) & LibC::O_NONBLOCK == 0
Socket.get_blocking(fd)
end

private def system_blocking=(value)
flags = fcntl(LibC::F_GETFL)
Socket.set_blocking(fd, value)
end

def self.get_blocking(fd : Handle)
fcntl(fd, LibC::F_GETFL) & LibC::O_NONBLOCK == 0
end

def self.set_blocking(fd : Handle, value : Bool)
flags = fcntl(fd, LibC::F_GETFL)
if value
flags &= ~LibC::O_NONBLOCK
else
flags |= LibC::O_NONBLOCK
end
fcntl(LibC::F_SETFL, flags)
fcntl(fd, LibC::F_SETFL, flags)
end

private def system_close_on_exec?
Expand Down
8 changes: 8 additions & 0 deletions src/crystal/system/wasi/file_descriptor.cr
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ module Crystal::System::FileDescriptor
r
end

def self.get_blocking(fd : Handle)
raise NotImplementedError.new("Crystal::System::FileDescriptor.get_blocking")
end

def self.set_blocking(fd : Handle, value : Bool)
raise NotImplementedError.new("Crystal::System::FileDescriptor.set_blocking")
end

protected def system_blocking_init(blocking : Bool?)
end

Expand Down
14 changes: 11 additions & 3 deletions src/crystal/system/wasi/socket.cr
Original file line number Diff line number Diff line change
Expand Up @@ -102,17 +102,25 @@ module Crystal::System::Socket
end

private def system_blocking?
fcntl(LibC::F_GETFL) & LibC::O_NONBLOCK == 0
Socket.get_blocking(fd)
end

private def system_blocking=(value)
flags = fcntl(LibC::F_GETFL)
Socket.set_blocking(fd, value)
end

def self.get_blocking(fd : Handle)
fcntl(fd, LibC::F_GETFL) & LibC::O_NONBLOCK == 0
end

def self.set_blocking(fd : Handle, value : Bool)
flags = fcntl(fd, LibC::F_GETFL)
if value
flags &= ~LibC::O_NONBLOCK
else
flags |= LibC::O_NONBLOCK
end
fcntl(LibC::F_SETFL, flags)
fcntl(fd, LibC::F_SETFL, flags)
end

private def system_close_on_exec?
Expand Down
12 changes: 10 additions & 2 deletions src/crystal/system/win32/file_descriptor.cr
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,16 @@ module Crystal::System::FileDescriptor
@system_blocking
end

def self.get_blocking(fd : Handle)
raise NotImplementedError.new("Cannot query the blocking mode of an `IO::FileDescriptor`")
end

def self.set_blocking(fd : Handle, value : Bool)
raise NotImplementedError.new("Cannot change the blocking mode of an `IO::FileDescriptor` after creation")
end

private def system_blocking=(blocking)
unless blocking == self.blocking
unless blocking == system_blocking?
raise IO::Error.new("Cannot reconfigure `IO::FileDescriptor#blocking` after creation")
end
end
Expand Down Expand Up @@ -366,7 +374,7 @@ module Crystal::System::FileDescriptor
# `blocking` must be set to `true` because the underlying handles never
# support overlapped I/O; instead, `#emulated_blocking?` should return
# `false` for `STDIN` as it uses a separate thread
io = IO::FileDescriptor.new(handle.address, blocking: true)
io = IO::FileDescriptor.new(handle: handle.address, blocking: true)

# Set sync or flush_on_newline as described in STDOUT and STDERR docs.
# See https://crystal-lang.org/api/toplevel.html#STDERR
Expand Down
10 changes: 7 additions & 3 deletions src/crystal/system/win32/socket.cr
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ module Crystal::System::Socket
ret
end

@blocking = true
@blocking : Bool = true

# WSA does not provide a direct way to query the blocking mode of a file descriptor.
# The best option seems to be just keeping track in an instance variable.
Expand All @@ -345,10 +345,14 @@ module Crystal::System::Socket
Socket.set_blocking(fd, blocking)
end

def self.get_blocking(fd : Handle)
raise NotImplementedError.new("Cannot query the blocking mode of a `Socket`")
end

# Changes the blocking mode as per BSD sockets, has no effect on the
# overlapped flag.
def self.set_blocking(fd, blocking)
mode = blocking ? 1_u32 : 0_u32
def self.set_blocking(fd : Handle, value : Bool)
mode = value ? 1_u32 : 0_u32
ret = LibC.WSAIoctl(fd, LibC::FIONBIO, pointerof(mode), sizeof(UInt32), nil, 0, out _, nil, nil)
raise ::Socket::Error.from_wsa_error("WSAIoctl") unless ret.zero?
end
Expand Down
26 changes: 24 additions & 2 deletions src/io/file_descriptor.cr
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,15 @@ class IO::FileDescriptor < IO

# :nodoc:
#
# Internal constructor to wrap a system *handle*.
def initialize(*, handle : Handle, @close_on_finalize = true)
# Internal constructor to wrap a system *handle*. The *blocking* arg is purely
# informational.
def initialize(*, handle : Handle, @close_on_finalize = true, blocking = nil)
@volatile_fd = Atomic.new(handle)
@closed = true # This is necessary so we can reference `self` in `system_closed?` (in case of an exception)
@closed = system_closed?
{% if flag?(:win32) %}
@system_blocking = !!blocking
{% end %}
end

# :nodoc:
Expand All @@ -79,6 +83,7 @@ class IO::FileDescriptor < IO
# This might be different from the internal file descriptor. For example, when
# `STDIN` is a terminal on Windows, this returns `false` since the underlying
# blocking reads are done on a completely separate thread.
@[Deprecated("Use Socket.get_blocking instead.")]
def blocking
emulated = emulated_blocking?
return emulated unless emulated.nil?
Expand All @@ -92,10 +97,27 @@ class IO::FileDescriptor < IO
# the event loop runtime requirements. Changing the blocking mode can cause
# the event loop to misbehave, for example block the entire program when a
# fiber tries to read from this file descriptor.
@[Deprecated("Use IO::FileDescriptor.set_blocking instead.")]
def blocking=(value)
self.system_blocking = value
end

# Returns whether the blocking mode of *fd* is blocking (true) or non blocking
# (false).
#
# NOTE: Only implemented on UNIX targets. Raises on Windows.
def self.get_blocking(fd : Handle) : Bool
Crystal::System::Socket.get_blocking(fd)
end

# Changes the blocking mode of *fd* to be blocking (true) or non blocking
# (false).
#
# NOTE: Only implemented on UNIX targets. Raises on Windows.
def self.set_blocking(fd : Handle, value : Bool)
Crystal::System::FileDescriptor.set_blocking(fd, value)
end

def close_on_exec? : Bool
system_close_on_exec?
end
Expand Down
2 changes: 1 addition & 1 deletion src/process.cr
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ class Process
# regular files will report an error and those require a separate pipe
# (https://github.com/crystal-lang/crystal/pull/13362#issuecomment-1519082712)
{% if flag?(:win32) %}
unless stdio.blocking || stdio.info.type.pipe?
unless stdio.system_blocking? || stdio.info.type.pipe?
return io_to_fd(stdio, for: dst_io)
end
{% end %}
Expand Down
22 changes: 19 additions & 3 deletions src/socket.cr
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ class Socket < IO
def initialize(fd, @family : Family, @type : Type, @protocol : Protocol = Protocol::IP, blocking = nil)
initialize(handle: fd, family: family, type: type, protocol: protocol)
blocking = Crystal::EventLoop.default_socket_blocking? if blocking.nil?
self.blocking = blocking unless blocking
Crystal::System::Socket.set_blocking(fd, blocking) unless blocking
self.sync = true
end

Expand Down Expand Up @@ -228,10 +228,10 @@ class Socket < IO
def accept? : Socket?
if rs = Crystal::EventLoop.current.accept(self)
sock = Socket.new(handle: rs[0], family: family, type: type, protocol: protocol, blocking: rs[1])
unless (blocking = self.blocking) == rs[1]
unless (blocking = system_blocking?) == rs[1]
# FIXME: unlike the overloads in TCPServer and UNIXServer, this version
# carries the blocking mode from the server socket to the client socket
sock.blocking = blocking
Crystal::System::Socket.set_blocking(fd, blocking)
end
sock.sync = sync?
sock
Expand Down Expand Up @@ -421,6 +421,7 @@ class Socket < IO
end

# Returns whether the socket's mode is blocking (true) or non blocking (false).
@[Deprecated("Use Socket.get_blocking instead.")]
def blocking
system_blocking?
end
Expand All @@ -431,10 +432,25 @@ class Socket < IO
# loop runtime requirements. Changing the blocking mode can cause the event
# loop to misbehave, for example block the entire program when a fiber tries
# to read from this socket.
@[Deprecated("Use Socket.set_blocking instead.")]
def blocking=(value)
self.system_blocking = value
end

# Returns whether the blocking mode of *fd* is blocking (true) or non blocking
# (false).
#
# NOTE: Only implemented on UNIX targets. Raises on Windows.
def self.get_blocking(fd : Handle) : Bool
Crystal::System::Socket.get_blocking(fd)
end

# Changes the blocking mode of *fd* to be blocking (true) or non blocking
# (false).
def self.set_blocking(fd : Handle, value : Bool)
Crystal::System::Socket.set_blocking(fd, value)
end

def close_on_exec?
system_close_on_exec?
end
Expand Down