• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JuliaLang / julia / #38182

15 Aug 2025 03:55AM UTC coverage: 77.87% (-0.4%) from 78.28%
#38182

push

local

web-flow
🤖 [master] Bump the SparseArrays stdlib from 30201ab to bb5ecc0 (#59263)

Stdlib: SparseArrays
URL: https://github.com/JuliaSparse/SparseArrays.jl.git
Stdlib branch: main
Julia branch: master
Old commit: 30201ab
New commit: bb5ecc0
Julia version: 1.13.0-DEV
SparseArrays version: 1.13.0
Bump invoked by: @ViralBShah
Powered by:
[BumpStdlibs.jl](https://github.com/JuliaLang/BumpStdlibs.jl)

Diff:
https://github.com/JuliaSparse/SparseArrays.jl/compare/30201abcb...bb5ecc091

```
$ git log --oneline 30201ab..bb5ecc0
bb5ecc0 fast quadratic form for dense matrix, sparse vectors (#640)
34ece87 Extend 3-arg `dot` to generic `HermOrSym` sparse matrices (#643)
095b685 Exclude unintended complex symmetric sparse matrices from 3-arg `dot` (#642)
8049287 Fix signature for 2-arg matrix-matrix `dot` (#641)
cff971d Make cond(::SparseMatrix, 1 / Inf) discoverable from 2-norm error (#629)
```

Co-authored-by: ViralBShah <744411+ViralBShah@users.noreply.github.com>

48274 of 61993 relevant lines covered (77.87%)

9571166.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

78.87
/base/stream.jl
1
# This file is a part of Julia. License is MIT: https://julialang.org/license
2

3
import .Libc: RawFD, dup
4
if Sys.iswindows()
5
    import .Libc: WindowsRawSocket
6
    const OS_HANDLE = WindowsRawSocket
7
    const INVALID_OS_HANDLE = WindowsRawSocket(Ptr{Cvoid}(-1))
8
else
9
    const OS_HANDLE = RawFD
10
    const INVALID_OS_HANDLE = RawFD(-1)
11
end
12

13

14
## types ##
15
abstract type IOServer end
16
"""
17
    LibuvServer
18

19
An abstract type for IOServers handled by libuv.
20

21
If `server isa LibuvServer`, it must obey the following interface:
22

23
- `server.handle` must be a `Ptr{Cvoid}`
24
- `server.status` must be an `Int`
25
- `server.cond` must be a `GenericCondition`
26
"""
27
abstract type LibuvServer <: IOServer end
28

29
function getproperty(server::LibuvServer, name::Symbol)
30
    if name === :handle
11,164✔
31
        return getfield(server, :handle)::Ptr{Cvoid}
9,715✔
32
    elseif name === :status
8,575✔
33
        return getfield(server, :status)::Int
22,371✔
34
    elseif name === :cond
3,902✔
35
        return getfield(server, :cond)::GenericCondition
3,902✔
36
    else
37
        return getfield(server, name)
×
38
    end
39
end
40

41
"""
42
    LibuvStream
43

44
An abstract type for IO streams handled by libuv.
45

46
If `stream isa LibuvStream`, it must obey the following interface:
47

48
- `stream.handle`, if present, must be a `Ptr{Cvoid}`
49
- `stream.status`, if present, must be an `Int`
50
- `stream.buffer`, if present, must be an `IOBuffer`
51
- `stream.sendbuf`, if present, must be a `Union{Nothing,IOBuffer}`
52
- `stream.cond`, if present, must be a `GenericCondition`
53
- `stream.lock`, if present, must be an `AbstractLock`
54
- `stream.throttle`, if present, must be an `Int`
55
"""
56
abstract type LibuvStream <: IO end
57

58
function getproperty(stream::LibuvStream, name::Symbol)
303✔
59
    if name === :handle
15,953,298✔
60
        return getfield(stream, :handle)::Ptr{Cvoid}
984,434✔
61
    elseif name === :status
15,100,670✔
62
        return getfield(stream, :status)::Int
3,867,389✔
63
    elseif name === :buffer
11,585,436✔
64
        return getfield(stream, :buffer)::IOBuffer
7,409,902✔
65
    elseif name === :sendbuf
5,027,917✔
66
        return getfield(stream, :sendbuf)::Union{Nothing,IOBuffer}
2,430,351✔
67
    elseif name === :cond
2,597,568✔
68
        return getfield(stream, :cond)::GenericCondition
1,500,436✔
69
    elseif name === :lock
1,108,064✔
70
        return getfield(stream, :lock)::AbstractLock
217,391✔
71
    elseif name === :throttle
890,777✔
72
        return getfield(stream, :throttle)::Int
549,711✔
73
    else
74
        return getfield(stream, name)
341,501✔
75
    end
76
end
77

78
# IO
79
# +- GenericIOBuffer{T<:AbstractVector{UInt8}} (not exported)
80
# +- AbstractPipe (not exported)
81
# .  +- Pipe
82
# .  +- Process (not exported)
83
# .  +- ProcessChain (not exported)
84
# +- DevNull (not exported)
85
# +- Filesystem.File
86
# +- LibuvStream (not exported)
87
# .  +- PipeEndpoint (not exported)
88
# .  +- TCPSocket
89
# .  +- TTY (not exported)
90
# .  +- UDPSocket
91
# .  +- BufferStream (FIXME: 2.0)
92
# +- IOBuffer = Base.GenericIOBuffer{Vector{UInt8}}
93
# +- IOStream
94

95
# IOServer
96
# +- LibuvServer
97
# .  +- PipeServer
98
# .  +- TCPServer
99

100
# Redirectable = Union{IO, FileRedirect, Libc.RawFD} (not exported)
101

102
bytesavailable(s::LibuvStream) = bytesavailable(s.buffer)
1,485,689✔
103

104
function eof(s::LibuvStream)
129,805✔
105
    bytesavailable(s) > 0 && return false
1,475,151✔
106
    wait_readnb(s, 1)
8,167✔
107
    # This function is race-y if used from multiple threads, but we guarantee
108
    # it to never return true until the stream is definitively exhausted
109
    # and that we won't return true if there's a readerror pending (it'll instead get thrown).
110
    # This requires some careful ordering here (TODO: atomic loads)
111
    bytesavailable(s) > 0 && return false
8,166✔
112
    open = isreadable(s) # must precede readerror check
1,788✔
113
    s.readerror === nothing || throw(s.readerror)
894✔
114
    return !open
894✔
115
end
116

117
# Limit our default maximum read and buffer size,
118
# to avoid DoS-ing ourself into an OOM situation
119
const DEFAULT_READ_BUFFER_SZ = 10485760 # 10 MB
120

121
# manually limit our write size, if the OS doesn't support full-size writes
122
if Sys.iswindows()
123
    const MAX_OS_WRITE = UInt(0x1FF0_0000) # 511 MB (determined semi-empirically, limited to 31 MB on XP)
124
else
125
    const MAX_OS_WRITE = UInt(0x7FFF_0000) # almost 2 GB (both macOS and linux have this kernel restriction, although only macOS documents it)
126
end
127

128

129
const StatusUninit      = 0 # handle is allocated, but not initialized
130
const StatusInit        = 1 # handle is valid, but not connected/active
131
const StatusConnecting  = 2 # handle is in process of connecting
132
const StatusOpen        = 3 # handle is usable
133
const StatusActive      = 4 # handle is listening for read/write/connect events
134
const StatusClosing     = 5 # handle is closing / being closed
135
const StatusClosed      = 6 # handle is closed
136
const StatusEOF         = 7 # handle is a TTY that has seen an EOF event (pretends to be closed until reseteof is called)
137
const StatusPaused      = 8 # handle is Active, but not consuming events, and will transition to Open if it receives an event
138
function uv_status_string(x)
×
139
    s = x.status
9✔
140
    if x.handle == C_NULL
9✔
141
        if s == StatusClosed
×
142
            return "closed"
×
143
        elseif s == StatusUninit
×
144
            return "null"
×
145
        end
146
        return "invalid status"
×
147
    elseif s == StatusUninit
9✔
148
        return "uninit"
×
149
    elseif s == StatusInit
9✔
150
        return "init"
1✔
151
    elseif s == StatusConnecting
8✔
152
        return "connecting"
×
153
    elseif s == StatusOpen
8✔
154
        return "open"
7✔
155
    elseif s == StatusActive
1✔
156
        return "active"
1✔
157
    elseif s == StatusPaused
×
158
        return "paused"
×
159
    elseif s == StatusClosing
×
160
        return "closing"
×
161
    elseif s == StatusClosed
×
162
        return "closed"
×
163
    elseif s == StatusEOF
×
164
        return "eof"
×
165
    end
166
    return "invalid status"
×
167
end
168

169
mutable struct PipeEndpoint <: LibuvStream
170
    handle::Ptr{Cvoid}
171
    status::Int
172
    buffer::IOBuffer
173
    cond::ThreadSynchronizer
174
    readerror::Any
175
    sendbuf::Union{IOBuffer, Nothing}
176
    lock::ReentrantLock # advisory lock
177
    throttle::Int
178
    function PipeEndpoint(handle::Ptr{Cvoid}, status)
1✔
179
        p = new(handle,
714✔
180
                status,
181
                PipeBuffer(),
182
                ThreadSynchronizer(),
183
                nothing,
184
                nothing,
185
                ReentrantLock(),
186
                DEFAULT_READ_BUFFER_SZ)
187
        associate_julia_struct(handle, p)
714✔
188
        finalizer(uvfinalize, p)
714✔
189
        return p
714✔
190
    end
191
end
192

193
function PipeEndpoint()
714✔
194
    pipe = PipeEndpoint(Libc.malloc(_sizeof_uv_named_pipe), StatusUninit)
714✔
195
    iolock_begin()
714✔
196
    err = ccall(:uv_pipe_init, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Cint), eventloop(), pipe.handle, 0)
714✔
197
    uv_error("failed to create pipe endpoint", err)
714✔
198
    pipe.status = StatusInit
714✔
199
    iolock_end()
714✔
200
    return pipe
714✔
201
end
202

203
function PipeEndpoint(fd::OS_HANDLE)
204
    pipe = PipeEndpoint()
2✔
205
    iolock_begin()
2✔
206
    err = ccall(:uv_pipe_open, Int32, (Ptr{Cvoid}, OS_HANDLE), pipe.handle, fd)
2✔
207
    uv_error("pipe_open", err)
2✔
208
    pipe.status = StatusOpen
2✔
209
    iolock_end()
2✔
210
    return pipe
2✔
211
end
212
if OS_HANDLE != RawFD
213
    PipeEndpoint(fd::RawFD) = PipeEndpoint(Libc._get_osfhandle(fd))
×
214
end
215

216

217
mutable struct TTY <: LibuvStream
218
    handle::Ptr{Cvoid}
219
    status::Int
220
    buffer::IOBuffer
221
    cond::ThreadSynchronizer
222
    readerror::Any
223
    sendbuf::Union{IOBuffer, Nothing}
224
    lock::ReentrantLock # advisory lock
225
    throttle::Int
226
    @static if Sys.iswindows(); ispty::Bool; end
227
    function TTY(handle::Ptr{Cvoid}, status)
228
        tty = new(
×
229
            handle,
230
            status,
231
            PipeBuffer(),
232
            ThreadSynchronizer(),
233
            nothing,
234
            nothing,
235
            ReentrantLock(),
236
            DEFAULT_READ_BUFFER_SZ)
237
        associate_julia_struct(handle, tty)
×
238
        finalizer(uvfinalize, tty)
×
239
        @static if Sys.iswindows()
240
            tty.ispty = ccall(:jl_ispty, Cint, (Ptr{Cvoid},), handle) != 0
241
        end
242
        return tty
×
243
    end
244
end
245

246
function TTY(fd::OS_HANDLE)
×
247
    tty = TTY(Libc.malloc(_sizeof_uv_tty), StatusUninit)
×
248
    iolock_begin()
×
249
    err = ccall(:uv_tty_init, Int32, (Ptr{Cvoid}, Ptr{Cvoid}, OS_HANDLE, Int32),
×
250
        eventloop(), tty.handle, fd, 0)
251
    uv_error("TTY", err)
×
252
    tty.status = StatusOpen
×
253
    iolock_end()
×
254
    return tty
×
255
end
256
if OS_HANDLE != RawFD
257
    TTY(fd::RawFD) = TTY(Libc._get_osfhandle(fd))
×
258
end
259

260
show(io::IO, stream::LibuvServer) = print(io, typeof(stream), "(",
×
261
    _fd(stream), " ",
262
    uv_status_string(stream), ")")
263
show(io::IO, stream::LibuvStream) = print(io, typeof(stream), "(",
6✔
264
    _fd(stream), " ",
265
    uv_status_string(stream), ", ",
266
    bytesavailable(stream.buffer), " bytes waiting)")
267

268
# Shared LibuvStream object interface
269

270
function isreadable(io::LibuvStream)
4✔
271
    bytesavailable(io) > 0 && return true
2,366✔
272
    isopen(io) || return false
3,039✔
273
    io.status == StatusEOF && return false
1,687✔
274
    return ccall(:uv_is_readable, Cint, (Ptr{Cvoid},), io.handle) != 0
1,466✔
275
end
276

277
function iswritable(io::LibuvStream)
6✔
278
    isopen(io) || return false
716✔
279
    io.status == StatusClosing && return false
8✔
280
    return ccall(:uv_is_writable, Cint, (Ptr{Cvoid},), io.handle) != 0
8✔
281
end
282

283
lock(s::LibuvStream) = lock(s.lock)
109,289✔
284
unlock(s::LibuvStream) = unlock(s.lock)
108,694✔
285

286
setup_stdio(stream::LibuvStream, ::Bool) = (stream, false)
778✔
287
rawhandle(stream::LibuvStream) = stream.handle
842✔
288
unsafe_convert(::Type{Ptr{Cvoid}}, s::Union{LibuvStream, LibuvServer}) = s.handle
283,516✔
289

290
function init_stdio(handle::Ptr{Cvoid})
×
291
    iolock_begin()
×
292
    t = ccall(:jl_uv_handle_type, Int32, (Ptr{Cvoid},), handle)
×
293
    local io
×
294
    if t == UV_FILE
×
295
        fd = ccall(:jl_uv_file_handle, OS_HANDLE, (Ptr{Cvoid},), handle)
×
296
        # TODO: Replace ios.c file with libuv fs?
297
        # return File(fd)
298
        @static if Sys.iswindows()
×
299
            # TODO: Get ios.c to understand native handles
300
            fd = ccall(:_open_osfhandle, RawFD, (WindowsRawSocket, Int32), fd, 0)
×
301
        end
302
        # TODO: Get fdio to work natively with file descriptors instead of integers
303
        io = fdio(cconvert(Cint, fd))
×
304
    elseif t == UV_TTY
×
305
        io = TTY(handle, StatusOpen)
×
306
    elseif t == UV_TCP
×
307
        Sockets = require_stdlib(PkgId(UUID((0x6462fe0b_24de_5631, 0x8697_dd941f90decc)), "Sockets"))
×
308
        io = Sockets.TCPSocket(handle, StatusOpen)
×
309
    elseif t == UV_NAMED_PIPE
×
310
        io = PipeEndpoint(handle, StatusOpen)
×
311
    else
312
        throw(ArgumentError("invalid stdio type: $t"))
×
313
    end
314
    iolock_end()
×
315
    return io
×
316
end
317

318
"""
319
    open(fd::OS_HANDLE)::IO
320

321
Take a raw file descriptor and wrap it in a Julia-aware IO type,
322
and take ownership of the fd handle.
323
Call `open(Libc.dup(fd))` to avoid the ownership capture
324
of the original handle.
325

326
!!! warning
327
    Do not call this on a handle that's already owned by some
328
    other part of the system.
329
"""
330
function open(h::OS_HANDLE)
2✔
331
    iolock_begin()
2✔
332
    t = ccall(:uv_guess_handle, Cint, (OS_HANDLE,), h)
2✔
333
    local io
334
    if t == UV_FILE
2✔
335
        @static if Sys.iswindows()
336
            # TODO: Get ios.c to understand native handles
337
            h = ccall(:_open_osfhandle, RawFD, (WindowsRawSocket, Int32), h, 0)
338
        end
339
        # TODO: Get fdio to work natively with file descriptors instead of integers
340
        io = fdio(cconvert(Cint, h))
×
341
    elseif t == UV_TTY
2✔
342
        io = TTY(h)
×
343
    elseif t == UV_TCP
2✔
344
        Sockets = require_stdlib(PkgId(UUID((0x6462fe0b_24de_5631, 0x8697_dd941f90decc)), "Sockets"))
×
345
        io = Sockets.TCPSocket(h)
×
346
    elseif t == UV_NAMED_PIPE
2✔
347
        io = PipeEndpoint(h)
2✔
348
        @static if Sys.iswindows()
349
            if ccall(:jl_ispty, Cint, (Ptr{Cvoid},), io.handle) != 0
350
                # replace the Julia `PipeEndpoint` type with a `TTY` type,
351
                # if we detect that this is a cygwin pty object
352
                pipe_handle, pipe_status = io.handle, io.status
353
                io.status = StatusClosed
354
                io.handle = C_NULL
355
                io = TTY(pipe_handle, pipe_status)
356
            end
357
        end
358
    else
359
        throw(ArgumentError("invalid stdio type: $t"))
×
360
    end
361
    iolock_end()
2✔
362
    return io
2✔
363
end
364

365
if OS_HANDLE != RawFD
366
    function open(fd::RawFD)
×
367
        h = Libc.dup(Libc._get_osfhandle(fd)) # make a dup to steal ownership away from msvcrt
×
368
        try
×
369
            io = open(h)
×
370
            ccall(:_close, Cint, (RawFD,), fd) # on success, destroy the old libc handle
×
371
            return io
×
372
        catch ex
373
            ccall(:CloseHandle, stdcall, Cint, (OS_HANDLE,), h) # on failure, destroy the new nt handle
×
374
            rethrow(ex)
×
375
        end
376
    end
377
end
378

379
function isopen(x::Union{LibuvStream, LibuvServer})
603✔
380
    if x.status == StatusUninit || x.status == StatusInit || x.handle === C_NULL
1,399,284✔
381
        throw(ArgumentError("$x is not initialized"))
×
382
    end
383
    return x.status != StatusClosed
699,642✔
384
end
385

386
function check_open(x::Union{LibuvStream, LibuvServer})
30✔
387
    if !isopen(x) || x.status == StatusClosing
563,952✔
388
        throw(IOError("stream is closed or unusable", 0))
×
389
    end
390
end
391

392
function wait_readnb(x::LibuvStream, nb::Int)
39,757✔
393
    # fast path before iolock acquire
394
    bytesavailable(x.buffer) >= nb && return
39,757✔
395
    open = isopen(x) && x.status != StatusEOF # must precede readerror check
38,673✔
396
    x.readerror === nothing || throw(x.readerror)
38,673✔
397
    open || return
38,780✔
398
    iolock_begin()
38,566✔
399
    # repeat fast path after iolock acquire, before other expensive work
400
    bytesavailable(x.buffer) >= nb && (iolock_end(); return)
38,566✔
401
    open = isopen(x) && x.status != StatusEOF
38,566✔
402
    x.readerror === nothing || throw(x.readerror)
38,566✔
403
    open || (iolock_end(); return)
38,566✔
404
    # now do the "real" work
405
    oldthrottle = x.throttle
38,566✔
406
    preserve_handle(x)
38,566✔
407
    lock(x.cond)
38,566✔
408
    try
38,566✔
409
        while bytesavailable(x.buffer) < nb
80,665✔
410
            x.readerror === nothing || throw(x.readerror)
42,897✔
411
            isopen(x) || break
43,140✔
412
            x.status == StatusEOF && break
42,654✔
413
            x.throttle = max(nb, x.throttle)
42,322✔
414
            start_reading(x) # ensure we are reading
42,322✔
415
            iolock_end()
42,322✔
416
            wait(x.cond)
42,322✔
417
            unlock(x.cond)
42,099✔
418
            iolock_begin()
42,099✔
419
            lock(x.cond)
42,099✔
420
        end
80,442✔
421
    finally
422
        if isempty(x.cond)
38,344✔
423
            stop_reading(x) # stop reading iff there are currently no other read clients of the stream
38,344✔
424
        end
425
        if oldthrottle <= x.throttle <= nb
38,344✔
426
            # if we're interleaving readers, we might not get back to the "original" throttle
427
            # but we consider that an acceptable "risk", since we can't be quite sure what the intended value is now
428
            x.throttle = oldthrottle
57✔
429
        end
430
        unpreserve_handle(x)
38,344✔
431
        unlock(x.cond)
38,344✔
432
    end
433
    iolock_end()
38,343✔
434
    nothing
38,343✔
435
end
436

437
function closewrite(s::LibuvStream)
353✔
438
    iolock_begin()
353✔
439
    if !iswritable(s)
355✔
440
        iolock_end()
351✔
441
        return
351✔
442
    end
443
    req = Libc.malloc(_sizeof_uv_shutdown)
2✔
444
    uv_req_set_data(req, C_NULL) # in case we get interrupted before arriving at the wait call
2✔
445
    err = ccall(:uv_shutdown, Int32, (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}),
2✔
446
                req, s, @cfunction(uv_shutdowncb_task, Cvoid, (Ptr{Cvoid}, Cint)))
447
    if err < 0
2✔
448
        Libc.free(req)
×
449
        uv_error("shutdown", err)
×
450
    end
451
    ct = current_task()
2✔
452
    preserve_handle(ct)
2✔
453
    sigatomic_begin()
2✔
454
    uv_req_set_data(req, ct)
2✔
455
    iolock_end()
2✔
456
    local status
457
    try
2✔
458
        sigatomic_end()
2✔
459
        status = wait()::Cint
2✔
460
        sigatomic_begin()
2✔
461
    finally
462
        # try-finally unwinds the sigatomic level, so need to repeat sigatomic_end
463
        sigatomic_end()
2✔
464
        iolock_begin()
2✔
465
        q = ct.queue; q === nothing || Base.list_deletefirst!(q::IntrusiveLinkedList{Task}, ct)
2✔
466
        if uv_req_data(req) != C_NULL
2✔
467
            # req is still alive,
468
            # so make sure we won't get spurious notifications later
469
            uv_req_set_data(req, C_NULL)
×
470
        else
471
            # done with req
472
            Libc.free(req)
2✔
473
        end
474
        iolock_end()
2✔
475
        unpreserve_handle(ct)
2✔
476
    end
477
    if isopen(s)
2✔
478
        if status < 0 || ccall(:uv_is_readable, Cint, (Ptr{Cvoid},), s.handle) == 0
4✔
479
            close(s)
×
480
        end
481
    end
482
    if status < 0
2✔
483
        throw(_UVError("shutdown", status))
×
484
    end
485
    nothing
2✔
486
end
487

488
function wait_close(x::Union{LibuvStream, LibuvServer})
3,168✔
489
    preserve_handle(x)
3,168✔
490
    lock(x.cond)
3,168✔
491
    try
3,168✔
492
        while isopen(x)
6,167✔
493
            wait(x.cond)
3,013✔
494
        end
2,999✔
495
    finally
496
        unlock(x.cond)
3,154✔
497
        unpreserve_handle(x)
3,154✔
498
    end
499
    nothing
3,154✔
500
end
501

502
function close(stream::Union{LibuvStream, LibuvServer})
3,167✔
503
    iolock_begin()
3,167✔
504
    if stream.status == StatusInit
3,167✔
505
        ccall(:jl_forceclose_uv, Cvoid, (Ptr{Cvoid},), stream.handle)
2✔
506
        stream.status = StatusClosing
2✔
507
    elseif isopen(stream)
3,165✔
508
        if stream.status != StatusClosing
3,010✔
509
            ccall(:jl_close_uv, Cvoid, (Ptr{Cvoid},), stream.handle)
3,010✔
510
            stream.status = StatusClosing
3,010✔
511
        end
512
    end
513
    iolock_end()
3,167✔
514
    wait_close(stream)
3,167✔
515
    nothing
3,153✔
516
end
517

518
function uvfinalize(uv::Union{LibuvStream, LibuvServer})
672✔
519
    iolock_begin()
672✔
520
    if uv.handle != C_NULL
672✔
521
        disassociate_julia_struct(uv.handle) # not going to call the usual close hooks (so preserve_handle is not needed)
672✔
522
        if uv.status == StatusUninit
672✔
523
            Libc.free(uv.handle)
×
524
        elseif uv.status == StatusInit
672✔
525
            ccall(:jl_forceclose_uv, Cvoid, (Ptr{Cvoid},), uv.handle)
×
526
        elseif isopen(uv)
672✔
527
            if uv.status != StatusClosing
145✔
528
                ccall(:jl_close_uv, Cvoid, (Ptr{Cvoid},), uv.handle)
145✔
529
            end
530
        elseif uv.status == StatusClosed
527✔
531
            Libc.free(uv.handle)
527✔
532
        end
533
        uv.handle = C_NULL
672✔
534
        uv.status = StatusClosed
672✔
535
    end
536
    iolock_end()
672✔
537
    nothing
672✔
538
end
539

540
if Sys.iswindows()
541
    ispty(s::TTY) = s.ispty
×
542
    ispty(s::IO) = false
×
543
end
544

545
"""
546
    displaysize([io::IO]) -> (lines, columns)
547

548
Return the nominal size of the screen that may be used for rendering output to
549
this `IO` object.
550
If no input is provided, the environment variables `LINES` and `COLUMNS` are read.
551
If those are not set, a default size of `(24, 80)` is returned.
552

553
# Examples
554
```jldoctest
555
julia> withenv("LINES" => 30, "COLUMNS" => 100) do
556
           displaysize()
557
       end
558
(30, 100)
559
```
560

561
To get your TTY size,
562

563
```julia-repl
564
julia> displaysize(stdout)
565
(34, 147)
566
```
567
"""
568
displaysize(io::IO) = displaysize()
36,509✔
569
displaysize() = (parse(Int, get(ENV, "LINES",   "24")),
30,098✔
570
                 parse(Int, get(ENV, "COLUMNS", "80")))::Tuple{Int, Int}
571

572
# This is a fancy way to make de-specialize a call to `displaysize(io::IO)`
573
# which is unfortunately invalidated by REPL
574
#  (https://github.com/JuliaLang/julia/issues/56080)
575
#
576
# This makes the call less efficient, but avoids being invalidated by REPL.
577
displaysize_(io::IO) = Base.invoke_in_world(Base.tls_world_age(), displaysize, io)::Tuple{Int,Int}
30,063✔
578

579
function displaysize(io::TTY)
28✔
580
    check_open(io)
28✔
581

582
    local h::Int, w::Int
583
    default_size = displaysize()
28✔
584

585
    @static if Sys.iswindows()
586
        if ispty(io)
587
            # io is actually a libuv pipe but a cygwin/msys2 pty
588
            try
589
                h, w = parse.(Int, split(read(open(Base.Cmd(String["stty", "size"]), "r", io).out, String)))
590
                h > 0 || (h = default_size[1])
591
                w > 0 || (w = default_size[2])
592
                return h, w
593
            catch
594
                return default_size
595
            end
596
        end
597
    end
598

599
    s1 = Ref{Int32}(0)
28✔
600
    s2 = Ref{Int32}(0)
28✔
601
    iolock_begin()
28✔
602
    check_open(io)
28✔
603
    Base.uv_error("size (TTY)", ccall(:uv_tty_get_winsize,
28✔
604
                                      Int32, (Ptr{Cvoid}, Ptr{Int32}, Ptr{Int32}),
605
                                      io, s1, s2) != 0)
606
    iolock_end()
28✔
607
    w, h = s1[], s2[]
28✔
608
    h > 0 || (h = default_size[1])
28✔
609
    w > 0 || (w = default_size[2])
28✔
610
    return h, w
28✔
611
end
612

613
### Libuv callbacks ###
614

615
## BUFFER ##
616
## Allocate space in buffer (for immediate use)
617
function alloc_request(buffer::IOBuffer, recommended_size::UInt)
2✔
618
    ensureroom(buffer, recommended_size)
408,980✔
619
    ptr = buffer.append ? buffer.size + 1 : buffer.ptr
204,491✔
620
    nb = min(length(buffer.data), buffer.maxsize + get_offset(buffer)) - ptr + 1
204,491✔
621
    return (Ptr{Cvoid}(pointer(buffer.data, ptr)), nb)
204,491✔
622
end
623

624
notify_filled(buffer::IOBuffer, nread::Int, base::Ptr{Cvoid}, len::UInt) = notify_filled(buffer, nread)
×
625

626
function notify_filled(buffer::IOBuffer, nread::Int)
1✔
627
    if buffer.append
225,988✔
628
        buffer.size += nread
225,988✔
629
    else
630
        buffer.ptr += nread
×
631
        buffer.size = max(buffer.size, buffer.ptr - 1)
×
632
    end
633
    nothing
204,099✔
634
end
635

636
function alloc_buf_hook(stream::LibuvStream, size::UInt)
2✔
637
    throttle = UInt(stream.throttle)
204,491✔
638
    return alloc_request(stream.buffer, (size > throttle) ? throttle : size)
204,491✔
639
end
640

641
function uv_alloc_buf(handle::Ptr{Cvoid}, size::Csize_t, buf::Ptr{Cvoid})
204,513✔
642
    hd = uv_handle_data(handle)
204,513✔
643
    if hd == C_NULL
204,513✔
644
        ccall(:jl_uv_buf_set_len, Cvoid, (Ptr{Cvoid}, Csize_t), buf, 0)
×
645
        return nothing
×
646
    end
647
    stream = unsafe_pointer_to_objref(hd)::LibuvStream
204,513✔
648

649
    local data::Ptr{Cvoid}, newsize::Csize_t
650
    if stream.status != StatusActive
204,513✔
651
        data = C_NULL
22✔
652
        newsize = 0
22✔
653
    else
654
        (data, newsize) = alloc_buf_hook(stream, UInt(size))
408,982✔
655
        if data == C_NULL
204,491✔
656
            newsize = 0
×
657
        end
658
        # avoid aliasing of `nread` with `errno` in uv_readcb
659
        # or exceeding the Win32 maximum uv_buf_t len
660
        maxsize = @static Sys.iswindows() ? typemax(Cint) : typemax(Cssize_t)
204,491✔
661
        newsize > maxsize && (newsize = maxsize)
204,491✔
662
    end
663

664
    ccall(:jl_uv_buf_set_base, Cvoid, (Ptr{Cvoid}, Ptr{Cvoid}), buf, data)
204,513✔
665
    ccall(:jl_uv_buf_set_len, Cvoid, (Ptr{Cvoid}, Csize_t), buf, newsize)
204,513✔
666
    nothing
204,513✔
667
end
668

669
function uv_readcb(handle::Ptr{Cvoid}, nread::Cssize_t, buf::Ptr{Cvoid})
208,761✔
670
    stream_unknown_type = @handle_as handle LibuvStream
208,761✔
671
    nrequested = ccall(:jl_uv_buf_len, Csize_t, (Ptr{Cvoid},), buf)
208,761✔
672
    function readcb_specialized(stream::LibuvStream, nread::Int, nrequested::UInt)
435,810✔
673
        lock(stream.cond)
227,049✔
674
        if nread < 0
227,049✔
675
            if nread == UV_ENOBUFS && nrequested == 0
1,061✔
676
                # remind the client that stream.buffer is full
677
                notify(stream.cond)
270✔
678
            elseif nread == UV_EOF # libuv called uv_stop_reading already
791✔
679
                if stream.status != StatusClosing
791✔
680
                    stream.status = StatusEOF
791✔
681
                    notify(stream.cond)
791✔
682
                    if stream isa TTY
791✔
683
                        # stream can still be used by reseteof (or possibly write)
684
                    elseif !(stream isa PipeEndpoint) && ccall(:uv_is_writable, Cint, (Ptr{Cvoid},), stream.handle) != 0
774✔
685
                        # stream can still be used by write
686
                    else
687
                        # underlying stream is no longer useful: begin finalization
688
                        ccall(:jl_close_uv, Cvoid, (Ptr{Cvoid},), stream.handle)
275✔
689
                        stream.status = StatusClosing
275✔
690
                    end
691
                end
692
            else
693
                stream.readerror = _UVError("read", nread)
×
694
                notify(stream.cond)
×
695
                # This is a fatal connection error
696
                ccall(:jl_close_uv, Cvoid, (Ptr{Cvoid},), stream.handle)
×
697
                stream.status = StatusClosing
×
698
            end
699
        else
700
            notify_filled(stream.buffer, nread)
225,988✔
701
            notify(stream.cond)
225,988✔
702
        end
703
        unlock(stream.cond)
227,049✔
704

705
        # Stop background reading when
706
        # 1) there's nobody paying attention to the data we are reading
707
        # 2) we have accumulated a lot of unread data OR
708
        # 3) we have an alternate buffer that has reached its limit.
709
        if stream.status == StatusPaused ||
453,828✔
710
           (stream.status == StatusActive &&
711
            ((bytesavailable(stream.buffer) >= stream.throttle) ||
712
             (bytesavailable(stream.buffer) >= stream.buffer.maxsize)))
713
            # save cycles by stopping kernel notifications from arriving
714
            ccall(:uv_read_stop, Cint, (Ptr{Cvoid},), stream)
406✔
715
            stream.status = StatusOpen
406✔
716
        end
717
        nothing
227,049✔
718
    end
719
    readcb_specialized(stream_unknown_type, Int(nread), UInt(nrequested))
208,761✔
720
    nothing
208,761✔
721
end
722

723
function reseteof(x::TTY)
2✔
724
    iolock_begin()
2✔
725
    if x.status == StatusEOF
2✔
726
        x.status = StatusOpen
×
727
    end
728
    iolock_end()
2✔
729
    nothing
2✔
730
end
731

732
function _uv_hook_close(uv::Union{LibuvStream, LibuvServer})
692✔
733
    lock(uv.cond)
692✔
734
    try
692✔
735
        uv.status = StatusClosed
692✔
736
        # notify any listeners that exist on this libuv stream type
737
        notify(uv.cond)
692✔
738
    finally
739
        unlock(uv.cond)
692✔
740
    end
741
    nothing
692✔
742
end
743

744

745
##########################################
746
# Pipe Abstraction
747
#  (composed of two half-pipes: .in and .out)
748
##########################################
749

750
mutable struct Pipe <: AbstractPipe
751
    in::PipeEndpoint # writable
335✔
752
    out::PipeEndpoint # readable
753
end
754

755
"""
756
    Pipe()
757

758
Construct an uninitialized Pipe object, especially for IO communication between multiple processes.
759

760
The appropriate end of the pipe will be automatically initialized if the object is used in
761
process spawning. This can be useful to easily obtain references in process pipelines, e.g.:
762

763
```
764
julia> err = Pipe()
765

766
# After this `err` will be initialized and you may read `foo`'s
767
# stderr from the `err` pipe, or pass `err` to other pipelines.
768
julia> run(pipeline(pipeline(`foo`, stderr=err), `cat`), wait=false)
769

770
# Now destroy the write half of the pipe, so that the read half will get EOF
771
julia> closewrite(err)
772

773
julia> read(err, String)
774
"stderr messages"
775
```
776

777
See also [`Base.link_pipe!`](@ref).
778
"""
779
Pipe() = Pipe(PipeEndpoint(), PipeEndpoint())
337✔
780
pipe_reader(p::Pipe) = p.out
298✔
781
pipe_writer(p::Pipe) = p.in
65,613✔
782

783
"""
784
    link_pipe!(pipe; reader_supports_async=false, writer_supports_async=false)
785

786
Initialize `pipe` and link the `in` endpoint to the `out` endpoint. The keyword
787
arguments `reader_supports_async`/`writer_supports_async` correspond to
788
`OVERLAPPED` on Windows and `O_NONBLOCK` on POSIX systems. They should be `true`
789
unless they'll be used by an external program (e.g. the output of a command
790
executed with [`run`](@ref)).
791
"""
792
function link_pipe!(pipe::Pipe;
339✔
793
                    reader_supports_async = false,
794
                    writer_supports_async = false)
795
     link_pipe!(pipe.out, reader_supports_async, pipe.in, writer_supports_async)
339✔
796
     return pipe
333✔
797
end
798

799
show(io::IO, stream::Pipe) = print(io,
1✔
800
    "Pipe(",
801
    _fd(stream.in), " ",
802
    uv_status_string(stream.in), " => ",
803
    _fd(stream.out), " ",
804
    uv_status_string(stream.out), ", ",
805
    bytesavailable(stream), " bytes waiting)")
806

807
closewrite(pipe::Pipe) = close(pipe.in)
×
808

809
## Functions for PipeEndpoint and PipeServer ##
810

811
function open_pipe!(p::PipeEndpoint, handle::OS_HANDLE)
1✔
812
    iolock_begin()
712✔
813
    if p.status != StatusInit
712✔
814
        error("pipe is already in use or has been closed")
×
815
    end
816
    err = ccall(:uv_pipe_open, Int32, (Ptr{Cvoid}, OS_HANDLE), p.handle, handle)
712✔
817
    uv_error("pipe_open", err)
712✔
818
    p.status = StatusOpen
711✔
819
    iolock_end()
711✔
820
    return p
711✔
821
end
822

823

824
function link_pipe!(read_end::PipeEndpoint, reader_supports_async::Bool,
120✔
825
                    write_end::PipeEndpoint, writer_supports_async::Bool)
826
    rd, wr = link_pipe(reader_supports_async, writer_supports_async)
120✔
827
    try
120✔
828
        try
120✔
829
            open_pipe!(read_end, rd)
120✔
830
        catch
831
            close_pipe_sync(rd)
1✔
832
            rethrow()
1✔
833
        end
834
        open_pipe!(write_end, wr)
119✔
835
    catch
836
        close_pipe_sync(wr)
1✔
837
        rethrow()
1✔
838
    end
839
    nothing
119✔
840
end
841

842
function link_pipe(reader_supports_async::Bool, writer_supports_async::Bool)
1✔
843
    UV_NONBLOCK_PIPE = 0x40
594✔
844
    fildes = Ref{Pair{OS_HANDLE, OS_HANDLE}}(INVALID_OS_HANDLE => INVALID_OS_HANDLE) # read (in) => write (out)
594✔
845
    err = ccall(:uv_pipe, Int32, (Ptr{Pair{OS_HANDLE, OS_HANDLE}}, Cint, Cint),
594✔
846
                fildes,
847
                reader_supports_async * UV_NONBLOCK_PIPE,
848
                writer_supports_async * UV_NONBLOCK_PIPE)
849
    uv_error("pipe", err)
594✔
850
    return fildes[]
594✔
851
end
852

853
if Sys.iswindows()
854
    function close_pipe_sync(handle::WindowsRawSocket)
×
855
        ccall(:CloseHandle, stdcall, Cint, (WindowsRawSocket,), handle)
×
856
        nothing
×
857
    end
858
else
859
    function close_pipe_sync(handle::RawFD)
1✔
860
        ccall(:close, Cint, (RawFD,), handle)
502✔
861
        nothing
476✔
862
    end
863
end
864

865
## Functions for any LibuvStream ##
866

867
# flow control
868

869
function start_reading(stream::LibuvStream)
164,365✔
870
    iolock_begin()
164,365✔
871
    if stream.status == StatusOpen
164,365✔
872
        if !isreadable(stream)
2,928✔
873
            error("tried to read a stream that is not readable")
×
874
        end
875
        # libuv may call the alloc callback immediately
876
        # for a TTY on Windows, so ensure the status is set first
877
        stream.status = StatusActive
1,465✔
878
        ret = ccall(:uv_read_start, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}),
1,465✔
879
                    stream, @cfunction(uv_alloc_buf, Cvoid, (Ptr{Cvoid}, Csize_t, Ptr{Cvoid})),
880
                    @cfunction(uv_readcb, Cvoid, (Ptr{Cvoid}, Cssize_t, Ptr{Cvoid})))
881
    elseif stream.status == StatusPaused
162,900✔
882
        stream.status = StatusActive
98,496✔
883
        ret = Int32(0)
98,496✔
884
    elseif stream.status == StatusActive
64,404✔
885
        ret = Int32(0)
64,404✔
886
    else
887
        ret = Int32(-1)
×
888
    end
889
    iolock_end()
164,365✔
890
    return ret
164,365✔
891
end
892

893
if Sys.iswindows()
894
    # the low performance version of stop_reading is required
895
    # on Windows due to a NT kernel bug that we can't use a blocking
896
    # stream for non-blocking (overlapped) calls,
897
    # and a ReadFile call blocking on one thread
898
    # causes all other operations on that stream to lockup
899
    function stop_reading(stream::LibuvStream)
×
900
        iolock_begin()
×
901
        if stream.status == StatusActive
×
902
            stream.status = StatusOpen
×
903
            ccall(:uv_read_stop, Cint, (Ptr{Cvoid},), stream)
×
904
        end
905
        iolock_end()
×
906
        nothing
×
907
    end
908
else
909
    function stop_reading(stream::LibuvStream)
910
        iolock_begin()
99,777✔
911
        if stream.status == StatusActive
99,777✔
912
            stream.status = StatusPaused
98,857✔
913
        end
914
        iolock_end()
99,777✔
915
        nothing
99,739✔
916
    end
917
end
918

919
# bulk read / write
920

921
readbytes!(s::LibuvStream, a::Vector{UInt8}, nb = length(a)) = readbytes!(s, a, Int(nb))
140✔
922
function readbytes!(s::LibuvStream, a::Vector{UInt8}, nb::Int)
249,719✔
923
    iolock_begin()
249,719✔
924
    sbuf = s.buffer
249,719✔
925
    @assert sbuf.seekable == false
249,719✔
926
    @assert sbuf.maxsize >= nb
249,719✔
927

928
    function wait_locked(s, buf, nb)
499,462✔
929
        while bytesavailable(buf) < nb
252,762✔
930
            s.readerror === nothing || throw(s.readerror)
3,191✔
931
            isopen(s) || break
3,337✔
932
            s.status != StatusEOF || break
3,071✔
933
            iolock_end()
3,019✔
934
            wait_readnb(s, nb)
3,019✔
935
            iolock_begin()
3,019✔
936
        end
3,019✔
937
    end
938

939
    if nb <= SZ_UNBUFFERED_IO # Under this limit we are OK with copying the array from the stream's buffer
249,719✔
940
        wait_locked(s, sbuf, nb)
249,578✔
941
    end
942
    if bytesavailable(sbuf) >= nb
249,719✔
943
        nread = readbytes!(sbuf, a, nb)
249,554✔
944
    else
945
        initsize = length(a)
165✔
946
        newbuf = _truncated_pipebuffer(a; maxsize=nb)
165✔
947
        nread = try
165✔
948
            s.buffer = newbuf
165✔
949
            write(newbuf, sbuf)
165✔
950
            wait_locked(s, newbuf, nb)
165✔
951
            bytesavailable(newbuf)
165✔
952
        finally
953
            s.buffer = sbuf
165✔
954
        end
955
        _take!(a, _unsafe_take!(newbuf))
165✔
956
        length(a) >= initsize || resize!(a, initsize)
165✔
957
    end
958
    iolock_end()
249,719✔
959
    return nread
249,719✔
960
end
961

962
function read(stream::LibuvStream)
546✔
963
    wait_readnb(stream, typemax(Int))
577✔
964
    iolock_begin()
577✔
965
    bytes = take!(stream.buffer)
577✔
966
    iolock_end()
577✔
967
    return bytes
567✔
968
end
969

970
function unsafe_read(s::LibuvStream, p::Ptr{UInt8}, nb::UInt)
781,693✔
971
    iolock_begin()
781,693✔
972
    sbuf = s.buffer
781,693✔
973
    @assert sbuf.seekable == false
781,693✔
974
    @assert sbuf.maxsize >= nb
781,693✔
975

976
    function wait_locked(s, buf, nb)
1,563,302✔
977
        while bytesavailable(buf) < nb
814,857✔
978
            s.readerror === nothing || throw(s.readerror)
33,635✔
979
            isopen(s) || throw(EOFError())
33,649✔
980
            s.status != StatusEOF || throw(EOFError())
33,772✔
981
            iolock_end()
33,470✔
982
            wait_readnb(s, nb)
33,470✔
983
            iolock_begin()
33,248✔
984
        end
33,248✔
985
    end
986

987
    if nb <= SZ_UNBUFFERED_IO # Under this limit we are OK with copying the array from the stream's buffer
781,693✔
988
        wait_locked(s, sbuf, Int(nb))
781,468✔
989
    end
990
    if bytesavailable(sbuf) >= nb
781,312✔
991
        unsafe_read(sbuf, p, nb)
781,171✔
992
    else
993
        newbuf = _truncated_pipebuffer(unsafe_wrap(Array, p, nb); maxsize=Int(nb))
141✔
994
        try
141✔
995
            s.buffer = newbuf
141✔
996
            write(newbuf, sbuf)
141✔
997
            wait_locked(s, newbuf, Int(nb))
141✔
998
        finally
999
            s.buffer = sbuf
141✔
1000
        end
1001
    end
1002
    iolock_end()
781,306✔
1003
    nothing
781,306✔
1004
end
1005

1006
function read(this::LibuvStream, ::Type{UInt8})
3,359,838✔
1007
    iolock_begin()
3,359,838✔
1008
    sbuf = this.buffer
3,359,838✔
1009
    @assert sbuf.seekable == false
3,359,838✔
1010
    while bytesavailable(sbuf) < 1
3,359,997✔
1011
        iolock_end()
163✔
1012
        eof(this) && throw(EOFError())
326✔
1013
        iolock_begin()
159✔
1014
    end
159✔
1015
    c = read(sbuf, UInt8)
3,359,834✔
1016
    iolock_end()
3,359,834✔
1017
    return c
3,359,834✔
1018
end
1019

1020
function readavailable(this::LibuvStream)
1,497✔
1021
    wait_readnb(this, 1) # unlike the other `read` family of functions, this one doesn't guarantee error reporting
2,304✔
1022
    iolock_begin()
2,304✔
1023
    buf = this.buffer
2,304✔
1024
    @assert buf.seekable == false
2,304✔
1025
    bytes = take!(buf)
2,304✔
1026
    iolock_end()
2,304✔
1027
    return bytes
2,304✔
1028
end
1029

1030
function copyuntil(out::IO, x::LibuvStream, c::UInt8; keep::Bool=false)
125,420✔
1031
    iolock_begin()
62,562✔
1032
    buf = x.buffer
62,562✔
1033
    @assert buf.seekable == false
62,562✔
1034
    if !occursin(c, buf) # fast path checks first
63,691✔
1035
        x.readerror === nothing || throw(x.readerror)
61,433✔
1036
        if isopen(x) && x.status != StatusEOF
61,433✔
1037
            preserve_handle(x)
61,433✔
1038
            lock(x.cond)
61,433✔
1039
            try
61,433✔
1040
                while !occursin(c, x.buffer)
244,834✔
1041
                    x.readerror === nothing || throw(x.readerror)
122,204✔
1042
                    isopen(x) || break
122,222✔
1043
                    x.status != StatusEOF || break
122,286✔
1044
                    start_reading(x) # ensure we are reading
122,086✔
1045
                    iolock_end()
122,086✔
1046
                    wait(x.cond)
122,086✔
1047
                    unlock(x.cond)
122,086✔
1048
                    iolock_begin()
122,086✔
1049
                    lock(x.cond)
122,086✔
1050
                end
183,519✔
1051
            finally
1052
                if isempty(x.cond)
61,433✔
1053
                    stop_reading(x) # stop reading iff there are currently no other read clients of the stream
61,433✔
1054
                end
1055
                unlock(x.cond)
61,433✔
1056
                unpreserve_handle(x)
61,433✔
1057
            end
1058
        end
1059
    end
1060
    copyuntil(out, buf, c; keep)
62,562✔
1061
    iolock_end()
62,562✔
1062
    return out
62,562✔
1063
end
1064

1065
uv_write(s::LibuvStream, p::Vector{UInt8}) = GC.@preserve p uv_write(s, pointer(p), UInt(sizeof(p)))
33,378✔
1066

1067
# caller must have acquired the iolock
1068
function uv_write(s::LibuvStream, p::Ptr{UInt8}, n::UInt)
277,731✔
1069
    uvw = uv_write_async(s, p, n)
277,731✔
1070
    ct = current_task()
277,731✔
1071
    preserve_handle(ct)
277,731✔
1072
    sigatomic_begin()
277,731✔
1073
    uv_req_set_data(uvw, ct)
277,731✔
1074
    iolock_end()
277,731✔
1075
    local status
1076
    try
277,731✔
1077
        sigatomic_end()
277,731✔
1078
        # wait for the last chunk to complete (or error)
1079
        # assume that any errors would be sticky,
1080
        # (so we don't need to monitor the error status of the intermediate writes)
1081
        status = wait()::Cint
277,731✔
1082
        sigatomic_begin()
277,729✔
1083
    finally
1084
        # try-finally unwinds the sigatomic level, so need to repeat sigatomic_end
1085
        sigatomic_end()
277,731✔
1086
        iolock_begin()
277,731✔
1087
        q = ct.queue; q === nothing || Base.list_deletefirst!(q::IntrusiveLinkedList{Task}, ct)
277,731✔
1088
        if uv_req_data(uvw) != C_NULL
277,731✔
1089
            # uvw is still alive,
1090
            # so make sure we won't get spurious notifications later
1091
            uv_req_set_data(uvw, C_NULL)
×
1092
        else
1093
            # done with uvw
1094
            Libc.free(uvw)
277,731✔
1095
        end
1096
        iolock_end()
277,731✔
1097
        unpreserve_handle(ct)
277,731✔
1098
    end
1099
    if status < 0
277,729✔
1100
        throw(_UVError("write", status))
×
1101
    end
1102
    return Int(n)
277,729✔
1103
end
1104

1105
# helper function for uv_write that returns the uv_write_t struct for the write
1106
# rather than waiting on it, caller must hold the iolock
1107
function uv_write_async(s::LibuvStream, p::Ptr{UInt8}, n::UInt)
277,825✔
1108
    check_open(s)
277,825✔
1109
    while true
277,825✔
1110
        uvw = Libc.malloc(_sizeof_uv_write)
277,825✔
1111
        uv_req_set_data(uvw, C_NULL) # in case we get interrupted before arriving at the wait call
277,825✔
1112
        nwrite = min(n, MAX_OS_WRITE) # split up the write into chunks the OS can handle.
277,825✔
1113
        # TODO: use writev instead of a loop
1114
        err = ccall(:jl_uv_write,
277,825✔
1115
                    Int32,
1116
                    (Ptr{Cvoid}, Ptr{Cvoid}, UInt, Ptr{Cvoid}, Ptr{Cvoid}),
1117
                    s, p, nwrite, uvw,
1118
                    @cfunction(uv_writecb_task, Cvoid, (Ptr{Cvoid}, Cint)))
1119
        if err < 0
277,825✔
1120
            Libc.free(uvw)
×
1121
            uv_error("write", err)
×
1122
        end
1123
        n -= nwrite
277,825✔
1124
        p += nwrite
277,825✔
1125
        if n == 0
277,825✔
1126
            return uvw
277,825✔
1127
        end
1128
    end
×
1129
end
1130

1131

1132
# Optimized send
1133
# - smaller writes are buffered, final uv write on flush or when buffer full
1134
# - large isbits arrays are unbuffered and written directly
1135

1136
function unsafe_write(s::LibuvStream, p::Ptr{UInt8}, n::UInt)
840,808✔
1137
    while true
841,060✔
1138
        # try to add to the send buffer
1139
        iolock_begin()
841,060✔
1140
        buf = s.sendbuf
841,060✔
1141
        buf === nothing && break
841,060✔
1142
        totb = bytesavailable(buf) + n
596,927✔
1143
        if totb < buf.maxsize
596,927✔
1144
            nb = unsafe_write(buf, p, n)
596,462✔
1145
            iolock_end()
596,462✔
1146
            return nb
596,462✔
1147
        end
1148
        bytesavailable(buf) == 0 && break
465✔
1149
        # perform flush(s)
1150
        arr = take!(buf)
252✔
1151
        uv_write(s, arr)
252✔
1152
    end
252✔
1153
    # perform the output to the kernel
1154
    return uv_write(s, p, n)
244,346✔
1155
end
1156

1157
function flush(s::LibuvStream)
33,315✔
1158
    iolock_begin()
33,315✔
1159
    buf = s.sendbuf
33,315✔
1160
    if buf !== nothing
33,315✔
1161
        if bytesavailable(buf) > 0
33,126✔
1162
            arr = take!(buf)
33,126✔
1163
            uv_write(s, arr)
33,126✔
1164
            return
33,126✔
1165
        end
1166
    end
1167
    uv_write(s, Ptr{UInt8}(Base.eventloop()), UInt(0)) # zero write from a random pointer to flush current queue
189✔
1168
    return
189✔
1169
end
1170

1171
function buffer_writes(s::LibuvStream, bufsize)
252✔
1172
    sendbuf = PipeBuffer(bufsize)
366✔
1173
    iolock_begin()
366✔
1174
    s.sendbuf = sendbuf
366✔
1175
    iolock_end()
366✔
1176
    return s
366✔
1177
end
1178

1179
## low-level calls to libuv ##
1180

1181
function write(s::LibuvStream, b::UInt8)
1,555,976✔
1182
    buf = s.sendbuf
1,555,976✔
1183
    if buf !== nothing
1,555,976✔
1184
        iolock_begin()
1,549,836✔
1185
        if bytesavailable(buf) + 1 < buf.maxsize
1,549,836✔
1186
            n = write(buf, b)
3,099,640✔
1187
            iolock_end()
1,549,820✔
1188
            return n
1,549,820✔
1189
        end
1190
        iolock_end()
16✔
1191
    end
1192
    return write(s, Ref{UInt8}(b))
6,156✔
1193
end
1194

1195
function uv_writecb_task(req::Ptr{Cvoid}, status::Cint)
258,567✔
1196
    d = uv_req_data(req)
258,567✔
1197
    if d != C_NULL
258,567✔
1198
        uv_req_set_data(req, C_NULL) # let the Task know we got the writecb
258,567✔
1199
        t = unsafe_pointer_to_objref(d)::Task
258,567✔
1200
        schedule(t, status)
258,567✔
1201
    else
1202
        # no owner for this req, safe to just free it
1203
        Libc.free(req)
×
1204
    end
1205
    nothing
258,567✔
1206
end
1207

1208
function uv_shutdowncb_task(req::Ptr{Cvoid}, status::Cint)
×
1209
    d = uv_req_data(req)
×
1210
    if d != C_NULL
×
1211
        uv_req_set_data(req, C_NULL) # let the Task know we got the shutdowncb
×
1212
        t = unsafe_pointer_to_objref(d)::Task
×
1213
        schedule(t, status)
×
1214
    else
1215
        # no owner for this req, safe to just free it
1216
        Libc.free(req)
×
1217
    end
1218
    nothing
×
1219
end
1220

1221

1222
_fd(x::IOStream) = RawFD(fd(x))
158✔
1223
_fd(x::Union{OS_HANDLE, RawFD}) = x
×
1224

1225
function _fd(x::Union{LibuvStream, LibuvServer})
2✔
1226
    fd = Ref{OS_HANDLE}(INVALID_OS_HANDLE)
44✔
1227
    if x.status != StatusUninit && x.status != StatusClosed && x.handle != C_NULL
44✔
1228
        err = ccall(:uv_fileno, Int32, (Ptr{Cvoid}, Ptr{OS_HANDLE}), x.handle, fd)
42✔
1229
        # handle errors by returning INVALID_OS_HANDLE
1230
    end
1231
    return fd[]
44✔
1232
end
1233

1234
struct RedirectStdStream <: Function
1235
    unix_fd::Int
1236
    writable::Bool
1237
end
1238
for (f, writable, unix_fd) in
1239
        ((:redirect_stdin, false, 0),
1240
         (:redirect_stdout, true, 1),
1241
         (:redirect_stderr, true, 2))
1242
    @eval const ($f) = RedirectStdStream($unix_fd, $writable)
1243
end
1244
function _redirect_io_libc(stream, unix_fd::Int)
1245
    posix_fd = _fd(stream)
190✔
1246
    @static if Sys.iswindows()
1247
        if 0 <= unix_fd <= 2
1248
            ccall(:SetStdHandle, stdcall, Int32, (Int32, OS_HANDLE),
1249
                -10 - unix_fd, Libc._get_osfhandle(posix_fd))
1250
        end
1251
    end
1252
    dup(posix_fd, RawFD(unix_fd))
190✔
1253
    nothing
173✔
1254
end
1255
function _redirect_io_global(io, unix_fd::Int)
1256
    unix_fd == 0 && (global stdin = io)
196✔
1257
    unix_fd == 1 && (global stdout = io)
196✔
1258
    unix_fd == 2 && (global stderr = io)
196✔
1259
    nothing
53✔
1260
end
1261
function (f::RedirectStdStream)(handle::Union{LibuvStream, IOStream})
38✔
1262
    _redirect_io_libc(handle, f.unix_fd)
67✔
1263
    c_sym = f.unix_fd == 0 ? cglobal(:jl_uv_stdin, Ptr{Cvoid}) :
115✔
1264
            f.unix_fd == 1 ? cglobal(:jl_uv_stdout, Ptr{Cvoid}) :
1265
            f.unix_fd == 2 ? cglobal(:jl_uv_stderr, Ptr{Cvoid}) :
1266
            C_NULL
1267
    c_sym == C_NULL || unsafe_store!(c_sym, handle.handle)
134✔
1268
    _redirect_io_global(handle, f.unix_fd)
67✔
1269
    return handle
67✔
1270
end
1271
function (f::RedirectStdStream)(::DevNull)
1272
    nulldev = @static Sys.iswindows() ? "NUL" : "/dev/null"
119✔
1273
    handle = open(nulldev, write=f.writable)
123✔
1274
    _redirect_io_libc(handle, f.unix_fd)
123✔
1275
    close(handle) # handle has been dup'ed in _redirect_io_libc
123✔
1276
    _redirect_io_global(devnull, f.unix_fd)
123✔
1277
    return devnull
123✔
1278
end
1279
function (f::RedirectStdStream)(io::AbstractPipe)
6✔
1280
    io2 = (f.writable ? pipe_writer : pipe_reader)(io)
12✔
1281
    f(io2)
7✔
1282
    _redirect_io_global(io, f.unix_fd)
6✔
1283
    return io
6✔
1284
end
1285
function (f::RedirectStdStream)(p::Pipe)
1286
    if p.in.status == StatusInit && p.out.status == StatusInit
11✔
1287
        link_pipe!(p)
11✔
1288
    end
1289
    io2 = getfield(p, f.writable ? :in : :out)
11✔
1290
    f(io2)
11✔
1291
    return p
11✔
1292
end
1293
(f::RedirectStdStream)() = f(Pipe())
11✔
1294

1295
# Deprecate these in v2 (RedirectStdStream support)
1296
iterate(p::Pipe) = (p.out, 1)
14✔
1297
iterate(p::Pipe, i::Int) = i == 1 ? (p.in, 2) : nothing
16✔
1298
getindex(p::Pipe, key::Int) = key == 1 ? p.out : key == 2 ? p.in : throw(KeyError(key))
×
1299

1300
"""
1301
    redirect_stdout([stream]) -> stream
1302

1303
Create a pipe to which all C and Julia level [`stdout`](@ref) output
1304
will be redirected. Return a stream representing the pipe ends.
1305
Data written to [`stdout`](@ref) may now be read from the `rd` end of
1306
the pipe.
1307

1308
!!! note
1309
    `stream` must be a compatible objects, such as an `IOStream`, `TTY`,
1310
    [`Pipe`](@ref), socket, or `devnull`.
1311

1312
See also [`redirect_stdio`](@ref).
1313
"""
1314
redirect_stdout
1315

1316
"""
1317
    redirect_stderr([stream]) -> stream
1318

1319
Like [`redirect_stdout`](@ref), but for [`stderr`](@ref).
1320

1321
!!! note
1322
    `stream` must be a compatible objects, such as an `IOStream`, `TTY`,
1323
    [`Pipe`](@ref), socket, or `devnull`.
1324

1325
See also [`redirect_stdio`](@ref).
1326
"""
1327
redirect_stderr
1328

1329
"""
1330
    redirect_stdin([stream]) -> stream
1331

1332
Like [`redirect_stdout`](@ref), but for [`stdin`](@ref).
1333
Note that the direction of the stream is reversed.
1334

1335
!!! note
1336
    `stream` must be a compatible objects, such as an `IOStream`, `TTY`,
1337
    [`Pipe`](@ref), socket, or `devnull`.
1338

1339
See also [`redirect_stdio`](@ref).
1340
"""
1341
redirect_stdin
1342

1343
"""
1344
    redirect_stdio(;stdin=stdin, stderr=stderr, stdout=stdout)
1345

1346
Redirect a subset of the streams `stdin`, `stderr`, `stdout`.
1347
Each argument must be an `IOStream`, `TTY`, [`Pipe`](@ref), socket, or
1348
`devnull`.
1349

1350
!!! compat "Julia 1.7"
1351
    `redirect_stdio` requires Julia 1.7 or later.
1352
"""
1353
function redirect_stdio(;stdin=nothing, stderr=nothing, stdout=nothing)
18✔
1354
    stdin  === nothing || redirect_stdin(stdin)
13✔
1355
    stderr === nothing || redirect_stderr(stderr)
16✔
1356
    stdout === nothing || redirect_stdout(stdout)
12✔
1357
end
1358

1359
"""
1360
    redirect_stdio(f; stdin=nothing, stderr=nothing, stdout=nothing)
1361

1362
Redirect a subset of the streams `stdin`, `stderr`, `stdout`,
1363
call `f()` and restore each stream.
1364

1365
Possible values for each stream are:
1366
* `nothing` indicating the stream should not be redirected.
1367
* `path::AbstractString` redirecting the stream to the file at `path`.
1368
* `io` an `IOStream`, `TTY`, [`Pipe`](@ref), socket, or `devnull`.
1369

1370
# Examples
1371
```julia-repl
1372
julia> redirect_stdio(stdout="stdout.txt", stderr="stderr.txt") do
1373
           print("hello stdout")
1374
           print(stderr, "hello stderr")
1375
       end
1376

1377
julia> read("stdout.txt", String)
1378
"hello stdout"
1379

1380
julia> read("stderr.txt", String)
1381
"hello stderr"
1382
```
1383

1384
# Edge cases
1385

1386
It is possible to pass the same argument to `stdout` and `stderr`:
1387
```julia-repl
1388
julia> redirect_stdio(stdout="log.txt", stderr="log.txt", stdin=devnull) do
1389
    ...
1390
end
1391
```
1392

1393
However it is not supported to pass two distinct descriptors of the same file.
1394
```julia-repl
1395
julia> io1 = open("same/path", "w")
1396

1397
julia> io2 = open("same/path", "w")
1398

1399
julia> redirect_stdio(f, stdout=io1, stderr=io2) # not supported
1400
```
1401
Also the `stdin` argument may not be the same descriptor as `stdout` or `stderr`.
1402
```julia-repl
1403
julia> io = open(...)
1404

1405
julia> redirect_stdio(f, stdout=io, stdin=io) # not supported
1406
```
1407

1408
!!! compat "Julia 1.7"
1409
    `redirect_stdio` requires Julia 1.7 or later.
1410
"""
1411
function redirect_stdio(f; stdin=nothing, stderr=nothing, stdout=nothing)
18✔
1412

1413
    function resolve(new::Nothing, oldstream, mode)
9✔
1414
        (new=nothing, close=false, old=nothing)
7✔
1415
    end
1416
    function resolve(path::AbstractString, oldstream,mode)
1417
        (new=open(path, mode), close=true, old=oldstream)
7✔
1418
    end
1419
    function resolve(new, oldstream, mode)
1420
        (new=new, close=false, old=oldstream)
3✔
1421
    end
1422

1423
    same_path(x, y) = false
9✔
1424
    function same_path(x::AbstractString, y::AbstractString)
1425
        # if x = y = "does_not_yet_exist.txt" then samefile will return false
1426
        (abspath(x) == abspath(y)) || samefile(x,y)
8✔
1427
    end
1428
    if same_path(stderr, stdin)
9✔
1429
        throw(ArgumentError("stdin and stderr cannot be the same path"))
2✔
1430
    end
1431
    if same_path(stdout, stdin)
7✔
1432
        throw(ArgumentError("stdin and stdout cannot be the same path"))
1✔
1433
    end
1434

1435
    new_in , close_in , old_in  = resolve(stdin , Base.stdin , "r")
6✔
1436
    new_out, close_out, old_out = resolve(stdout, Base.stdout, "w")
6✔
1437
    if same_path(stderr, stdout)
8✔
1438
        # make sure that in case stderr = stdout = "same/path"
1439
        # only a single io is used instead of opening the same file twice
1440
        new_err, close_err, old_err = new_out, false, Base.stderr
1✔
1441
    else
1442
        new_err, close_err, old_err = resolve(stderr, Base.stderr, "w")
5✔
1443
    end
1444

1445
    redirect_stdio(; stderr=new_err, stdin=new_in, stdout=new_out)
7✔
1446

1447
    try
6✔
1448
        return f()
6✔
1449
    finally
1450
        redirect_stdio(;stderr=old_err, stdin=old_in, stdout=old_out)
6✔
1451
        close_err && close(new_err)
6✔
1452
        close_in  && close(new_in )
6✔
1453
        close_out && close(new_out)
6✔
1454
    end
1455
end
1456

1457
function (f::RedirectStdStream)(thunk::Function, stream)
19✔
1458
    stdold = f.unix_fd == 0 ? stdin :
36✔
1459
             f.unix_fd == 1 ? stdout :
1460
             f.unix_fd == 2 ? stderr :
1461
             throw(ArgumentError("Not implemented to get old handle of fd except for stdio"))
1462
    f(stream)
32✔
1463
    try
19✔
1464
        return thunk()
20✔
1465
    finally
1466
        f(stdold)
19✔
1467
    end
1468
end
1469

1470

1471
"""
1472
    redirect_stdout(f::Function, stream)
1473

1474
Run the function `f` while redirecting [`stdout`](@ref) to `stream`.
1475
Upon completion, [`stdout`](@ref) is restored to its prior setting.
1476
"""
1477
redirect_stdout(f::Function, stream)
1478

1479
"""
1480
    redirect_stderr(f::Function, stream)
1481

1482
Run the function `f` while redirecting [`stderr`](@ref) to `stream`.
1483
Upon completion, [`stderr`](@ref) is restored to its prior setting.
1484
"""
1485
redirect_stderr(f::Function, stream)
1486

1487
"""
1488
    redirect_stdin(f::Function, stream)
1489

1490
Run the function `f` while redirecting [`stdin`](@ref) to `stream`.
1491
Upon completion, [`stdin`](@ref) is restored to its prior setting.
1492
"""
1493
redirect_stdin(f::Function, stream)
1494

1495
mark(x::LibuvStream)     = mark(x.buffer)
3,004✔
1496
unmark(x::LibuvStream)   = unmark(x.buffer)
2✔
1497
reset(x::LibuvStream)    = reset(x.buffer)
3,004✔
1498
ismarked(x::LibuvStream) = ismarked(x.buffer)
4✔
1499

1500
function peek(s::LibuvStream, ::Type{T}) where T
3,002✔
1501
    mark(s)
3,002✔
1502
    try read(s, T)
3,002✔
1503
    finally
1504
        reset(s)
3,002✔
1505
    end
1506
end
1507

1508
# BufferStream's are non-OS streams, backed by a regular IOBuffer
1509
mutable struct BufferStream <: LibuvStream
1510
    buffer::IOBuffer
1511
    cond::Threads.Condition
1512
    readerror::Any
1513
    buffer_writes::Bool
1514
    lock::ReentrantLock # advisory lock
1515
    status::Int
1516

1517
    BufferStream() = new(PipeBuffer(), Threads.Condition(), nothing, false, ReentrantLock(), StatusActive)
52✔
1518
end
1519

1520
isopen(s::BufferStream) = s.status != StatusClosed
1✔
1521

1522
closewrite(s::BufferStream) = close(s)
1✔
1523

1524
function close(s::BufferStream)
1525
    lock(s.cond) do
1✔
1526
        s.status = StatusClosed
1527
        notify(s.cond) # aka flush
1528
        nothing
1529
    end
1530
end
1531
uvfinalize(s::BufferStream) = nothing
×
1532
setup_stdio(stream::BufferStream, child_readable::Bool) = invoke(setup_stdio, Tuple{IO, Bool}, stream, child_readable)
×
1533

1534
function read(s::BufferStream, ::Type{UInt8})
1535
    nread = lock(s.cond) do
10,600✔
1536
        wait_readnb(s, 1)
1537
        read(s.buffer, UInt8)
1538
    end
1539
    return nread
×
1540
end
1541
function unsafe_read(s::BufferStream, a::Ptr{UInt8}, nb::UInt)
1542
    lock(s.cond) do
1✔
1543
        wait_readnb(s, Int(nb))
1544
        unsafe_read(s.buffer, a, nb)
1545
        nothing
1546
    end
1547
end
1548
bytesavailable(s::BufferStream) = bytesavailable(s.buffer)
10,549✔
1549

1550
isreadable(s::BufferStream) = (isopen(s) || bytesavailable(s) > 0) && s.buffer.readable
×
1551
iswritable(s::BufferStream) = isopen(s) && s.buffer.writable
×
1552

1553
function wait_readnb(s::BufferStream, nb::Int)
×
1554
    lock(s.cond) do
×
1555
        while isopen(s) && bytesavailable(s.buffer) < nb
×
1556
            wait(s.cond)
×
1557
        end
×
1558
    end
1559
end
1560

1561
function readavailable(this::BufferStream)
×
1562
    bytes = lock(this.cond) do
×
1563
        wait_readnb(this, 1)
×
1564
        buf = this.buffer
×
1565
        @assert buf.seekable == false
×
1566
        take!(buf)
×
1567
    end
1568
    return bytes
×
1569
end
1570

1571
function read(stream::BufferStream)
1572
    bytes = lock(stream.cond) do
41✔
1573
        wait_close(stream)
1574
        take!(stream.buffer)
1575
    end
1576
    return bytes
×
1577
end
1578

1579
function readbytes!(s::BufferStream, a::Vector{UInt8}, nb::Int)
1580
    sbuf = s.buffer
1✔
1581
    @assert sbuf.seekable == false
1✔
1582
    @assert sbuf.maxsize >= nb
1✔
1583

1584
    function wait_locked(s, buf, nb)
×
1585
        while bytesavailable(buf) < nb
1586
            s.readerror === nothing || throw(s.readerror)
1587
            isopen(s) || break
1588
            s.status != StatusEOF || break
1589
            wait_readnb(s, nb)
1590
        end
1591
    end
1592

1593
    bytes = lock(s.cond) do
1✔
1594
        if nb <= SZ_UNBUFFERED_IO # Under this limit we are OK with copying the array from the stream's buffer
1595
            wait_locked(s, sbuf, nb)
1596
        end
1597
        if bytesavailable(sbuf) >= nb
1598
            nread = readbytes!(sbuf, a, nb)
1599
        else
1600
            initsize = length(a)
1601
            newbuf = _truncated_pipebuffer(a; maxsize=nb)
1602
            nread = try
1603
                s.buffer = newbuf
1604
                write(newbuf, sbuf)
1605
                wait_locked(s, newbuf, nb)
1606
                bytesavailable(newbuf)
1607
            finally
1608
                s.buffer = sbuf
1609
            end
1610
            _take!(a, _unsafe_take!(newbuf))
1611
            length(a) >= initsize || resize!(a, initsize)
1612
        end
1613
        return nread
1614
    end
1615
    return bytes
1✔
1616
end
1617

1618
show(io::IO, s::BufferStream) = print(io, "BufferStream(bytes waiting=", bytesavailable(s.buffer), ", isopen=", isopen(s), ")")
1✔
1619

1620
function readuntil(s::BufferStream, c::UInt8; keep::Bool=false)
×
1621
    bytes = lock(s.cond) do
×
1622
        while isopen(s) && !occursin(c, s.buffer)
×
1623
            wait(s.cond)
×
1624
        end
×
1625
        readuntil(s.buffer, c, keep=keep)
×
1626
    end
1627
    return bytes
×
1628
end
1629

1630
function wait_close(s::BufferStream)
×
1631
    lock(s.cond) do
×
1632
        while isopen(s)
×
1633
            wait(s.cond)
×
1634
        end
×
1635
    end
1636
end
1637

1638
start_reading(s::BufferStream) = Int32(0)
×
1639
stop_reading(s::BufferStream) = nothing
×
1640

1641
write(s::BufferStream, b::UInt8) = write(s, Ref{UInt8}(b))
×
1642
function unsafe_write(s::BufferStream, p::Ptr{UInt8}, nb::UInt)
1643
    nwrite = lock(s.cond) do
229✔
1644
        check_open(s)
1645
        rv = unsafe_write(s.buffer, p, nb)
1646
        s.buffer_writes || notify(s.cond)
1647
        rv
1648
    end
1649
    return nwrite
×
1650
end
1651

1652
function eof(s::BufferStream)
1653
    bytesavailable(s) > 0 && return false
10,493✔
1654
    iseof = lock(s.cond) do
59✔
1655
        wait_readnb(s, 1)
1656
        return !isopen(s) && bytesavailable(s) <= 0
1657
    end
1658
    return iseof
59✔
1659
end
1660

1661
# If buffer_writes is called, it will delay notifying waiters till a flush is called.
1662
buffer_writes(s::BufferStream, bufsize=0) = (s.buffer_writes = true; s)
×
1663
function flush(s::BufferStream)
×
1664
    lock(s.cond) do
×
1665
        check_open(s)
×
1666
        notify(s.cond)
×
1667
        nothing
×
1668
    end
1669
end
1670

1671
skip(s::BufferStream, n) = skip(s.buffer, n)
×
1672

1673
function reseteof(s::BufferStream)
×
1674
    lock(s.cond) do
×
1675
        s.status = StatusOpen
×
1676
        nothing
×
1677
    end
1678
    nothing
×
1679
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc