[Git][ghc/ghc][wip/js-th] make thrunner stdin asynchronous, fixing macOS/Windows

Luite Stegeman (@luite) gitlab at gitlab.haskell.org
Mon Jan 23 05:58:56 UTC 2023



Luite Stegeman pushed to branch wip/js-th at Glasgow Haskell Compiler / GHC


Commits:
75b6a584 by Luite Stegeman at 2023-01-23T14:55:04+09:00
make thrunner stdin asynchronous, fixing macOS/Windows

warning: buffering very inefficient

- - - - -


2 changed files:

- libraries/base/jsbits/base.js
- thrunner.js


Changes:

=====================================
libraries/base/jsbits/base.js
=====================================
@@ -677,8 +677,12 @@ if(h$isNode()) {
         c(0);
     }
 
-    process.stdin.on('readable', h$base_process_stdin);
-    process.stdin.on('end', function() { h$base_stdin_eof = true; h$base_process_stdin(); });
+    // if we are in a Template Haskell context (global.h$TH is set) then we
+    // should keep away from stdin, because it's used for communication with GHC
+    if(typeof global.h$TH !== 'object') {
+        process.stdin.on('readable', h$base_process_stdin);
+        process.stdin.on('end', function() { h$base_stdin_eof = true; h$base_process_stdin(); });
+    }
 
     h$base_isattyStdin  = function() { return process.stdin.isTTY;  };
     h$base_isattyStdout = function() { return process.stdout.isTTY; };


=====================================
thrunner.js
=====================================
@@ -11,8 +11,8 @@
                   n: response to request n
 */
 
-var h$THfs = require('node:fs');
-var h$THvm = require('node:vm');
+var h$THfs = require('fs');
+var h$THvm = require('vm');
 
 if(typeof __dirname == 'undefined') {
   // TODO work out the best shim to use here
@@ -67,10 +67,9 @@ function h$lookupClosure(v) {
 globalThis.h$loadJS = h$loadJS;
 globalThis.h$lookupClosure = h$lookupClosure;
 
-function h$initTH() {
+async function h$initTH() {
   console.log("Welcome to GHC's JS interpreter");
 
-  process.stdin.setEncoding('binary');
   process.stderr.setEncoding('binary');
 
   process.stdin.on('end', () => {
@@ -81,28 +80,21 @@ function h$initTH() {
   h$interp_loop();
 }
 
-function h$interp_loop() {
-  const fd = h$THfs.openSync('/dev/stdin', 'rs');
-
-  const cmd_buf = Buffer.alloc(1);
-  const int32_buf = Buffer.alloc(4);
-
-  const getInt32 = () => {
-            h$THfs.readSync(fd, int32_buf, 0, 4);
-            return (new Int32Array(int32_buf))[0];
-          }
+async function h$interp_loop() {
+  async function getInt32() {
+    let b = await readStdin(4);
+    return new Int32Array(b)[0];
+  }
 
-  const getBuffer = (size) => {
-            const b = Buffer.alloc(size);
-            h$THfs.readSync(fd, b, 0, size);
-            return b;
-          }
+  async function getByte() {
+    let b = await readStdin(1);
+    return b[0];
+  }
 
   console.log("Entering interpreter loop...");
 
-  while(1) {
-    h$THfs.readSync(fd, cmd_buf, 0, 1);
-    const cmd_id = cmd_buf[0];
+  while(true) {
+    const cmd_id = await getByte();
 
     // interpret command
     switch (cmd_id) {
@@ -114,8 +106,8 @@ function h$interp_loop() {
 
       // load JS file
       case 1:
-        const payload_size = getInt32();
-        const payload = getBuffer(payload_size);
+        const payload_size = await getInt32();
+        const payload = await readStdin(payload_size);
         const p = payload.toString('utf8');
         h$loadJS(p);
         break;
@@ -132,11 +124,55 @@ function h$interp_loop() {
   }
 }
 
+/////////////////////////////////////////////////////////////////////
+// stdin buffering helpers
+//
+// usage: await readStdin(n);
+//   this returns a buffer with n bytes from stdin
+//
+// perhaps we can use some existing infrastructure instead of
+// managing our own buffers
+/////////////////////////////////////////////////////////////////////
+let stdinBufs = [], stdinWaiters = [];
+process.stdin.on('data', (d) => {
+    stdinBufs.push(d);
+    let waiters = stdinWaiters;
+    stdinWaiters = [];
+    for(const r of waiters) r();
+});
+
+
+// helper function, reads n bytes from stdin if available in the buffer,
+// returns null otherwise
+//
+// warning: this is very inefficient for larger buffers
+function getStdinData(n) {
+    if(stdinBufs.length == 0) return null;
+    let b = stdinBufs.length == 1 ? stdinBufs[0] : Buffer.concat(stdinBufs);
+    if(b.length >= n) {
+        let r = b.slice(0, n);
+        stdinBufs = [b.slice(n)];
+        return r;
+    } else {
+        if(stdinBufs.length !== 1) stdinBufs = [b];
+        return null;
+    }
+}
+
+async function readStdin(n) {
+    while(true) {
+        let d = getStdinData(n);
+        if(d) {
+            return d;
+        } else {
+            await new Promise((resolve) => { stdinWaiters.push(resolve); });
+        }
+    }
+}
+/////////////////////////////////////////////////////////////////////
+
 function h$runServer() {
   console.log("Run server");
-  // don't allow Haskell to read from stdin
-  h$base_stdin_fd.read = function(fd, fdo, buf, buf_offset, n, c) { c(0); }
-  // run server
   h$main(h$ghciZCGHCiziServerzidefaultServer);
 }
 



View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/75b6a584057d9f162339bd2f40f6535ec8613a0f

-- 
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/75b6a584057d9f162339bd2f40f6535ec8613a0f
You're receiving this email because of your account on gitlab.haskell.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/ghc-commits/attachments/20230123/3c1731a5/attachment-0001.html>


More information about the ghc-commits mailing list