Skip to content

Commit fef9b4b

Browse files
authored
add lock option to open to disable locking (#35426)
1 parent 2848bfb commit fef9b4b

File tree

2 files changed

+62
-31
lines changed

2 files changed

+62
-31
lines changed

NEWS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@ New library features
124124
* `accumulate`, `cumsum`, and `cumprod` now support `Tuple` ([#34654]) and arbitrary iterators ([#34656]).
125125
* In `splice!` with no replacement, values to be removed can now be specified with an
126126
arbitrary iterable (instead of a `UnitRange`) ([#34524]).
127+
* `open` for files now accepts a keyword argument `lock` controlling whether file operations
128+
will acquire locks for safe multi-threaded access. Setting it to `false` provides better
129+
performance when only one thread will access the file.
127130

128131
Standard library changes
129132
------------------------

base/iostream.jl

Lines changed: 59 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ mutable struct IOStream <: IO
1616
name::AbstractString
1717
mark::Int64
1818
lock::ReentrantLock
19+
_dolock::Bool
1920

20-
IOStream(name::AbstractString, buf::Array{UInt8,1}) = new(pointer(buf), buf, name, -1, ReentrantLock())
21+
IOStream(name::AbstractString, buf::Array{UInt8,1}) = new(pointer(buf), buf, name, -1, ReentrantLock(), true)
2122
end
2223

2324
function IOStream(name::AbstractString, finalize::Bool)
@@ -33,6 +34,18 @@ IOStream(name::AbstractString) = IOStream(name, true)
3334
unsafe_convert(T::Type{Ptr{Cvoid}}, s::IOStream) = convert(T, pointer(s.ios))
3435
show(io::IO, s::IOStream) = print(io, "IOStream(", s.name, ")")
3536

37+
macro _lock_ios(s, expr)
38+
s = esc(s)
39+
quote
40+
l = ($s)._dolock
41+
temp = ($s).lock
42+
l && lock(temp)
43+
val = $(esc(expr))
44+
l && unlock(temp)
45+
val
46+
end
47+
end
48+
3649
"""
3750
fd(stream)
3851
@@ -46,13 +59,13 @@ stat(s::IOStream) = stat(fd(s))
4659
isopen(s::IOStream) = ccall(:ios_isopen, Cint, (Ptr{Cvoid},), s.ios) != 0
4760

4861
function close(s::IOStream)
49-
bad = @lock_nofail s.lock ccall(:ios_close, Cint, (Ptr{Cvoid},), s.ios) != 0
62+
bad = @_lock_ios s ccall(:ios_close, Cint, (Ptr{Cvoid},), s.ios) != 0
5063
systemerror("close", bad)
5164
end
5265

5366
function flush(s::IOStream)
5467
sigatomic_begin()
55-
bad = @lock_nofail s.lock ccall(:ios_flush, Cint, (Ptr{Cvoid},), s.ios) != 0
68+
bad = @_lock_ios s ccall(:ios_flush, Cint, (Ptr{Cvoid},), s.ios) != 0
5669
sigatomic_end()
5770
systemerror("flush", bad)
5871
end
@@ -91,7 +104,7 @@ julia> String(take!(io))
91104
```
92105
"""
93106
function truncate(s::IOStream, n::Integer)
94-
err = @lock_nofail s.lock ccall(:ios_trunc, Cint, (Ptr{Cvoid}, Csize_t), s.ios, n) != 0
107+
err = @_lock_ios s ccall(:ios_trunc, Cint, (Ptr{Cvoid}, Csize_t), s.ios, n) != 0
95108
systemerror("truncate", err)
96109
return s
97110
end
@@ -112,7 +125,7 @@ julia> read(io, Char)
112125
```
113126
"""
114127
function seek(s::IOStream, n::Integer)
115-
ret = @lock_nofail s.lock ccall(:ios_seek, Int64, (Ptr{Cvoid}, Int64), s.ios, n)
128+
ret = @_lock_ios s ccall(:ios_seek, Int64, (Ptr{Cvoid}, Int64), s.ios, n)
116129
systemerror("seek", ret == -1)
117130
ret < -1 && error("seek failed")
118131
return s
@@ -146,7 +159,7 @@ seekstart(s::IO) = seek(s,0)
146159
Seek a stream to its end.
147160
"""
148161
function seekend(s::IOStream)
149-
err = @lock_nofail s.lock ccall(:ios_seek_end, Int64, (Ptr{Cvoid},), s.ios) != 0
162+
err = @_lock_ios s ccall(:ios_seek_end, Int64, (Ptr{Cvoid},), s.ios) != 0
150163
systemerror("seekend", err)
151164
return s
152165
end
@@ -169,7 +182,7 @@ julia> read(io, Char)
169182
```
170183
"""
171184
function skip(s::IOStream, delta::Integer)
172-
ret = @lock_nofail s.lock ccall(:ios_skip, Int64, (Ptr{Cvoid}, Int64), s.ios, delta)
185+
ret = @_lock_ios s ccall(:ios_skip, Int64, (Ptr{Cvoid}, Int64), s.ios, delta)
173186
systemerror("skip", ret == -1)
174187
ret < -1 && error("skip failed")
175188
return s
@@ -201,13 +214,13 @@ julia> position(io)
201214
```
202215
"""
203216
function position(s::IOStream)
204-
pos = @lock_nofail s.lock ccall(:ios_pos, Int64, (Ptr{Cvoid},), s.ios)
217+
pos = @_lock_ios s ccall(:ios_pos, Int64, (Ptr{Cvoid},), s.ios)
205218
systemerror("position", pos == -1)
206219
return pos
207220
end
208221

209222
_eof_nolock(s::IOStream) = ccall(:ios_eof_blocking, Cint, (Ptr{Cvoid},), s.ios) != 0
210-
eof(s::IOStream) = @lock_nofail s.lock _eof_nolock(s)
223+
eof(s::IOStream) = @_lock_ios s _eof_nolock(s)
211224

212225
## constructing and opening streams ##
213226

@@ -229,7 +242,7 @@ end
229242
fdio(fd::Integer, own::Bool=false) = fdio(string("<fd ",fd,">"), fd, own)
230243

231244
"""
232-
open(filename::AbstractString; keywords...) -> IOStream
245+
open(filename::AbstractString; lock = true, keywords...) -> IOStream
233246
234247
Open a file in a mode specified by five boolean keyword arguments:
235248
@@ -243,8 +256,14 @@ Open a file in a mode specified by five boolean keyword arguments:
243256
244257
The default when no keywords are passed is to open files for reading only.
245258
Returns a stream for accessing the opened file.
259+
260+
The `lock` keyword argument controls whether operations will be locked for
261+
safe multi-threaded access.
262+
263+
!!! compat "Julia 1.5"
264+
The `lock` argument is available as of Julia 1.5.
246265
"""
247-
function open(fname::AbstractString;
266+
function open(fname::AbstractString; lock = true,
248267
read :: Union{Bool,Nothing} = nothing,
249268
write :: Union{Bool,Nothing} = nothing,
250269
create :: Union{Bool,Nothing} = nothing,
@@ -259,6 +278,9 @@ function open(fname::AbstractString;
259278
append = append,
260279
)
261280
s = IOStream(string("<file ",fname,">"))
281+
if !lock
282+
s._dolock = false
283+
end
262284
systemerror("opening file $(repr(fname))",
263285
ccall(:ios_file, Ptr{Cvoid},
264286
(Ptr{UInt8}, Cstring, Cint, Cint, Cint, Cint),
@@ -270,7 +292,7 @@ function open(fname::AbstractString;
270292
end
271293

272294
"""
273-
open(filename::AbstractString, [mode::AbstractString]) -> IOStream
295+
open(filename::AbstractString, [mode::AbstractString]; lock = true) -> IOStream
274296
275297
Alternate syntax for open, where a string-based mode specifier is used instead of the five
276298
booleans. The values of `mode` correspond to those from `fopen(3)` or Perl `open`, and are
@@ -285,6 +307,9 @@ equivalent to setting the following boolean groups:
285307
| `w+` | read, write, create, truncate | `truncate = true, read = true` |
286308
| `a+` | read, write, create, append | `append = true, read = true` |
287309
310+
The `lock` keyword argument controls whether operations will be locked for
311+
safe multi-threaded access.
312+
288313
# Examples
289314
```jldoctest
290315
julia> io = open("myfile.txt", "w");
@@ -313,31 +338,34 @@ julia> close(io)
313338
314339
julia> rm("myfile.txt")
315340
```
341+
342+
!!! compat "Julia 1.5"
343+
The `lock` argument is available as of Julia 1.5.
316344
"""
317-
function open(fname::AbstractString, mode::AbstractString)
318-
mode == "r" ? open(fname, read = true) :
319-
mode == "r+" ? open(fname, read = true, write = true) :
320-
mode == "w" ? open(fname, truncate = true) :
321-
mode == "w+" ? open(fname, truncate = true, read = true) :
322-
mode == "a" ? open(fname, append = true) :
323-
mode == "a+" ? open(fname, append = true, read = true) :
345+
function open(fname::AbstractString, mode::AbstractString; lock = true)
346+
mode == "r" ? open(fname, lock = lock, read = true) :
347+
mode == "r+" ? open(fname, lock = lock, read = true, write = true) :
348+
mode == "w" ? open(fname, lock = lock, truncate = true) :
349+
mode == "w+" ? open(fname, lock = lock, truncate = true, read = true) :
350+
mode == "a" ? open(fname, lock = lock, append = true) :
351+
mode == "a+" ? open(fname, lock = lock, append = true, read = true) :
324352
throw(ArgumentError("invalid open mode: $mode"))
325353
end
326354

327355
## low-level calls ##
328356

329357
function write(s::IOStream, b::UInt8)
330358
iswritable(s) || throw(ArgumentError("write failed, IOStream is not writeable"))
331-
Int(@lock_nofail s.lock ccall(:ios_putc, Cint, (Cint, Ptr{Cvoid}), b, s.ios))
359+
Int(@_lock_ios s ccall(:ios_putc, Cint, (Cint, Ptr{Cvoid}), b, s.ios))
332360
end
333361

334362
function unsafe_write(s::IOStream, p::Ptr{UInt8}, nb::UInt)
335363
iswritable(s) || throw(ArgumentError("write failed, IOStream is not writeable"))
336-
return Int(@lock_nofail s.lock ccall(:ios_write, Csize_t, (Ptr{Cvoid}, Ptr{Cvoid}, Csize_t), s.ios, p, nb))
364+
return Int(@_lock_ios s ccall(:ios_write, Csize_t, (Ptr{Cvoid}, Ptr{Cvoid}, Csize_t), s.ios, p, nb))
337365
end
338366

339367
# num bytes available without blocking
340-
bytesavailable(s::IOStream) = @lock_nofail s.lock ccall(:jl_nb_available, Int32, (Ptr{Cvoid},), s.ios)
368+
bytesavailable(s::IOStream) = @_lock_ios s ccall(:jl_nb_available, Int32, (Ptr{Cvoid},), s.ios)
341369

342370
function readavailable(s::IOStream)
343371
lock(s.lock)
@@ -353,7 +381,7 @@ function readavailable(s::IOStream)
353381
end
354382

355383
function read(s::IOStream, ::Type{UInt8})
356-
b = @lock_nofail s.lock ccall(:ios_getc, Cint, (Ptr{Cvoid},), s.ios)
384+
b = @_lock_ios s ccall(:ios_getc, Cint, (Ptr{Cvoid},), s.ios)
357385
if b == -1
358386
throw(EOFError())
359387
end
@@ -379,7 +407,7 @@ read(s::IOStream, ::Type{Float64}) = reinterpret(Float64, read(s, Int64))
379407
end
380408

381409
function unsafe_read(s::IOStream, p::Ptr{UInt8}, nb::UInt)
382-
nr = @lock_nofail s.lock ccall(:ios_readall, Csize_t, (Ptr{Cvoid}, Ptr{Cvoid}, Csize_t), s, p, nb)
410+
nr = @_lock_ios s ccall(:ios_readall, Csize_t, (Ptr{Cvoid}, Ptr{Cvoid}, Csize_t), s, p, nb)
383411
if nr != nb
384412
throw(EOFError())
385413
end
@@ -389,25 +417,25 @@ end
389417
## text I/O ##
390418

391419
take!(s::IOStream) =
392-
@lock_nofail s.lock ccall(:jl_take_buffer, Vector{UInt8}, (Ptr{Cvoid},), s.ios)
420+
@_lock_ios s ccall(:jl_take_buffer, Vector{UInt8}, (Ptr{Cvoid},), s.ios)
393421

394422
function readuntil(s::IOStream, delim::UInt8; keep::Bool=false)
395-
@lock_nofail s.lock ccall(:jl_readuntil, Array{UInt8,1}, (Ptr{Cvoid}, UInt8, UInt8, UInt8), s.ios, delim, 0, !keep)
423+
@_lock_ios s ccall(:jl_readuntil, Array{UInt8,1}, (Ptr{Cvoid}, UInt8, UInt8, UInt8), s.ios, delim, 0, !keep)
396424
end
397425

398426
# like readuntil, above, but returns a String without requiring a copy
399427
function readuntil_string(s::IOStream, delim::UInt8, keep::Bool)
400-
@lock_nofail s.lock ccall(:jl_readuntil, Ref{String}, (Ptr{Cvoid}, UInt8, UInt8, UInt8), s.ios, delim, 1, !keep)
428+
@_lock_ios s ccall(:jl_readuntil, Ref{String}, (Ptr{Cvoid}, UInt8, UInt8, UInt8), s.ios, delim, 1, !keep)
401429
end
402430

403431
function readline(s::IOStream; keep::Bool=false)
404-
@lock_nofail s.lock ccall(:jl_readuntil, Ref{String}, (Ptr{Cvoid}, UInt8, UInt8, UInt8), s.ios, '\n', 1, keep ? 0 : 2)
432+
@_lock_ios s ccall(:jl_readuntil, Ref{String}, (Ptr{Cvoid}, UInt8, UInt8, UInt8), s.ios, '\n', 1, keep ? 0 : 2)
405433
end
406434

407435
function readbytes_all!(s::IOStream, b::Array{UInt8}, nb)
408436
olb = lb = length(b)
409437
nr = 0
410-
@lock_nofail s.lock begin
438+
@_lock_ios s begin
411439
GC.@preserve b while nr < nb
412440
if lb < nr+1
413441
lb = max(65536, (nr+1) * 2)
@@ -430,7 +458,7 @@ function readbytes_some!(s::IOStream, b::Array{UInt8}, nb)
430458
resize!(b, nb)
431459
end
432460
local nr
433-
@lock_nofail s.lock begin
461+
@_lock_ios s begin
434462
nr = GC.@preserve b Int(ccall(:ios_read, Csize_t, (Ptr{Cvoid}, Ptr{Cvoid}, Csize_t),
435463
s.ios, pointer(b), nb))
436464
end
@@ -498,5 +526,5 @@ end
498526
## peek ##
499527

500528
function peek(s::IOStream)
501-
@lock_nofail s.lock ccall(:ios_peekc, Cint, (Ptr{Cvoid},), s)
529+
@_lock_ios s ccall(:ios_peekc, Cint, (Ptr{Cvoid},), s)
502530
end

0 commit comments

Comments
 (0)