Simulate synchonous I/O for interactive console program

70 views
Skip to first unread message

mertes...@gmail.com

unread,
Apr 8, 2019, 3:40:46 AM4/8/19
to emscripten-discuss
Hello,
I have problems with emcc and node.ja when I test
a small interactive program:

---------- begin test program tst261.c ----------
#include <stdio.h>

int main(int argc, char *argv[])
  {
    char buf[100];
    printf("Your input? ");
    fflush(stdout);
    while (fgets(buf, sizeof(buf), stdin) != NULL &&
           buf[0] != '!') {
      printf("You typed: %s", buf);
      printf("Your input (Use ! to terminate)? ");
      fflush(stdout);
    }
    return 0;
  }
---------- end test program tst261.c ----------

When I use gcc and run the program afterwards it looks like follows:

  myPrompt> gcc tst261.c -o tst261
  myPrompt> ./tst261
  Your input? test
  You typed: test
  Your input (Use ! to terminate)? another test
  You typed: another test
  Your input (Use ! to terminate)? !
  myPrompt>

As you can see input and output can alternate.
With emcc and node.js it looks like follows:

  myPrompt> emcc tst261.c -o tst261.js
  myPrompt> node tst261.js
  test
  another test
  !
  <<< Here I typed cntl-d >>>
  Your input? You typed: test
  Your input (Use ! to terminate)? You typed: another test
  stdio streams had content in them that was not flushed. you should set EXIT_RUNTIME to 1 (see the FAQ), or make sure to emit a newline when you printf etc.
  myPrompt>

As you can see: After the start of the program all the
input must be typed in followed by cntl-d at the beginnig of a line.
Afterwards the program processes all given input in one batch.

Interestingly it is also possible to press cntl-d several times:

  myPrompt> node tst261.js
  test
  <<< Here I typed cntl-d >>>
  Your input? You typed: test
  another test
  <<< Here I typed cntl-d >>>
  Your input (Use ! to terminate)? You typed: another test
  !
  <<< Here I typed cntl-d >>>
  stdio streams had content in them that was not flushed. you should set EXIT_RUNTIME to 1 (see the FAQ), or make sure to emit a newline when you printf etc.
  myPrompt>

I know that Javascript is single threaded, uses an event loop
and works with asynchronous I/O. But I think that it should be
possible to base a synchonous I/O on an asynchronous one.

All JavaScript "solutions" I found in the internet suggest, that
the program must register some callback (or use a Promise) an then
needs to terminate. Afterwards the event loop would call the callback
respecively Promise. In any such case the program is terminated
and executes in a callback function.

In the days when Pascal and other programming languages experimented
with cooperative multitasking it was always possible to write
such a simple interactive program like above. The sychronous
input function entered the event loop (where other events would be
handled just like the Javascript event loop does) and it came back,
when a keyboard input had been typed in.

I really cannot believe that it is not possible to compile such
a simple interactive program with emcc.

I have just started to compile my project (Seed7) with emcc.
Several things already work good, but interactive things do not.

I also need a synchronous solution to read single keypresses
without echo and a function to detect if a key has been pressed.

First I target node.js, but later the browsers are also a target.

Many thanks in advance for your help.

Regards,
Thomas Mertes

--
Seed7 Homepage:  http://seed7.sourceforge.net
Seed7 - The extensible programming language: User defined statements
and operators, abstract data types, templates without special
syntax, OO with interfaces and multiple dispatch, statically typed,
interpreted or compiled, portable, runs under linux/unix/windows.

Brian Dickens

unread,
Apr 8, 2019, 5:23:16 AM4/8/19
to emscripte...@googlegroups.com
On Mon, Apr 8, 2019 at 3:40 AM <mertes...@gmail.com> wrote:

> I really cannot believe that it is not possible to compile such
> a simple interactive program with emcc.  

Believe it.  :-)

The JavaScript hosts like Node.js and browsers weren't designed for synchronous I/O.  In a browser you can use Window.prompt() and alert()...both of which are too annoying to be practical.  But other than that you won't see any I/O or impacts of DOM manipulations.  JavaScript programmers have just learned to live with it.

For I/O to process you have to yield to the main loop...which means your C code can't still be running (on the main thread) when it does.  The only options for C code that wants to do synchronous I/O while remaining on the stack are if that code is running on a worker thread, or if you use the emterpreter:


Threads aren't an option yet for running under Node.js, as Emscripten does not currently have pthreads support (though Node.js seemingly has the necessary features where it *could* be implemented):


So this means that you pretty much can only preserve your C stack state if you're running in a bytecode interpreter, vs. directly on WASM or asm.js.  It's not really emscripten's fault, it's just the way things are.

There are probably too many variances in the meaning of what a "console app" in the browser would look like (what kind of DOM tree, CSS, etc.) to canonize one of those into the emscripten build yet.  A Node.js console app may seem more clearly defined, but it would still be non-trivial...and since interactive console-based node.js programs are uncommon, it's probably just not a priority.  And as mentioned, it would force you to use the bytecode interpreter at this time.

Since your example is a programming language, you might find some of the following work to be of interest--we've been building an interactive REPL for the Rebol language:


Best
--Brian 

Floh

unread,
Apr 8, 2019, 6:19:17 AM4/8/19
to emscripten-discuss
Welcome to the wonders of the web platform :)

I remember well how confused I was when starting to tinker with emscripten, being used to platforms where an application is allowed to "own the game loop", and blocking calls are not really an issue. Eventually I came around to accept those restrictions instead of working around them with hacks (mobile platforms like Android and iOS have similar restrictions, however on Android it's quite typical to workaround it by running the actual application code in a separate thread, and not the "UI thread").

I'm now writing all my cross-platform-code in such an "asyncronous-friendly" way. Instead of an "owned" game loop there's a frame-callback, and all IO is asynchronous with callbacks instead of blocking.

It would be nice to have async-await-style blocking, where control is given back to the browser runtime in the middle of a function, but the way this is implemented in many native green-threading-libs (by switching stack frames with a bit of assembly code) isn't possible in asm.js / wasm (AFAIK it works with the emterpreter approach though).

With shared-memory-threading support it should also be possible to workaround those limitations, similar to how it's done on Android. But I think most (all?) calls to Javascript/HTML5 APIs would still need to go through the main thread.

There's a little blurb here about coroutine support as a potential WebAssembly future-feature, but I don't know the state of that.

Floh

unread,
Apr 8, 2019, 6:23:08 AM4/8/19
to emscripten-discuss
> There's a little blurb here about coroutine support as a potential WebAssembly future-feature, but I don't know the state of that.

Gabriel Cuvillier

unread,
Apr 8, 2019, 7:02:58 AM4/8/19
to emscripte...@googlegroups.com

100% agree with Floh there.

It was difficult for me too to accept that "blocking" the main thread is not possible. This is particularly true when porting existing C++ programs, most of them being designed around that idea. You have to learn the "asynchronous" way of doing things, sometime called the "callback hell" ;)

While I do understand why we can't block the main thread, I still don't get why it is not technically possible with WebAssembly to give back control to the browser in the middle of execution, without relying on things like Emterpreter (which is a nice thing btw!). WebAssembly is a VM after all, a "Pause/Freeze" mechanism is not something impossible. Maybe it's because it is too closely tied to the JS VM (stack frames, etc..)

--
You received this message because you are subscribed to the Google Groups "emscripten-discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to emscripten-disc...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Thomas Mertes

unread,
May 26, 2019, 10:16:24 AM5/26/19
to emscripten-discuss
On Mon, Apr 8, 2019 at 11:23:16 UTC+2 Brian Dickens wrote:
> On Mon, Apr 8, 2019 at 3:40 AM <mertes...@gmail.com> wrote:
>
> > I really cannot believe that it is not possible to compile such
> > a simple interactive program with emcc. 
>
> Believe it.  :-)
>
> The JavaScript hosts like Node.js and browsers weren't designed for synchronous I/O.  In a browser you can use Window.prompt() and alert()...both of which are too annoying to be practical.  But other than that you won't see any I/O or impacts of DOM manipulations.  JavaScript programmers have just learned to live with it.
>
> For I/O to process you have to yield to the main loop...which means your C
code can't still be running (on the main thread) when it does.  The only
options for C code that wants to do synchronous I/O while remaining on the
stack are if that code is running on a worker thread, ...

I looked at the worker_threads provided by node.js.
When a worker thread is started it can get some workerData from
the main thread. A worker_thread can also send something back to
the main thread via a parentPort. To receive the message from the
worker_thread the main thread uses something like

  worker.on('message', (msg) => { console.log(msg); });

and then terminates. The message from the worker_thread is then
handled in this callback. I have found also examples where the
worker_thread registers some callback and terminates itself to
receive a message from the main thread. It seems that this works
like anything else in JavaScript: asynchronous

What I am missing is: The worker thread should be able to wait
for a message (without callback and terminating the worker_thread)
and to continue after the message has been received. It is
important that no termination of the thread is necessary and
that the program can continue after the "wait" statement.

A not so elegant solution would be possible, when there is the
possibility to poll, if a message has been sent. In this case
the worker_thread could poll for a message (possibly with some
sleep in between).

I can understand that the main thread works asynchronous and
never waits for something. But for worker_threads this could
be different.

I was thinking to start a worker_thread with the main program
and the main thread would only handle events and sending them
to the worker_thread. To do this the worker_thread must be
able to work synchronous (wait for messages from the main thread).

Is this possible?
Reply all
Reply to author
Forward
0 new messages