msantos/prx

Send Stdout after each carriage retrun

Closed this issue · 7 comments

I execute a c program with prx which log some useful outputs, I want to receive my outputs line by line (not accumulated in a buffer) after each carriage return .
How can I do it ?

I suspect the terminal needs to be setup in line-buffered mode:

{ok, Parent} = prx:fork(),
{ok, Child} = prx:fork(Parent),
prx:execvp(Child, ["stdbuf", "-oL", "/path/to/cmd", "arg"]).

If that solves the problem, you can set a secret environment variable before doing the exec that does the same thing as stdbuf is doing:

prx:setenv(Child, "_STDBUF_O", "L", 1).

Works on Linux and if I remember correctly, also on OpenBSD/FreeBSD.

If it doesn't work, you can pass the buffer to an accumulator function that breaks on newlines and returns a list. Also, if you have a simple test case, I can have a look.

sorry, The first solution didn't work for me.
For the second solution, I want to receive outputs in real time because my c program read from serial ports and send them to erlang over prx.

For serial ports, there isn't a concept of line buffered, just buffered or unbuffered (1 character at a time). Usually the C program would open the serial port in "raw" mode, then read bytes until a newline then output. prx is getting buffered reads from the C program.

You can play with setting the serial line attributes using stty(1). Or you can modify the program if you have source code.

If you have an example, it would help.

Here is an example of interacting with a serial console. The example uses picocom to login to a raspberry pi serial console. picocom is invoked using "script" to force a tty.

{ok, Task} = prx:fork(),                                                        
{ok, Sh} = prx:fork(Task).                                                      
ok = prx:execvp(Sh, ["script", "-f", "-c", "picocom -b 115200 /dev/ttyUSB1", "/dev/null"]),
% should wait for a prompt/timeout before sending the username
prx:stdin(Sh, "pi\n"),                                                          
prx:stdin(Sh, "password\n").                                                    
prx:stdin(Sh, "ls\n").

sorry, I think that I didn't explain well, in fact I have a c program that perform some operations against a serial port(I didn't deal with prx in communication with serial device).
Then, I run my c program from prx as an executable as follows:

Ok = prx:execvp(Child, ["c_program_executable", "arg","arg","arg", "arg" ]),

Basically I run my prx command in a gen_server and I handle message reception in a handle_info callback.

My problem is how to communicate with my executable log instantaneously, for example I want that my erlang code receive each printf/cout from my c program at runtime without buffering data and sending it when reaching an amount of size.

@hamdijmii1992 thanks, that is what I understood to be the problem but it is difficult to provide specific advice without a test case. So I'll expand a little on #1 (comment) and #1 (comment).

prx will send your gen_server any data immediately as it reads it from the child process. Something else is buffering. One typical cause is buffered writes. An example program:

#include <stdio.h>
#include <unistd.h>

/* cc -g -Wall -o tt tt.c */                                                                
    int                                                                         
main(int argc, char *argv[])                                                    
{                                                                               
    int n = 0;                                                                  
                                                                                
    for ( ; ; ) {                                                               
        sleep(1);                                                               
        (void)fprintf(stdout, "%d\n", n);                                       
        if (argc > 1) (void)fflush(stdout);                                     
        n++;                                                                    
    }                                                                           
                                                                                
    return 0;                                                                   
}

Running it:

1> {ok, Task} = prx:fork().                                                     
{ok,<0.114.0>}                                                                  
2> {ok, Sh} = prx:fork(Task).                                                   
{ok,<0.119.0>}                                                                  
3> prx:execvp(Sh, ["./tt"]). % buffered write                                                    
ok                                                                              
4> flush().                                                                     
ok                                                                              
5> flush().                                                                     
ok                                                                              
6> flush().                                                                     
ok                                                                              
7> flush().  % no data until buffer is full                                                                   
ok                                                                              
8> {ok, Taskb} = prx:fork(Task).                                                
{ok,<0.126.0>}                                                                  
9> prx:execvp(Taskb, ["./tt", "x"]).  % force fflush(3) after write                                          
ok                                                                              
10> flush().                                                                    
Shell got {stdout,<0.126.0>,<<"0\n">>}                                          
ok                                                                              
11> flush().                                                                    
Shell got {stdout,<0.126.0>,<<"1\n">>}                                          
ok                                                                              
12> flush().                                                                    
Shell got {stdout,<0.126.0>,<<"2\n">>}                                          
Shell got {stdout,<0.126.0>,<<"3\n">>}                                          
ok                                         

Using stdbuf(1) with unbuffered writes:

1> {ok, F1} = prx:fork().
{ok,<0.114.0>}
2> {ok, F1a} = prx:fork(F1).
{ok,<0.119.0>}
3> prx:execvp(F1a, ["stdbuf", "-o", "0", "./tt"]).
ok
4> flush().
Shell got {stdout,<0.119.0>,<<"0\n">>}
ok
5> flush().
Shell got {stdout,<0.119.0>,<<"1\n">>}
ok
6> flush().
Shell got {stdout,<0.119.0>,<<"2\n">>}
ok
7> flush().
Shell got {stdout,<0.119.0>,<<"3\n">>}
ok
8> flush().
Shell got {stdout,<0.119.0>,<<"4\n">>}
ok
9> 

Setting the magic environment variables on Linux:

1> {ok, F1} = prx:fork().
{ok,<0.114.0>}
2> {ok, F1a} = prx:fork(F1).
{ok,<0.119.0>}
3> prx:setenv(F1a, "_STDBUF_O", "0", 1).
ok
4> prx:setenv(F1a, "LD_PRELOAD", "/usr/lib/x86_64-linux-gnu/coreutils/libstdbuf.so", 1).
ok
5> prx:execvp(F1a, ["./tt"]).
ok
6> flush().
Shell got {stdout,<0.119.0>,<<"0\n">>}
ok
7> flush().
Shell got {stdout,<0.119.0>,<<"1\n">>}
Shell got {stdout,<0.119.0>,<<"2\n">>}
ok

Hope that helps you to get it working!

Thanks very much @msantos, I tested Using stdbuf(1) with unbuffered writes and it worked very fine