-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
JL_STD* vs ios_std* cleanup #9450
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -227,8 +227,6 @@ convert(T::Type{Ptr{Void}}, s::AsyncStream) = convert(T, s.handle) | |
handle(s::AsyncStream) = s.handle | ||
handle(s::Ptr{Void}) = s | ||
|
||
make_stdout_stream() = _uv_tty2tty(ccall(:jl_stdout_stream, Ptr{Void}, ())) | ||
|
||
associate_julia_struct(handle::Ptr{Void},jlobj::ANY) = | ||
ccall(:jl_uv_associate_julia_struct,Void,(Ptr{Void},Any),handle,jlobj) | ||
disassociate_julia_struct(uv) = disassociate_julia_struct(uv.handle) | ||
|
@@ -239,6 +237,8 @@ function init_stdio(handle) | |
t = ccall(:jl_uv_handle_type,Int32,(Ptr{Void},),handle) | ||
if t == UV_FILE | ||
return fdio(ccall(:jl_uv_file_handle,Int32,(Ptr{Void},),handle)) | ||
# Replace ios.c filw with libuv file? | ||
# return File(RawFD(ccall(:jl_uv_file_handle,Int32,(Ptr{Void},),handle))) | ||
else | ||
if t == UV_TTY | ||
ret = TTY(handle) | ||
|
@@ -272,7 +272,6 @@ function reinit_stdio() | |
global uv_jl_readcb = cglobal(:jl_uv_readcb) | ||
global uv_jl_connectioncb = cglobal(:jl_uv_connectioncb) | ||
global uv_jl_connectcb = cglobal(:jl_uv_connectcb) | ||
global uv_jl_writecb = cglobal(:jl_uv_writecb) | ||
global uv_jl_writecb_task = cglobal(:jl_uv_writecb_task) | ||
global uv_eventloop = ccall(:jl_global_event_loop, Ptr{Void}, ()) | ||
global STDIN = init_stdio(ccall(:jl_stdin_stream ,Ptr{Void},())) | ||
|
@@ -726,93 +725,52 @@ end | |
# finish_read(state...) | ||
#end | ||
|
||
macro uv_write(n,call) | ||
esc(quote | ||
check_open(s) | ||
uvw = c_malloc(_sizeof_uv_write+$(n)) | ||
err = $call | ||
function uv_write(s::AsyncStream, p, n::Integer) | ||
check_open(s) | ||
uvw = c_malloc(_sizeof_uv_write) | ||
try | ||
uv_req_set_data(uvw,C_NULL) | ||
err = ccall(:jl_uv_write, | ||
Int32, | ||
(Ptr{Void}, Ptr{Void}, UInt, Ptr{Void}, Ptr{Void}), | ||
handle(s), p, n, uvw, | ||
uv_jl_writecb_task::Ptr{Void}) | ||
if err < 0 | ||
c_free(uvw) | ||
uv_error("write", err) | ||
end | ||
end) | ||
end | ||
|
||
## low-level calls ## | ||
|
||
function write!{T}(s::AsyncStream, a::Array{T}) | ||
if isbits(T) | ||
n = uint(length(a)*sizeof(T)) | ||
@uv_write n ccall(:jl_write_no_copy, Int32, (Ptr{Void}, Ptr{Void}, UInt, Ptr{Void}, Ptr{Void}), handle(s), a, n, uvw, uv_jl_writecb::Ptr{Void}) | ||
return int(length(a)*sizeof(T)) | ||
else | ||
throw(MethodError(write,(s,a))) | ||
ct = current_task() | ||
uv_req_set_data(uvw,ct) | ||
ct.state = :waiting | ||
stream_wait(ct) | ||
finally | ||
c_free(uvw) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this needs a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. although this seems to be less InterruptException-safe than it was before. perhaps also need to move the JL_SIGATOMIC into this function, since the code it was protecting also moved here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is still JL_SIGATOMIC in C code (in jl_uv_write()) around the call to uv_write(). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. mostly the possible |
||
end | ||
return n | ||
end | ||
function write!(s::AsyncStream, p::Ptr, nb::Integer) | ||
@uv_write nb ccall(:jl_write_no_copy, Int32, (Ptr{Void}, Ptr{Void}, UInt, Ptr{Void}, Ptr{Void}), handle(s), p, nb, uvw, uv_jl_writecb::Ptr{Void}) | ||
return nb | ||
end | ||
write!(s::AsyncStream, string::ByteString) = write!(s,string.data) | ||
|
||
function _uv_hook_writecb(s::AsyncStream, req::Ptr{Void}, status::Int32) | ||
if status < 0 | ||
err = UVError("write",status) | ||
showerror(STDERR, err, backtrace()) | ||
end | ||
nothing | ||
end | ||
## low-level calls ## | ||
|
||
function write(s::AsyncStream, b::UInt8) | ||
@uv_write 1 ccall(:jl_putc_copy, Int32, (UInt8, Ptr{Void}, Ptr{Void}, Ptr{Void}), b, handle(s), uvw, uv_jl_writecb_task::Ptr{Void}) | ||
ct = current_task() | ||
uv_req_set_data(uvw,ct) | ||
ct.state = :waiting | ||
stream_wait(ct) | ||
return 1 | ||
end | ||
function write(s::AsyncStream, c::Char) | ||
nb = utf8sizeof(c) | ||
@uv_write nb ccall(:jl_pututf8_copy, Int32, (Ptr{Void}, UInt32, Ptr{Void}, Ptr{Void}), handle(s), c, uvw, uv_jl_writecb_task::Ptr{Void}) | ||
ct = current_task() | ||
uv_req_set_data(uvw,ct) | ||
ct.state = :waiting | ||
stream_wait(ct) | ||
return nb | ||
end | ||
write(s::AsyncStream, b::UInt8) = write(s, [b]) | ||
write(s::AsyncStream, c::Char) = write(s, string(c)) | ||
function write{T}(s::AsyncStream, a::Array{T}) | ||
if isbits(T) | ||
n = uint(length(a)*sizeof(T)) | ||
@uv_write n ccall(:jl_write_no_copy, Int32, (Ptr{Void}, Ptr{Void}, UInt, Ptr{Void}, Ptr{Void}), handle(s), a, n, uvw, uv_jl_writecb_task::Ptr{Void}) | ||
ct = current_task() | ||
uv_req_set_data(uvw,ct) | ||
ct.state = :waiting | ||
stream_wait(ct) | ||
return int(length(a)*sizeof(T)) | ||
return uv_write(s, a, n); | ||
else | ||
check_open(s) | ||
invoke(write,(IO,Array),s,a) | ||
end | ||
end | ||
function write(s::AsyncStream, p::Ptr, nb::Integer) | ||
@uv_write nb ccall(:jl_write_no_copy, Int32, (Ptr{Void}, Ptr{Void}, UInt, Ptr{Void}, Ptr{Void}), handle(s), p, nb, uvw, uv_jl_writecb_task::Ptr{Void}) | ||
ct = current_task() | ||
uv_req_set_data(uvw,ct) | ||
ct.state = :waiting | ||
stream_wait(ct) | ||
return int(nb) | ||
end | ||
|
||
write(s::AsyncStream, p::Ptr, n::Integer) = uv_write(s, p, n) | ||
|
||
function _uv_hook_writecb_task(s::AsyncStream,req::Ptr{Void},status::Int32) | ||
d = uv_req_data(req) | ||
@assert d != C_NULL | ||
if status < 0 | ||
err = UVError("write",status) | ||
if d != C_NULL | ||
schedule(unsafe_pointer_to_objref(d)::Task,err,error=true) | ||
else | ||
showerror(STDERR, err, backtrace()) | ||
end | ||
elseif d != C_NULL | ||
schedule(unsafe_pointer_to_objref(d)::Task,err,error=true) | ||
else | ||
schedule(unsafe_pointer_to_objref(d)::Task) | ||
end | ||
end | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,3 +14,4 @@ | |
subarrays | ||
sysimg | ||
llvm | ||
stdio |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
*************************************** | ||
printf() and stdio in the Julia runtime | ||
*************************************** | ||
|
||
Libuv wrappers for stdio | ||
------------------------ | ||
|
||
julia.h defines `libuv <http://docs.libuv.org>`_ wrappers for the | ||
<stdio.h> streams:: | ||
|
||
uv_stream_t *JL_STDIN; | ||
uv_stream_t *JL_STDOUT; | ||
uv_stream_t *JL_STDERR; | ||
|
||
... and corresponding output functions:: | ||
|
||
int jl_printf(uv_stream_t *s, const char *format, ...); | ||
int jl_vprintf(uv_stream_t *s, const char *format, va_list args); | ||
|
||
These printf functions are used by :code:`julia/{src,ui}/*.c` wherever stdio | ||
is needed to ensure that output buffering is handled in a unified | ||
way. | ||
|
||
In special cases, like signal handlers, where the full libuv | ||
infrastructure is too heavy, :func:`jl_safe_printf` can be used to | ||
:code:`write(2)` directly to :data:`STDERR_FILENO`:: | ||
|
||
void jl_safe_printf(const char \*str, ...); | ||
|
||
|
||
|
||
Interface between JL_STD* and Julia code | ||
---------------------------------------- | ||
|
||
:data:`Base.STDIN`, :data:`Base.STDOUT` and :data:`Base.STDERR` are | ||
bound to the :code:`JL_STD*` `libuv <http://docs.libuv.org>`_ streams | ||
defined in the runtime. | ||
|
||
Julia's :func:`__init__` function (inbase/sysimg.jl) calls | ||
:func:`reinit_stdio` (in base/stream.jl) to create Julia objects | ||
for :data:`Base.STDIN`, :data:`Base.STDOUT` and :data:`Base.STDERR`. | ||
|
||
:func:`reinit_stdio` uses :func:`ccall` to retrieve pointers to | ||
:code:`JL_STD*` and calls :func:`jl_uv_handle_type()` to inspect | ||
the type of each stream. It then creates a Julia :code:`Base.File`, | ||
:code:`Base.TTY` or :code:`Base.Pipe` object to represent each | ||
stream. e.g:: | ||
|
||
$ julia -e 'typeof((STDIN, STDOUT, STDERR))' | ||
(TTY,TTY,TTY) | ||
|
||
$ julia -e 'println(typeof((STDIN, STDOUT, STDERR)))' < /dev/null 2>/dev/null | ||
(Base.FS.File,TTY,Base.FS.File) | ||
|
||
$ echo hello | julia -e 'println(typeof((STDIN, STDOUT, STDERR)))' | cat | ||
(Pipe,Pipe,TTY) | ||
|
||
The :func:`Base.read()` and :func:`Base.write()` methods for these | ||
streams use :func:`ccall` to call libuv wrappers in :code:`src/jl_uv.c`. e.g:: | ||
|
||
stream.jl: function write(s::AsyncStream, p::Ptr, nb::Integer) | ||
-> ccall(:jl_write_no_copy, ...) | ||
jl_uv.c: -> int jl_write_no_copy(uv_stream_t *stream, ...) | ||
-> uv_write(uvw, stream, buf, ...) | ||
|
||
printf() during initialisation | ||
------------------------------ | ||
|
||
The libuv streams relied apon by :func:`jl_printf` etc are not | ||
available until mid-way through initialisation of the runtime (see | ||
init.c, :func:`init_stdio`). Error messages or warnings that need | ||
to be printed before this are routed to the standard C library | ||
:func:`fwrite` function by the following mechanism: | ||
|
||
In sys.c the :code:`JL_STD*` stream pointers are statically initialised | ||
to integer constants: STD*_FILENO (0, 1 and 2). In jl_uv.c the | ||
:func:`jl_write` function checks its :code:`uv_stream_t* stream` | ||
argument and calls :func:`fwrite` if stream is set to STDOUT_FILENO | ||
or STDERR_FILENO. | ||
|
||
This allows for uniform use of :func:`jl_printf()` throughout the | ||
runtime regardless of whether or not any particular piece of code | ||
is reachable before initialisation is complete. | ||
|
||
|
||
|
||
Legacy ios.c library | ||
-------------------- | ||
|
||
The :code:`julia/src/support/ios.c` library is inherited from `femptolisp <http://github.com/JeffBezanson/femtolisp>`_. | ||
It provides cross-platform buffered file IO and in-memory temporary buffers. | ||
|
||
:code:`ios.c` is still used by: | ||
|
||
- :code:`julia/src/flisp/*.c` | ||
- :code:`julia/src/dump.c` -- for serialisation file IO and for memory buffers. | ||
- :code:`base/iostream.jl` -- for file IO (see :code:`base/fs.jl` for libuv equivalent). | ||
|
||
Use of :code:`ios.c` in these modules is mostly self contained and | ||
seperated from the libuv io system. However, there is `one place | ||
<http://github.com/JuliaLang/julia/blob/master/src/flisp/print.c#L654>`_ | ||
where femptolisp calls though to :func:`jl_printf` with a legacy :code:`ios_t` stream. | ||
|
||
There is a hack in :code:`ios.h` that makes the :code:`ios_t.bm` | ||
field line up with the :code:`uv_stream_t.type` and ensures that | ||
the values used for :code:`ios_t.bm` to not overlap with valid | ||
UV_HANDLE_TYPE values. This allows :code:`uv_stream_t` pointers | ||
to point to :code:`ios_t` streams. | ||
|
||
This is needed because :func`jl_printf` caller :func`jl_static_show` | ||
is passed an :code:`ios_t` stream by femptolisp's :func:`fl_print` function. | ||
Julia's :func:`jl_write` function has special handling for this:: | ||
|
||
if (stream->type > UV_HANDLE_TYPE_MAX) { | ||
return ios_write((ios_t*)stream, str, n); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would worry about the performance of allocating an array here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could be handled by
jl_fs_write
, passing&c
as the data.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would argue that this is not the right layer to deal with performance of single-byte writes.
The OS implementation of the single-byte write involves a sys-call, maybe a network stack, etc... it is expensive.
If the caller needs to efficiently write lots of single bytes, they should write to a buffer interface that wraps the raw io interface.
Is there a particular use-pattern in current Julia code where single character writes to File are performance critical?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@JeffBezanson you have to be careful there, since we could execute a task switch and invalid the stack slot before it gets written out
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @vtjnash,
Sorry, I'm afraid I don't quite understand.
Are you saying that the user of a buffered interface might write a character that does not get sent because it is left in the buffer? ... in that case they should call buffer.flush(). No?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that was addressed to Jeff's comment. just bad timing as we both typed replies at the same time