Skip to content

Commit

Permalink
Open non-blocking regular files as overlapped on Windows (#14921)
Browse files Browse the repository at this point in the history
  • Loading branch information
HertzDevil authored Aug 23, 2024
1 parent d031bfa commit c462cd6
Show file tree
Hide file tree
Showing 7 changed files with 27 additions and 16 deletions.
6 changes: 3 additions & 3 deletions spec/std/file/tempfile_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ describe Crystal::System::File do
fd, path = Crystal::System::File.mktemp("A", "Z", dir: tempdir, random: TestRNG.new([7, 8, 9, 10, 11, 12, 13, 14]))
path.should eq Path[tempdir, "A789abcdeZ"].to_s
ensure
File.from_fd(path, fd).close if fd && path
IO::FileDescriptor.new(fd).close if fd
end
end

Expand All @@ -212,7 +212,7 @@ describe Crystal::System::File do
fd, path = Crystal::System::File.mktemp("A", "Z", dir: tempdir, random: TestRNG.new([7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22]))
path.should eq File.join(tempdir, "AfghijklmZ")
ensure
File.from_fd(path, fd).close if fd && path
IO::FileDescriptor.new(fd).close if fd
end
end

Expand All @@ -223,7 +223,7 @@ describe Crystal::System::File do
expect_raises(File::AlreadyExistsError, "Error creating temporary file") do
fd, path = Crystal::System::File.mktemp("A", "Z", dir: tempdir, random: TestRNG.new([7, 8, 9, 10, 11, 12, 13, 14]))
ensure
File.from_fd(path, fd).close if fd && path
IO::FileDescriptor.new(fd).close if fd
end
end
end
Expand Down
8 changes: 8 additions & 0 deletions spec/std/file_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ describe "File" do
end
end

it "opens regular file as non-blocking" do
with_tempfile("regular") do |path|
File.open(path, "w", blocking: false) do |file|
file.blocking.should be_false
end
end
end

{% if flag?(:unix) %}
if File.exists?("/dev/tty")
it "opens character device" do
Expand Down
2 changes: 1 addition & 1 deletion src/crystal/system/file.cr
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ module Crystal::System::File
io << suffix
end

handle, errno = open(path, mode, perm)
handle, errno = open(path, mode, perm, blocking: true)

if error_is_none?(errno)
return {handle, path}
Expand Down
6 changes: 3 additions & 3 deletions src/crystal/system/unix/file.cr
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ require "file/error"

# :nodoc:
module Crystal::System::File
def self.open(filename : String, mode : String, perm : Int32 | ::File::Permissions)
def self.open(filename : String, mode : String, perm : Int32 | ::File::Permissions, blocking)
perm = ::File::Permissions.new(perm) if perm.is_a? Int32

fd, errno = open(filename, open_flag(mode), perm)
fd, errno = open(filename, open_flag(mode), perm, blocking)

unless errno.none?
raise ::File::Error.from_os_error("Error opening file with mode '#{mode}'", errno, file: filename)
Expand All @@ -15,7 +15,7 @@ module Crystal::System::File
fd
end

def self.open(filename : String, flags : Int32, perm : ::File::Permissions) : {LibC::Int, Errno}
def self.open(filename : String, flags : Int32, perm : ::File::Permissions, blocking _blocking) : {LibC::Int, Errno}
filename.check_no_null_byte
flags |= LibC::O_CLOEXEC

Expand Down
16 changes: 10 additions & 6 deletions src/crystal/system/win32/file.cr
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ module Crystal::System::File
# write at the end of the file.
@system_append = false

def self.open(filename : String, mode : String, perm : Int32 | ::File::Permissions) : FileDescriptor::Handle
def self.open(filename : String, mode : String, perm : Int32 | ::File::Permissions, blocking : Bool?) : FileDescriptor::Handle
perm = ::File::Permissions.new(perm) if perm.is_a? Int32
# Only the owner writable bit is used, since windows only supports
# the read only attribute.
Expand All @@ -24,16 +24,16 @@ module Crystal::System::File
perm = LibC::S_IREAD
end

handle, error = open(filename, open_flag(mode), ::File::Permissions.new(perm))
handle, error = open(filename, open_flag(mode), ::File::Permissions.new(perm), blocking != false)
unless error.error_success?
raise ::File::Error.from_os_error("Error opening file with mode '#{mode}'", error, file: filename)
end

handle
end

def self.open(filename : String, flags : Int32, perm : ::File::Permissions) : {FileDescriptor::Handle, WinError}
access, disposition, attributes = self.posix_to_open_opts flags, perm
def self.open(filename : String, flags : Int32, perm : ::File::Permissions, blocking : Bool) : {FileDescriptor::Handle, WinError}
access, disposition, attributes = self.posix_to_open_opts flags, perm, blocking

handle = LibC.CreateFileW(
System.to_wstr(filename),
Expand All @@ -48,7 +48,7 @@ module Crystal::System::File
{handle.address, handle == LibC::INVALID_HANDLE_VALUE ? WinError.value : WinError::ERROR_SUCCESS}
end

private def self.posix_to_open_opts(flags : Int32, perm : ::File::Permissions)
private def self.posix_to_open_opts(flags : Int32, perm : ::File::Permissions, blocking : Bool)
access = if flags.bits_set? LibC::O_WRONLY
LibC::FILE_GENERIC_WRITE
elsif flags.bits_set? LibC::O_RDWR
Expand Down Expand Up @@ -77,7 +77,7 @@ module Crystal::System::File
disposition = LibC::OPEN_EXISTING
end

attributes = LibC::FILE_ATTRIBUTE_NORMAL
attributes = 0
unless perm.owner_write?
attributes |= LibC::FILE_ATTRIBUTE_READONLY
end
Expand All @@ -97,6 +97,10 @@ module Crystal::System::File
attributes |= LibC::FILE_FLAG_RANDOM_ACCESS
end

unless blocking
attributes |= LibC::FILE_FLAG_OVERLAPPED
end

{access, disposition, attributes}
end

Expand Down
3 changes: 1 addition & 2 deletions src/crystal/system/win32/file_descriptor.cr
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ module Crystal::System::FileDescriptor

private def system_blocking_init(value)
@system_blocking = value
Crystal::EventLoop.current.create_completion_port(windows_handle) unless value
end

private def system_close_on_exec?
Expand Down Expand Up @@ -264,13 +265,11 @@ module Crystal::System::FileDescriptor
w_pipe_flags |= LibC::FILE_FLAG_OVERLAPPED unless write_blocking
w_pipe = LibC.CreateNamedPipeA(pipe_name, w_pipe_flags, pipe_mode, 1, PIPE_BUFFER_SIZE, PIPE_BUFFER_SIZE, 0, nil)
raise IO::Error.from_winerror("CreateNamedPipeA") if w_pipe == LibC::INVALID_HANDLE_VALUE
Crystal::EventLoop.current.create_completion_port(w_pipe) unless write_blocking

r_pipe_flags = LibC::FILE_FLAG_NO_BUFFERING
r_pipe_flags |= LibC::FILE_FLAG_OVERLAPPED unless read_blocking
r_pipe = LibC.CreateFileW(System.to_wstr(pipe_name), LibC::GENERIC_READ | LibC::FILE_WRITE_ATTRIBUTES, 0, nil, LibC::OPEN_EXISTING, r_pipe_flags, nil)
raise IO::Error.from_winerror("CreateFileW") if r_pipe == LibC::INVALID_HANDLE_VALUE
Crystal::EventLoop.current.create_completion_port(r_pipe) unless read_blocking

r = IO::FileDescriptor.new(r_pipe.address, read_blocking)
w = IO::FileDescriptor.new(w_pipe.address, write_blocking)
Expand Down
2 changes: 1 addition & 1 deletion src/file.cr
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ class File < IO::FileDescriptor
# additional syscall.
def self.new(filename : Path | String, mode = "r", perm = DEFAULT_CREATE_PERMISSIONS, encoding = nil, invalid = nil, blocking = true)
filename = filename.to_s
fd = Crystal::System::File.open(filename, mode, perm: perm)
fd = Crystal::System::File.open(filename, mode, perm: perm, blocking: blocking)
new(filename, fd, blocking: blocking, encoding: encoding, invalid: invalid).tap { |f| f.system_set_mode(mode) }
end

Expand Down

0 comments on commit c462cd6

Please sign in to comment.