Skip to content

Commit 98d7872

Browse files
committed
Integrate Crystal::FdLock into File, IO::FileDescriptor and Socket
Only operations that can affect the file descriptor are counted, for example read or write, truncating a file or changing file permissions. Mere queries with no side effects go through normally because at worst they will fail (they would have anyway) or return invalid information.
1 parent 6f25837 commit 98d7872

File tree

4 files changed

+93
-41
lines changed

4 files changed

+93
-41
lines changed

src/crystal/system/unix/file.cr

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ module Crystal::System::File
109109
end
110110

111111
private def system_chown(uid : Int, gid : Int)
112-
ret = LibC.fchown(fd, uid, gid)
112+
ret = @fd_lock.reference { LibC.fchown(fd, uid, gid) }
113113
raise ::File::Error.from_errno("Error changing owner", file: path) if ret == -1
114114
end
115115

@@ -120,7 +120,7 @@ module Crystal::System::File
120120
end
121121

122122
private def system_chmod(mode)
123-
if LibC.fchmod(fd, mode) == -1
123+
if @fd_lock.reference { LibC.fchmod(fd, mode) } == -1
124124
raise ::File::Error.from_errno("Error changing permissions", file: path)
125125
end
126126
end
@@ -201,12 +201,12 @@ module Crystal::System::File
201201
timespecs = uninitialized LibC::Timespec[2]
202202
timespecs[0] = Crystal::System::Time.to_timespec(atime)
203203
timespecs[1] = Crystal::System::Time.to_timespec(mtime)
204-
LibC.futimens(fd, timespecs)
204+
@fd_lock.reference { LibC.futimens(fd, timespecs) }
205205
{% elsif LibC.has_method?("futimes") %}
206206
timevals = uninitialized LibC::Timeval[2]
207207
timevals[0] = Crystal::System::Time.to_timeval(atime)
208208
timevals[1] = Crystal::System::Time.to_timeval(mtime)
209-
LibC.futimes(fd, timevals)
209+
@fd_lock.reference { LibC.futimes(fd, timevals) }
210210
{% else %}
211211
{% raise "Missing futimens & futimes" %}
212212
{% end %}
@@ -218,7 +218,7 @@ module Crystal::System::File
218218

219219
private def system_truncate(size) : Nil
220220
flush
221-
code = LibC.ftruncate(fd, size)
221+
code = @fd_lock.reference { LibC.ftruncate(fd, size) }
222222
if code != 0
223223
raise ::File::Error.from_errno("Error truncating file", file: path)
224224
end

src/crystal/system/unix/file_descriptor.cr

Lines changed: 44 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ require "termios"
33
{% if flag?(:android) && LibC::ANDROID_API < 28 %}
44
require "c/sys/ioctl"
55
{% end %}
6+
require "crystal/fd_lock"
67

78
# :nodoc:
89
module Crystal::System::FileDescriptor
@@ -18,13 +19,15 @@ module Crystal::System::FileDescriptor
1819
STDOUT_HANDLE = 1
1920
STDERR_HANDLE = 2
2021

22+
@fd_lock = FdLock.new
23+
2124
private def system_blocking?
22-
flags = system_fcntl(LibC::F_GETFL)
25+
flags = FileDescriptor.fcntl(fd, LibC::F_GETFL)
2326
!flags.bits_set? LibC::O_NONBLOCK
2427
end
2528

2629
private def system_blocking=(value)
27-
FileDescriptor.set_blocking(fd, value)
30+
@fd_lock.reference { FileDescriptor.set_blocking(fd, value) }
2831
end
2932

3033
protected def self.get_blocking(fd : Handle)
@@ -56,7 +59,7 @@ module Crystal::System::FileDescriptor
5659
end
5760

5861
private def system_close_on_exec?
59-
flags = system_fcntl(LibC::F_GETFD)
62+
flags = FileDescriptor.fcntl(fd, LibC::F_GETFD)
6063
flags.bits_set? LibC::FD_CLOEXEC
6164
end
6265

@@ -76,7 +79,7 @@ module Crystal::System::FileDescriptor
7679
end
7780

7881
private def system_fcntl(cmd, arg = 0)
79-
FileDescriptor.fcntl(fd, cmd, arg)
82+
@fd_lock.reference { FileDescriptor.fcntl(fd, cmd, arg) }
8083
end
8184

8285
def self.system_info(fd)
@@ -91,11 +94,11 @@ module Crystal::System::FileDescriptor
9194
end
9295

9396
private def system_info
94-
FileDescriptor.system_info fd
97+
@fd_lock.reference { FileDescriptor.system_info(fd) }
9598
end
9699

97100
private def system_seek(offset, whence : IO::Seek) : Nil
98-
seek_value = LibC.lseek(fd, offset, whence)
101+
seek_value = @fd_lock.reference { LibC.lseek(fd, offset, whence) }
99102

100103
if seek_value == -1
101104
raise IO::Error.from_errno "Unable to seek", target: self
@@ -113,19 +116,23 @@ module Crystal::System::FileDescriptor
113116
end
114117

115118
private def system_reopen(other : IO::FileDescriptor)
116-
{% if LibC.has_method?(:dup3) %}
117-
flags = other.close_on_exec? ? LibC::O_CLOEXEC : 0
118-
if LibC.dup3(other.fd, fd, flags) == -1
119-
raise IO::Error.from_errno("Could not reopen file descriptor")
120-
end
121-
{% else %}
122-
Process.lock_read do
123-
if LibC.dup2(other.fd, fd) == -1
124-
raise IO::Error.from_errno("Could not reopen file descriptor")
125-
end
126-
self.close_on_exec = other.close_on_exec?
119+
other.@fd_lock.reference do
120+
@fd_lock.reference do
121+
{% if LibC.has_method?(:dup3) %}
122+
flags = other.close_on_exec? ? LibC::O_CLOEXEC : 0
123+
if LibC.dup3(other.fd, fd, flags) == -1
124+
raise IO::Error.from_errno("Could not reopen file descriptor")
125+
end
126+
{% else %}
127+
Process.lock_read do
128+
if LibC.dup2(other.fd, fd) == -1
129+
raise IO::Error.from_errno("Could not reopen file descriptor")
130+
end
131+
self.close_on_exec = other.close_on_exec?
132+
end
133+
{% end %}
127134
end
128-
{% end %}
135+
end
129136

130137
# Mark the handle open, since we had to have dup'd a live handle.
131138
@closed = false
@@ -134,8 +141,9 @@ module Crystal::System::FileDescriptor
134141
end
135142

136143
private def system_close
137-
event_loop.before_close(self)
138-
event_loop.close(self)
144+
if @fd_lock.try_close? { event_loop.before_close(self) }
145+
event_loop.close(self)
146+
end
139147
end
140148

141149
def file_descriptor_close(&) : Nil
@@ -196,7 +204,7 @@ module Crystal::System::FileDescriptor
196204
end
197205

198206
private def flock(op) : Bool
199-
if 0 == LibC.flock(fd, op)
207+
if 0 == @fd_lock.reference { LibC.flock(fd, op) }
200208
true
201209
else
202210
errno = Errno.value
@@ -209,7 +217,7 @@ module Crystal::System::FileDescriptor
209217
end
210218

211219
private def system_fsync(flush_metadata = true) : Nil
212-
ret =
220+
ret = @fd_lock.reference do
213221
if flush_metadata
214222
LibC.fsync(fd)
215223
else
@@ -219,6 +227,7 @@ module Crystal::System::FileDescriptor
219227
LibC.fdatasync(fd)
220228
{% end %}
221229
end
230+
end
222231

223232
if ret != 0
224233
raise IO::Error.from_errno("Error syncing file", target: self)
@@ -246,7 +255,9 @@ module Crystal::System::FileDescriptor
246255
end
247256

248257
def self.pread(file, buffer, offset)
249-
bytes_read = LibC.pread(file.fd, buffer, buffer.size, offset).to_i64
258+
bytes_read = file.@fd_lock.reference do
259+
LibC.pread(file.fd, buffer, buffer.size, offset).to_i64
260+
end
250261

251262
if bytes_read == -1
252263
raise IO::Error.from_errno("Error reading file", target: file)
@@ -351,7 +362,7 @@ module Crystal::System::FileDescriptor
351362
@[AlwaysInline]
352363
private def system_tcsetattr(optional_actions, termios_p)
353364
{% if LibC.has_method?(:tcsetattr) %}
354-
LibC.tcsetattr(fd, optional_actions, termios_p)
365+
@fd_lock.reference { LibC.tcsetattr(fd, optional_actions, termios_p) }
355366
{% else %}
356367
optional_actions = optional_actions.value if optional_actions.is_a?(Termios::LineControl)
357368
cmd = case optional_actions
@@ -366,7 +377,7 @@ module Crystal::System::FileDescriptor
366377
return LibC::Int.new(-1)
367378
end
368379

369-
LibC.ioctl(fd, cmd, termios_p)
380+
@fd_lock.reference { LibC.ioctl(fd, cmd, termios_p) }
370381
{% end %}
371382
end
372383

@@ -385,4 +396,12 @@ module Crystal::System::FileDescriptor
385396
{% end %}
386397
termios
387398
end
399+
400+
private def system_read(slice : Bytes) : Int32
401+
@fd_lock.read { event_loop.read(self, slice) }
402+
end
403+
404+
private def system_write(slice : Bytes) : Int32
405+
@fd_lock.write { event_loop.write(self, slice) }
406+
end
388407
end

src/crystal/system/unix/socket.cr

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
require "c/netdb"
22
require "c/netinet/tcp"
33
require "c/sys/socket"
4+
require "crystal/fd_lock"
45

56
module Crystal::System::Socket
67
{% if IO.has_constant?(:Evented) %}
@@ -9,6 +10,8 @@ module Crystal::System::Socket
910

1011
alias Handle = Int32
1112

13+
@fd_lock = FdLock.new
14+
1215
def self.socket(family, type, protocol, blocking) : Handle
1316
{% if LibC.has_constant?(:SOCK_CLOEXEC) %}
1417
flags = type.value | LibC::SOCK_CLOEXEC
@@ -36,29 +39,29 @@ module Crystal::System::Socket
3639
# Tries to bind the socket to a local address.
3740
# Yields an `Socket::BindError` if the binding failed.
3841
private def system_bind(addr, addrstr, &)
39-
unless LibC.bind(fd, addr, addr.size) == 0
42+
unless @fd_lock.reference { LibC.bind(fd, addr, addr.size) } == 0
4043
yield ::Socket::BindError.from_errno("Could not bind to '#{addrstr}'")
4144
end
4245
end
4346

4447
private def system_listen(backlog, &)
45-
unless LibC.listen(fd, backlog) == 0
48+
unless @fd_lock.reference { LibC.listen(fd, backlog) } == 0
4649
yield ::Socket::Error.from_errno("Listen failed")
4750
end
4851
end
4952

5053
private def system_accept : {Handle, Bool}?
51-
event_loop.accept(self)
54+
@fd_lock.read { event_loop.accept(self) }
5255
end
5356

5457
private def system_close_read
55-
if LibC.shutdown(fd, LibC::SHUT_RD) != 0
58+
if @fd_lock.reference { LibC.shutdown(fd, LibC::SHUT_RD) } != 0
5659
raise ::Socket::Error.from_errno("shutdown read")
5760
end
5861
end
5962

6063
private def system_close_write
61-
if LibC.shutdown(fd, LibC::SHUT_WR) != 0
64+
if @fd_lock.reference { LibC.shutdown(fd, LibC::SHUT_WR) } != 0
6265
raise ::Socket::Error.from_errno("shutdown write")
6366
end
6467
end
@@ -154,7 +157,9 @@ module Crystal::System::Socket
154157
private def system_setsockopt(optname, optval, level = LibC::SOL_SOCKET)
155158
optsize = LibC::SocklenT.new(sizeof(typeof(optval)))
156159

157-
ret = LibC.setsockopt(fd, level, optname, pointerof(optval), optsize)
160+
ret = @fd_lock.reference do
161+
LibC.setsockopt(fd, level, optname, pointerof(optval), optsize)
162+
end
158163
raise ::Socket::Error.from_errno("setsockopt #{optname}") if ret == -1
159164
ret
160165
end
@@ -164,7 +169,9 @@ module Crystal::System::Socket
164169
end
165170

166171
private def system_blocking=(value)
167-
FileDescriptor.set_blocking(fd, value)
172+
@fd_lock.reference do
173+
FileDescriptor.set_blocking(fd, value)
174+
end
168175
end
169176

170177
def self.get_blocking(fd : Handle)
@@ -176,7 +183,7 @@ module Crystal::System::Socket
176183
end
177184

178185
private def system_close_on_exec?
179-
flags = system_fcntl(LibC::F_GETFD)
186+
flags = FileDescriptor.fcntl(fd, LibC::F_GETFD)
180187
(flags & LibC::FD_CLOEXEC) == LibC::FD_CLOEXEC
181188
end
182189

@@ -190,7 +197,7 @@ module Crystal::System::Socket
190197
end
191198

192199
private def system_fcntl(cmd, arg = 0)
193-
FileDescriptor.fcntl(fd, cmd, arg)
200+
@fd_lock.reference { FileDescriptor.fcntl(fd, cmd, arg) }
194201
end
195202

196203
def self.socketpair(type : ::Socket::Type, protocol : ::Socket::Protocol, blocking : Bool) : {Handle, Handle}
@@ -224,8 +231,10 @@ module Crystal::System::Socket
224231
end
225232

226233
private def system_close
227-
event_loop.before_close(self)
228-
event_loop.close(self)
234+
if @fd_lock.try_close? { event_loop.before_close(self) }
235+
event_loop.close(self)
236+
@fd_lock.reset
237+
end
229238
end
230239

231240
def socket_close(&)
@@ -348,4 +357,24 @@ module Crystal::System::Socket
348357
val
349358
end
350359
{% end %}
360+
361+
private def system_send_to(bytes : Bytes, addr : ::Socket::Address)
362+
@fd_lock.write { event_loop.send_to(self, bytes, addr) }
363+
end
364+
365+
private def system_receive_from(bytes : Bytes) : Tuple(Int32, ::Socket::Address)
366+
@fd_lock.read { event_loop.receive_from(self, bytes) }
367+
end
368+
369+
private def system_connect(addr, timeout = nil)
370+
@fd_lock.write { event_loop.connect(self, addr, timeout) }
371+
end
372+
373+
private def system_read(slice : Bytes) : Int32
374+
@fd_lock.read { event_loop.read(self, slice) }
375+
end
376+
377+
private def system_write(slice : Bytes) : Int32
378+
@fd_lock.write { event_loop.write(self, slice) }
379+
end
351380
end

src/crystal/system/wasi/socket.cr

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ module Crystal::System::Socket
3737
end
3838
end
3939

40+
private def system_accept : {::Socket::Handle, Bool}?
41+
event_loop.accept(self)
42+
end
43+
4044
private def system_send_buffer_size : Int
4145
raise NotImplementedError.new "Crystal::System::Socket#system_send_buffer_size"
4246
end

0 commit comments

Comments
 (0)