Skip to content

Commit

Permalink
Support non-blocking File#read_at on Windows (#14958)
Browse files Browse the repository at this point in the history
  • Loading branch information
HertzDevil committed Sep 3, 2024
1 parent 6ee4eb9 commit 18c28f9
Show file tree
Hide file tree
Showing 5 changed files with 42 additions and 33 deletions.
20 changes: 11 additions & 9 deletions spec/std/file_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -1295,17 +1295,19 @@ describe "File" do

it "reads at offset" do
filename = datapath("test_file.txt")
File.open(filename) do |file|
file.read_at(6, 100) do |io|
io.gets_to_end.should eq("World\nHello World\nHello World\nHello World\nHello World\nHello World\nHello World\nHello World\nHello Worl")
end
{true, false}.each do |blocking|
File.open(filename, blocking: blocking) do |file|
file.read_at(6, 100) do |io|
io.gets_to_end.should eq("World\nHello World\nHello World\nHello World\nHello World\nHello World\nHello World\nHello World\nHello Worl")
end

file.read_at(0, 240) do |io|
io.gets_to_end.should eq(File.read(filename))
end
file.read_at(0, 240) do |io|
io.gets_to_end.should eq(File.read(filename))
end

file.read_at(6_i64, 5_i64) do |io|
io.gets_to_end.should eq("World")
file.read_at(6_i64, 5_i64) do |io|
io.gets_to_end.should eq("World")
end
end
end
end
Expand Down
6 changes: 3 additions & 3 deletions src/crystal/system/unix/file_descriptor.cr
Original file line number Diff line number Diff line change
Expand Up @@ -219,11 +219,11 @@ module Crystal::System::FileDescriptor
{r, w}
end

def self.pread(fd, buffer, offset)
bytes_read = LibC.pread(fd, buffer, buffer.size, offset).to_i64
def self.pread(file, buffer, offset)
bytes_read = LibC.pread(file.fd, buffer, buffer.size, offset).to_i64

if bytes_read == -1
raise IO::Error.from_errno "Error reading file"
raise IO::Error.from_errno("Error reading file", target: file)
end

bytes_read
Expand Down
33 changes: 18 additions & 15 deletions src/crystal/system/win32/file_descriptor.cr
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,6 @@ module Crystal::System::FileDescriptor
end

protected def windows_handle
FileDescriptor.windows_handle(fd)
end

def self.windows_handle(fd)
LibC::HANDLE.new(fd)
end

Expand Down Expand Up @@ -278,19 +274,26 @@ module Crystal::System::FileDescriptor
{r, w}
end

def self.pread(fd, buffer, offset)
handle = windows_handle(fd)
def self.pread(file, buffer, offset)
handle = file.windows_handle

overlapped = LibC::OVERLAPPED.new
overlapped.union.offset.offset = LibC::DWORD.new!(offset)
overlapped.union.offset.offsetHigh = LibC::DWORD.new!(offset >> 32)
if LibC.ReadFile(handle, buffer, buffer.size, out bytes_read, pointerof(overlapped)) == 0
error = WinError.value
return 0_i64 if error == WinError::ERROR_HANDLE_EOF
raise IO::Error.from_os_error "Error reading file", error, target: self
end
if file.system_blocking?
overlapped = LibC::OVERLAPPED.new
overlapped.union.offset.offset = LibC::DWORD.new!(offset)
overlapped.union.offset.offsetHigh = LibC::DWORD.new!(offset >> 32)
if LibC.ReadFile(handle, buffer, buffer.size, out bytes_read, pointerof(overlapped)) == 0
error = WinError.value
return 0_i64 if error == WinError::ERROR_HANDLE_EOF
raise IO::Error.from_os_error "Error reading file", error, target: file
end

bytes_read.to_i64
bytes_read.to_i64
else
IOCP.overlapped_operation(file, "ReadFile", file.read_timeout, offset: offset) do |overlapped|
ret = LibC.ReadFile(handle, buffer, buffer.size, out byte_count, overlapped)
{ret, byte_count}
end.to_i64
end
end

def self.from_stdio(fd)
Expand Down
14 changes: 9 additions & 5 deletions src/crystal/system/win32/iocp.cr
Original file line number Diff line number Diff line change
Expand Up @@ -168,15 +168,16 @@ module Crystal::IOCP
end
end

def self.overlapped_operation(file_descriptor, method, timeout, *, writing = false, &)
def self.overlapped_operation(file_descriptor, method, timeout, *, offset = nil, writing = false, &)
handle = file_descriptor.windows_handle
seekable = LibC.SetFilePointerEx(handle, 0, out original_offset, IO::Seek::Current) != 0

OverlappedOperation.run(handle) do |operation|
overlapped = operation.to_unsafe
if seekable
overlapped.value.union.offset.offset = LibC::DWORD.new!(original_offset)
overlapped.value.union.offset.offsetHigh = LibC::DWORD.new!(original_offset >> 32)
start_offset = offset || original_offset
overlapped.value.union.offset.offset = LibC::DWORD.new!(start_offset)
overlapped.value.union.offset.offsetHigh = LibC::DWORD.new!(start_offset >> 32)
end
result, value = yield operation

Expand Down Expand Up @@ -215,8 +216,11 @@ module Crystal::IOCP

# operation completed asynchronously; seek to the original file position
# plus the number of bytes read or written (other operations might have
# moved the file pointer so we don't use `IO::Seek::Current` here)
LibC.SetFilePointerEx(handle, original_offset + byte_count, nil, IO::Seek::Set) if seekable
# moved the file pointer so we don't use `IO::Seek::Current` here), unless
# we are calling `Crystal::System::FileDescriptor.pread`
if seekable && !offset
LibC.SetFilePointerEx(handle, original_offset + byte_count, nil, IO::Seek::Set)
end
byte_count
end
end
Expand Down
2 changes: 1 addition & 1 deletion src/file/preader.cr
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class File::PReader < IO
count = slice.size
count = Math.min(count, @bytesize - @pos)

bytes_read = Crystal::System::FileDescriptor.pread(@file.fd, slice[0, count], @offset + @pos)
bytes_read = Crystal::System::FileDescriptor.pread(@file, slice[0, count], @offset + @pos)

@pos += bytes_read

Expand Down

0 comments on commit 18c28f9

Please sign in to comment.