# Low-level IO with libuv

**URL:** https://discourse.julialang.org/t/low-level-io-with-libuv/3432
**Category:** Internals & Design
**Created:** [April 29, 2017, 1:57pm UTC](https://discourse.julialang.org/t/low-level-io-with-libuv/3432 "2017-04-29T13:57:26Z")
**Posts on this page:** 5
**Page:** 1

<div class="post-metadata">

### Author: ![wavexx](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/wavexx/32/2197_2.png) [@wavexx](https://discourse.julialang.org/u/wavexx)
#### Post date: [April 29, 2017, 1:57pm UTC](https://discourse.julialang.org/t/low-level-io-with-libuv/3432/1 "2017-04-29T13:57:26Z")

</div>

Hi, I’m working on the Expect.jl module to interact with external  
processes under a pty ([https://github.com/wavexx/Expect.jl](https://github.com/wavexx/Expect.jl)).

For those unfamiliar with expect-like modules, the current API looks  
like this:

```julia
  interact(`ssh somewhere`, 10) do ssh
      expect!(ssh, " password: ")
      println(ssh, "supersecret")
      ...
  end

```

I need to have fine-grained control on how buffering is performed at a  
lower level and I think I’ve reached a limit on what I can do using the  
Julia runtime and need some guidance.

I’ll start with the easiest issue:

Julia’s Base.TTY seem to be designed only as a front-end to the main  
terminal interacting with the user. This seems to stem from libuv as  
well. For one, libuv tracks the first tty on which the uv\_tty\_set\_mode  
has been called, just to reset it on shutdown.

[https://github.com/libuv/libuv/issues/1292](https://github.com/libuv/libuv/issues/1292)

This obviously doesn’t take pseudo-ttys into consideration. Julia itself  
_also_ calls uv\_tty\_set\_mode in jl\_close\_uv!

This seems broken, as if we use libuv, we should _only_ be calling  
uv\_tty\_reset\_mode before exiting (which is intended to be called from a  
signal handler) and ensure the tty reset by libuv itself is the console  
(again, this can only be done currently by calling uv\_tty\_set\_mode with  
a non-normal mode at start! - another issue with the libuv api design).  
Getting stuff fixed in libuv takes forever, and even more to propagate  
the change to julia, so this is why I’m not directly filling PRs and  
issues right away.

I see the api in libuv broken in this regard: there should be a function  
call to explicitly set the tty to reset on un\_tty\_reset\_mode.

The second issue is reading. Reads from LibuvStream are throttled by  
julia, however I need to ensure the input stream coming from the  
coprocess is never stopped, otherwise I can incur in a stall in a  
pending write. Looking at Base.wait\_readnb I see no easy way to avoid  
the call to stop\_reading. Ideas?

I’d love to be able to re-use the code in stream.jl, but the handling of  
the read timeout is also something I want to manage directly. Is there a  
“recommended” way to create a new handle directly using libuv? I need to  
cram the descriptor as being watched in the main uv loop and set my own  
callbacks.

Thanks for any pointer.

---

<div class="post-metadata">

### Author: ![jameson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jameson/32/23_2.png) [@jameson](https://discourse.julialang.org/u/jameson)
#### Post date: [May 1, 2017, 5:34am UTC](https://discourse.julialang.org/t/low-level-io-with-libuv/3432/2 "2017-05-01T05:34:37Z")

</div>

> [@wavexx](#):
>
> Julia’s Base.TTY seem to be designed only as a front-end to the main  
> terminal interacting with the user

This is correct. In particular, TTY is really most representative of a TTY slave. For a TTY master, it’s perhaps closer to open this as a PipeEndpoint object on the Julia side. This can also help with avoid some of the libuv bugs surrounding its tty support (although you don’t need to use the same type for the Julia and libuv representation).

> [@wavexx](#):
>
> Getting stuff fixed in libuv takes forever, and even more to propagate  
> the change to julia, so this is why I’m not directly filling PRs and  
> issues right away.

You should file duplicate issues against Julia’s libuv fork.

> [@wavexx](#):
>
> I see the api in libuv broken in this regard: there should be a function  
> call to explicitly set the tty to reset on un\_tty\_reset\_mode.

Yeah, this is a libuv bug. AFAICT, they/their users must not be using external processes / tty redirection much, since this use case is why we have a fork. We’ve tried to work around this by calling `uv_tty_set_mode` from `julia_init` on all (tty) file descriptors, which has been fairly successful at hiding it. But I agree we should fix it to detect / record exactly the case where we called it on `stdin fd(0)` during init, and otherwise not reset the tty mode.  
New issue opened: [improve uv\_tty\_reset\_mode logic · Issue #21659 · JuliaLang/julia · GitHub](https://github.com/JuliaLang/julia/issues/21659)

> [@wavexx](#):
>
> Reads from LibuvStream are throttled by  
> julia, however I need to ensure the input stream coming from the  
> coprocess is never stopped

They aren’t stopped. They are throttled by your code whenever you don’t have a buffer assigned for reading into. Run your IO handler in an `@async` task, and it won’t get throttled.

> [@wavexx](#):
>
> but the handling of  
> the read timeout is also something I want to manage directly.

There is no read timeout. It is strongly discouraged to add one as this would cause race conditions in your code. There is either data or there is not (`eof` / `readavailable`).

---

<div class="post-metadata">

### Author: ![wavexx](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/wavexx/32/2197_2.png) [@wavexx](https://discourse.julialang.org/u/wavexx)
#### Post date: [May 6, 2017, 4:24pm UTC](https://discourse.julialang.org/t/low-level-io-with-libuv/3432/3 "2017-05-06T16:24:29Z")

</div>

> [@jameson](#):
>
> This is correct. In particular, TTY is really most representative of a TTY slave. For a TTY master, it’s perhaps closer to open this as a PipeEndpoint object on the Julia side. This can also help with avoid some of the libuv bugs surrounding its tty support (although you don’t need to use the same type for the Julia and libuv representation).

Is there a way to construct a PipeEndpoint manually from a RawFD that I don’t see?

> [@](#):
>
> They aren’t stopped. They are throttled by your code whenever you don’t have a buffer assigned for reading into. Run your IO handler in an `@async` task, and it won’t get throttled.

I want to consume into a buffer indefinitely, even when there’s no explicit reader. Adding something like `@async while true wait(fd)` will do what I need, but is not a solution I particularly endorse.

> [@](#):
>
> There is no read timeout. It is strongly discouraged to add one as this would cause race conditions in your code. There is either data or there is not (`eof` / `readavailable`).

I know there’s no read timeout in libuv. I want to generate timeouts at a higher level. This is a fundamental property of “expect”. But to do so correctly I want to use libuv callbacks directly, as most of the julia runtime makes too many assumptions in how streams are handled.

The question is what would be the best way to register my own descriptors (along with custom read/write callbacks) into the main libuv loop.

---

<div class="post-metadata">

### Author: ![jameson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jameson/32/23_2.png) [@jameson](https://discourse.julialang.org/u/jameson)
#### Post date: [May 8, 2017, 4:01pm UTC](https://discourse.julialang.org/t/low-level-io-with-libuv/3432/4 "2017-05-08T16:01:07Z")

</div>

> I want to consume into a buffer indefinitely, even when there’s no explicit reader.

A reader is a buffer. You can’t consume into a buffer, even when there is no buffer.

> But to do so correctly I want to use libuv callbacks directly, as most of the julia runtime makes too many assumptions in how streams are handled.

I think you’re making too many assumptions - Julia’s runtime is pretty much a plain wrapper around the libuv async primitives.

> I want to generate timeouts at a higher level.

Sure, we have some examples of this in Base already, such as timeouts on starting `addprocs` or in the test code. Usually this is done by starting a Timer, and if it expires, have it `close` the client handle, as that is the most reliable way to avoid race conditions (for unreliable connections). Although, if you’re connecting over a socket, you can usually just ask the kernel to handle this for you by configuring the SO\_KEEPALIVE (either directly with setsockopt, or per application, in your .ssh/config file for instance).

> Is there a way to construct a PipeEndpoint manually from a RawFD that I don’t see?

You need to make an uninitialized `p = PipeEndpoint()`, open it with a `ccall to jl_init_pipe`, then associate it with the fd handle (`ccall to uv_pipe_open`)

---

<div class="post-metadata">

### Author: ![wavexx](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/wavexx/32/2197_2.png) [@wavexx](https://discourse.julialang.org/u/wavexx)
#### Post date: [May 12, 2017, 11:12am UTC](https://discourse.julialang.org/t/low-level-io-with-libuv/3432/5 "2017-05-12T11:12:10Z")

</div>

> > But to do so correctly I want to use libuv callbacks directly, as  
> > most of the julia runtime makes too many assumptions in how streams  
> > are handled.
> 
> I think you’re making too many assumptions - Julia’s runtime is pretty  
> much a plain wrapper around the libuv async primitives.

I’m talking about LibuvStream/s (PipeEndpoint or TTY are pretty similar  
in that regard). They do have an internal buffer, which is managed by  
the collection of stream.jl functions. uv callbacks are enabled/disabled  
depending on whether there’s someone on the notify queue. Am I  
interpreting stream.jl wrong here?

> unreliable connections). Although, if you’re connecting over a socket,  
> you can usually just ask the kernel to handle this for you by  
> configuring the SO\_KEEPALIVE (either directly with setsockopt, or per  
> application, in your .ssh/config file for instance).

This is unrelated to the actual connection status of the stream. Local  
pipes are always reliable. These timeouts are an higher-level  
abstraction to manage an unknown stream protocol where the speed of the  
data rate might be relevant.

> You need to make an uninitialized `p = PipeEndpoint()`, open it with a  
> `ccall to jl_init_pipe`, then associate it with the fd handle (`ccall to uv_pipe_open`)

I’ll have a look.
