Skip to content

Commit 3d1ffcc

Browse files
authored
Crystal::EventLoop::FileDescriptor#open now sets the non/blocking flag (#15754)
Moves the responsibility to set `FILE_FLAG_OVERLAPPED` or `O_NONBLOCK` to each event loop, so they can eventually own the blocking flag. There is no change in behavior, yet: the event loops still set the flag depending on the `blocking` argument. Refactors the `IO::FileDescriptor` and `File` internal constructors so we can instantiate the object without changing the blocking state (already set).
1 parent 445ef91 commit 3d1ffcc

File tree

16 files changed

+66
-47
lines changed

16 files changed

+66
-47
lines changed

spec/std/file/tempfile_spec.cr

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ describe Crystal::System::File do
197197
it "creates random file name" do
198198
with_tempfile "random-path" do |tempdir|
199199
Dir.mkdir tempdir
200-
fd, path = Crystal::System::File.mktemp("A", "Z", dir: tempdir, random: TestRNG.new([7, 8, 9, 10, 11, 12, 13, 14]))
200+
fd, path, _ = Crystal::System::File.mktemp("A", "Z", dir: tempdir, random: TestRNG.new([7, 8, 9, 10, 11, 12, 13, 14]))
201201
path.should eq Path[tempdir, "A789abcdeZ"].to_s
202202
ensure
203203
IO::FileDescriptor.new(fd).close if fd
@@ -209,7 +209,7 @@ describe Crystal::System::File do
209209
Dir.mkdir tempdir
210210
existing_path = Path[tempdir, "A789abcdeZ"]
211211
File.touch existing_path
212-
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]))
212+
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]))
213213
path.should eq File.join(tempdir, "AfghijklmZ")
214214
ensure
215215
IO::FileDescriptor.new(fd).close if fd
@@ -221,7 +221,7 @@ describe Crystal::System::File do
221221
Dir.mkdir tempdir
222222
File.touch Path[tempdir, "A789abcdeZ"]
223223
expect_raises(File::AlreadyExistsError, "Error creating temporary file") do
224-
fd, path = Crystal::System::File.mktemp("A", "Z", dir: tempdir, random: TestRNG.new([7, 8, 9, 10, 11, 12, 13, 14]))
224+
fd, path, _ = Crystal::System::File.mktemp("A", "Z", dir: tempdir, random: TestRNG.new([7, 8, 9, 10, 11, 12, 13, 14]))
225225
ensure
226226
IO::FileDescriptor.new(fd).close if fd
227227
end

src/crystal/event_loop/file_descriptor.cr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ abstract class Crystal::EventLoop
77
# `nil`.
88
#
99
# Returns the system file descriptor or handle, or a system error.
10-
abstract def open(path : String, flags : Int32, permissions : File::Permissions, blocking : Bool?) : System::FileDescriptor::Handle | Errno | WinError
10+
abstract def open(path : String, flags : Int32, permissions : File::Permissions, blocking : Bool?) : {System::FileDescriptor::Handle, Bool} | Errno | WinError
1111

1212
# Reads at least one byte from the file descriptor into *slice*.
1313
#

src/crystal/event_loop/iocp.cr

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ class Crystal::EventLoop::IOCP < Crystal::EventLoop
223223
FiberEvent.new(:select_timeout, fiber)
224224
end
225225

226-
def open(path : String, flags : Int32, permissions : File::Permissions, blocking : Bool?) : System::FileDescriptor::Handle | WinError
226+
def open(path : String, flags : Int32, permissions : File::Permissions, blocking : Bool?) : {System::FileDescriptor::Handle, Bool} | WinError
227227
access, disposition, attributes = System::File.posix_to_open_opts(flags, permissions, blocking)
228228

229229
handle = LibC.CreateFileW(
@@ -239,7 +239,8 @@ class Crystal::EventLoop::IOCP < Crystal::EventLoop
239239
if handle == LibC::INVALID_HANDLE_VALUE
240240
WinError.value
241241
else
242-
handle.address
242+
create_completion_port(handle) unless blocking
243+
{handle.address, !!blocking}
243244
end
244245
end
245246

src/crystal/event_loop/libevent.cr

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,16 +108,19 @@ class Crystal::EventLoop::LibEvent < Crystal::EventLoop
108108
end
109109
end
110110

111-
def open(path : String, flags : Int32, permissions : File::Permissions, blocking : Bool?) : System::FileDescriptor::Handle | Errno
111+
def open(path : String, flags : Int32, permissions : File::Permissions, blocking : Bool?) : {System::FileDescriptor::Handle, Bool} | Errno
112112
path.check_no_null_byte
113113

114114
fd = LibC.open(path, flags | LibC::O_CLOEXEC, permissions)
115+
return Errno.value if fd == -1
115116

116-
if fd == -1
117-
Errno.value
118-
else
119-
fd
117+
blocking = !System::File.special_type?(fd) if blocking.nil?
118+
unless blocking
119+
status_flags = System::FileDescriptor.fcntl(fd, LibC::F_GETFL)
120+
System::FileDescriptor.fcntl(fd, LibC::F_SETFL, status_flags | LibC::O_NONBLOCK)
120121
end
122+
123+
{fd, blocking}
121124
end
122125

123126
def read(file_descriptor : Crystal::System::FileDescriptor, slice : Bytes) : Int32

src/crystal/event_loop/polling.cr

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -154,16 +154,19 @@ abstract class Crystal::EventLoop::Polling < Crystal::EventLoop
154154

155155
# file descriptor interface, see Crystal::EventLoop::FileDescriptor
156156

157-
def open(path : String, flags : Int32, permissions : File::Permissions, blocking : Bool?) : System::FileDescriptor::Handle | Errno
157+
def open(path : String, flags : Int32, permissions : File::Permissions, blocking : Bool?) : {System::FileDescriptor::Handle, Bool} | Errno
158158
path.check_no_null_byte
159159

160160
fd = LibC.open(path, flags | LibC::O_CLOEXEC, permissions)
161+
return Errno.value if fd == -1
161162

162-
if fd == -1
163-
Errno.value
164-
else
165-
fd
163+
blocking = !System::File.special_type?(fd) if blocking.nil?
164+
unless blocking
165+
status_flags = System::FileDescriptor.fcntl(fd, LibC::F_GETFL)
166+
System::FileDescriptor.fcntl(fd, LibC::F_SETFL, status_flags | LibC::O_NONBLOCK)
166167
end
168+
169+
{fd, blocking}
167170
end
168171

169172
def read(file_descriptor : System::FileDescriptor, slice : Bytes) : Int32

src/crystal/event_loop/wasi.cr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class Crystal::EventLoop::Wasi < Crystal::EventLoop
2828
raise NotImplementedError.new("Crystal::Wasi::EventLoop.create_fd_read_event")
2929
end
3030

31-
def open(filename : String, flags : Int32, permissions : File::Permissions, blocking : Bool?) : System::FileDescriptor::Handle | Errno | WinError
31+
def open(filename : String, flags : Int32, permissions : File::Permissions, blocking : Bool?) : {System::FileDescriptor::Handle, Bool} | Errno | WinError
3232
raise NotImplementedError.new("Crystal::Wasi::EventLoop#open")
3333
end
3434

src/crystal/system/file.cr

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ module Crystal::System::File
5151

5252
LOWER_ALPHANUM = "0123456789abcdefghijklmnopqrstuvwxyz".to_slice
5353

54-
def self.mktemp(prefix : String?, suffix : String?, dir : String, random : ::Random = ::Random::DEFAULT) : {FileDescriptor::Handle, String}
54+
def self.mktemp(prefix : String?, suffix : String?, dir : String, random : ::Random = ::Random::DEFAULT) : {FileDescriptor::Handle, String, Bool}
5555
flags = LibC::O_RDWR | LibC::O_CREAT | LibC::O_EXCL
5656
perm = ::File::Permissions.new(0o600)
5757

@@ -68,8 +68,9 @@ module Crystal::System::File
6868
end
6969

7070
case result = EventLoop.current.open(path, flags, perm, blocking: true)
71-
when FileDescriptor::Handle
72-
return {result, path}
71+
when Tuple(FileDescriptor::Handle, Bool)
72+
fd, blocking = result
73+
return {fd, path, blocking}
7374
when Errno::EEXIST, WinError::ERROR_FILE_EXISTS
7475
# retry
7576
else

src/crystal/system/unix/file.cr

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,25 @@ require "file/error"
33

44
# :nodoc:
55
module Crystal::System::File
6-
def self.open(filename : String, mode : String, perm : Int32 | ::File::Permissions, blocking : Bool?) : FileDescriptor::Handle
6+
def self.open(filename : String, mode : String, perm : Int32 | ::File::Permissions, blocking : Bool?) : {FileDescriptor::Handle, Bool}
77
perm = ::File::Permissions.new(perm) if perm.is_a? Int32
88

99
case result = EventLoop.current.open(filename, open_flag(mode), perm, blocking)
10-
in FileDescriptor::Handle
10+
in Tuple(FileDescriptor::Handle, Bool)
1111
result
1212
in Errno
1313
raise ::File::Error.from_os_error("Error opening file with mode '#{mode}'", result, file: filename)
1414
end
1515
end
1616

17-
protected def system_set_mode(mode : String)
17+
protected def system_init(mode : String, blocking : Bool) : Nil
18+
end
19+
20+
def self.special_type?(fd)
21+
stat = uninitialized LibC::Stat
22+
ret = fstat(fd, pointerof(stat))
23+
# not checking for S_IFSOCK because we can't open(2) a socket
24+
ret != -1 && (stat.st_mode & LibC::S_IFMT).in?(LibC::S_IFCHR, LibC::S_IFIFO)
1825
end
1926

2027
def self.info?(path : String, follow_symlinks : Bool) : ::File::Info?

src/crystal/system/unix/file_descriptor.cr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ module Crystal::System::FileDescriptor
3434
fcntl(LibC::F_SETFL, new_flags) unless new_flags == current_flags
3535
end
3636

37-
private def system_blocking_init(blocking : Bool?)
37+
protected def system_blocking_init(blocking : Bool?)
3838
if blocking.nil?
3939
blocking =
4040
case system_info.type

src/crystal/system/wasi/file.cr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ require "../unix/file"
22

33
# :nodoc:
44
module Crystal::System::File
5-
protected def system_set_mode(mode : String)
5+
protected def system_init(mode : String, blocking : Bool) : Nil
66
end
77

88
def self.chmod(path, mode)

0 commit comments

Comments
 (0)