JEG2

JEG2

Author of Designing Elixir Systems with OTP

How do I read available characters?

While working on the ICFP Contest this weekend, I struggled to find a good way to periodically read content not delimited by newlines. Let me show some examples of what I mean.

Here’s a Ruby script that produces messages:

$stdout.sync = true
loop do
  message = rand(1_000_000)
  $stdout.write "<#{message}>"
  sleep rand(3) + 1
end

I can think of multiple ways, using Ruby, to read these messages as they come in. For example, I can use non-blocking I/O:

loop do
  begin
    raw = $stdin.readpartial(1_024)
    puts raw[/\d+/]
  rescue EOFError
    sleep 0.1
  end
end

Or I can read what’s available:

require "io/wait"

loop do
  $stdin.wait_readable
  raw = $stdin.read($stdin.nread)
  puts raw[/\d+/]
end

There are other options too, like using IO::select(). Here’s how the above examples work in practice:

$ ruby producer.rb | ruby read_nonblocking.rb 
319187
122221
30420
…
$ ruby producer.rb | ruby read_ready.rb 
640243
971582
366808
…

I haven’t found a good way to do similar work with Elixir. The best I’ve come up with for the same input is to read character by character:

defmodule MessageReader do
  def read_message(device, buffer \\ "") do
    new_buffer = buffer <> IO.read(device, 1)
    if String.first(new_buffer) == "<" and String.last(new_buffer) == ">" do
      String.slice(new_buffer, 1..-2)
    else
      read_message(device, new_buffer)
    end
  end

  def read_messages(device, handler) do
    read_message(device)
    |> handler.()
    read_messages(device, handler)
  end
end

MessageReader.read_messages(:stdio, &IO.puts/1)

This does work:

$ ruby producer.rb | elixir read_chars.exs  
963609
378034
387827
…

However, that would be pretty inefficient with long messages and I can’t find a way to read ahead. Am I missing a useful trick?

Thanks in advance!

First Post!

NobbZ

NobbZ

Have you took a look at IO.stream/2?

:stdin
|> IO.stream(1_024)
|> Enum.each(&IO.puts/1)

Should be roughly equivalent to your first ruby version.

Most Liked

josevalim

josevalim

Creator of Elixir

If you really want to play with what is under the hood, the IO message protocol in Erlang does provide a get_until functionality: The Erlang I/O Protocol — OTP 29.0.2 (stdlib 8.0.1)

The Erlang io:read/2 function uses it to parse terms out of the standard input:

read(Io, Prompt) ->
    case request(Io, {get_until,unicode,Prompt,erl_scan,tokens,[1]}) of
	{ok,Toks,_EndLine} ->
	    erl_parse:parse_term(Toks);
%	{error, Reason} when atom(Reason) ->
%	    erlang:error(conv_reason(read, Reason), [Io, Prompt]);
	{error,E,_EndLine} ->
	    {error,E};
	{eof,_EndLine} ->
	    eof;
	Other ->
	    Other
    end.

Dave Thomas has recently asked for a similar feature and we would be glad to expose it in the IO module API.

josevalim

josevalim

Creator of Elixir

I would personally prefer to provide a low-level function first and then a higher level one. Otherwise folks will have to reach the low-level protocol every time they need something more complex than an expression.

I am also not sure we can support the until approach in binread/2 since it uses a different protocol iirc.

JEG2

JEG2

Author of Designing Elixir Systems with OTP

Sorry, but I don’t believe it is. I think your code blocks until it can deliver 1,024 bytes, so you don’t receive messages as they come in. I tried to run it to verify my assumptions (after I fixed the :stdin to :stdio bug) and it did seem to be the case.

Last Post!

marciol

marciol

Seems that it was implemented 2 months ago right?

https://github.com/elixir-lang/elixir/pull/11081

Where Next?

Popular in Questions Top

joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? Ecto.Repo — Ecto v3.14.0 has exampl...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New

Other popular topics Top

grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 54260 488
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
sergio
Kind of like when jquery came out, it was super necessary. Existing drag and drop libraries have a bunch of baggage to support old browse...
New

We're in Beta

About us Mission Statement