1

I copied an async sleep function from here https://stackoverflow.com/a/39914235/7492244 Then I used it basically in this program. https://nodejs.org/api/readline.html#example-read-file-stream-line-by-line

So my own index.js looks like:

const fs = require('fs');
const readline = require("readline");

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function main() {
    const fileStream = fs.createReadStream('input.txt');
    let lineReader = readline.createInterface({
        input: fileStream,
        crlfDelay: Infinity,
    });
    console.log("Enter sleep");
    await sleep(1000);
    console.log("Exit sleep");

    for await (const line of lineReader) {
        console.log("line: " + line);
    }
    console.log("DONE");
}

main();

I get this mind boggling behaviour that it somehow prints Enter sleep and Exit sleep but not DONE. However it does terminate, and without printing any errors. I found out after hours of debugging that it works if I remove the call to sleep. What is wrong with this sleep function?

Edit: Note: I am specifically just trying to understand why the call to sleep breaks (afaics) the flow of the program. The use case or end goal is not important.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
Ploppz
  • 154
  • 2
  • 13
  • Why does this code need sleep? – evolutionxbox Mar 23 '22 at 13:37
  • @evolutionxbox Maybe to do some sort of throttling control.. – Keith Mar 23 '22 at 13:39
  • 1
    There is no real reason why the code needs sleep, I was just debugging. But I strive to understand why it behaves as it does, and whether there's something wrong with that sleep function, because I use it in my project as well. – Ploppz Mar 23 '22 at 13:46
  • Does it work if you put the `fs.createReadStream` and `readline.createInterface` calls after the `sleep()`? I'd guess there's some race condition (possibly a bug in nodejs) where it does read the entire file while sleeping, and then leaves an inconsistent state for the `for await`. – Bergi Mar 23 '22 at 13:59
  • Btw what version of nodejs are you on? – Bergi Mar 23 '22 at 14:00
  • @Bergi Testing here on v16.11.1, and putting the sleep before makes no difference, I was wondering if it was ref problems, so deliberately created a timeout and `ref()`, it at start and then `unref()` at end. Must say, this does seem odd.. – Keith Mar 23 '22 at 14:04
  • Yes, it works if the sleep call is first in the function. `node --version` shows `v17.7.1`. Should I report it as a bug? – Ploppz Mar 23 '22 at 14:04
  • Just a heads up, the bug is with `createReadStream`, using a `readableStream` directly does not exhibit this issue.. – Keith Mar 23 '22 at 14:18
  • @Bergi Correction, I didn't move the sleep, I kept the other one there too. Just moving it before does the same as OP in v.16.. – Keith Mar 23 '22 at 14:48
  • 1
    @Ploppz Yes, please [report a bug](https://github.com/nodejs/node/issues/new?assignees=&labels=&template=1-bug-report.yml) (and then link it here) – Bergi Mar 23 '22 at 19:41
  • 2
    Here it is https://github.com/nodejs/node/issues/42454 – Ploppz Mar 24 '22 at 14:16

2 Answers2

1

Ok, this one was strange.

It seems like if you don't pause a stream while you do the timeout the process will terminate. No errors either, even try/catch and try/finally will fail..

But if you pause & resume, this seems to fix the issue..

eg..

const fs = require('fs');
const readline = require("readline");

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function main() {
    const fileStream = fs.createReadStream('input.txt');
    let lineReader = readline.createInterface({
        input: fileStream,
        crlfDelay: Infinity,
    });
    console.log("Enter sleep");
    lineReader.pause();
    await sleep(1000);
    lineReader.resume();
    console.log("Exit sleep");

    for await (const line of lineReader) {
        console.log("line: " + line);
    }
    console.log("DONE");
}

main();

ps, if you use sleep in the for await you don't need to pause.. So it appears if you don't start reading a stream straight away without pausing, that's when the issue occurs.

Of course the other option is just call the sleep before the readline.createInterface, and the pause won't be required.

Keith
  • 22,005
  • 2
  • 27
  • 44
  • This does sound very much like a bug, no? – Bergi Mar 23 '22 at 16:41
  • @Bergi Yeah, I would say so, it should at least error rather than just terminating. I think the OP was on about reporting as a bug, he could maybe refer to this post for context. – Keith Mar 23 '22 at 19:08
-1

This seem a bit strange that use of reading a stream;

since you can add event like this

const lines = []:
lineReader.on('line', line => lines.push(line))

and delete your for loop, in the end you can have only that

const fs = require('fs');                                                                                                                                                                                  
const readline = require("readline");                                                                                                                                                                      
                                                                                                                                                                                                           
function sleep(ms) {                                                                                                                                                                                       
  return new Promise((resolve) => setTimeout(resolve, ms));                                                                                                                                                
}                                                                                                                                                                                                          
                                                                                                                                                                                                           
async function main() {                                                                                                                                                                                    
    const lines = [];                                                                                                                                                                                      
    const fileStream = fs.createReadStream('text.txt');                                                                                                                                                    
    let lineReader = readline.createInterface({                                                                                                                                                            
        input: fileStream,                                                                                                                                                                                 
        crlfDelay: Infinity,                                                                                                                                                                               
    });                                                                                                                                                                                                    
    lineReader.on('line', line => lines.push(line))                                                                                                                                                        
    console.log("Enter sleep");                                                                                                                                                                            
    await sleep(1000);                                                                                                                                                                                     
    console.log("Exit sleep");                                                                                                                                                                             
    console.log(lines);                                                                                                                                                                                    
    console.log("DONE");                                                                                                                                                                                   
}                                                                                                                                                                                                          
                                                                                                                                                                                                           
main();

By the way it dosn't work your way since you have a timeout and the strezam close.

Just console.log the lineReader and you will see the status closed at true and multiple data like the timout it handle and everything :P

Thibaud
  • 1,059
  • 4
  • 14
  • 27
  • This is just a test for another purpose: I want to listen to stdout and wait until a certain message from stdout before returning from the function. Is that possible without `for await`? – Ploppz Mar 23 '22 at 13:47
  • Yea sur usually you can like you sleep function create a promise and resolve only when you have your result :P just be warn of the std timeout but with good parameters (I don't know them by hearth) you will have no problem – Thibaud Mar 23 '22 at 13:52
  • 1
    "*This seem a bit strange that use of reading a stream;*" - no, using the async iterator is the normal and recommended way to ingest a stream, blocking while processing and waiting until it ends. – Bergi Mar 23 '22 at 13:54
  • 1
    Your suggestion does not wait for the `lineReader` to finish, it just prints the lines that it could read in 1 second and "DONE" immediately after that. – Bergi Mar 23 '22 at 13:55