Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does this console log twice?

What I'm trying to do:

I want to use Node to fire up two child processes in a particular order at a particular time, console logging their stdout as they stream, occasionally switching between the two.

The output I want:

`Proc 1 log # 1`
`Proc 1 log # 2`
`Proc 1 log # 3`
`Proc 1 log # 4`
`Proc 2 log # 1`
`Proc 2 log # 2`
`Proc 2 log # 3`
`Proc 2 log # 4`
`Proc 1 log # 9`
`Proc 1 log # 10`
`Proc 1 log # 11`
`Proc 1 log # 12`
`Proc 1 log # 13`
`Proc 1 log # 14`
`Proc 1 log # 15`
`All procs have finished!`

 

Of course it's super easy to do. Imperatively. But it's also really ugly & stateful and just urgh. So I'm trying to do it purely, and have built up the computation using the Task monad from folktale (the old one, that is), threading through a stateful object by hand like a chump:

 

//    main _ :: Task error {childProcs}
const main = startProc1({})
  .chain(logUntilProc1IsReady)
  .chain(startProc2)
  .chain(logUntilProc2IsReady)
  .chain(logUntilProc1IsFinished)

 

Much prettier. It would be much better too, if it worked!

 

The output I get:

 

`Proc 1 log # 1`                                       
`Proc 1 log # 2`
`Proc 1 log # 3`
`Proc 1 log # 4`
`Proc 2 log # 1`
`Proc 1 log # 6`   // <-- These should not be logged
`Proc 2 log # 2`
`Proc 1 log # 7`
`Proc 2 log # 3`
`Proc 1 log # 8`
`Proc 2 log # 4`
`Proc 1 log # 9`
`Proc 1 log # 10`  // <-- now it's logging twice! :confounded:
`Proc 1 log # 10`
`Proc 2 log # 6`
`Proc 1 log # 11`
`Proc 1 log # 11`
`Proc 2 log # 7`
`Proc 1 log # 12`
`Proc 1 log # 12`
`Proc 2 log # 8`
`Proc 1 log # 13`
`Proc 1 log # 13`
`Proc 2 log # 9`
`Proc 1 log # 14`
`Proc 1 log # 14`
`Proc 2 log # 10`
`All procs have finished!`

 

Stuff I've done:

Here is the logging function:

 

//    logStreamUntil :: int -> (a -> bool) -> proc -> string -> Task error State () {childProcs}
const logStreamUntil = curry((predFunc, procName, procObj) => 
  new Task ((_, res) => {
    const proc = procObj[procName]
    const logUntilPred = data =>
      predFunc(data) 
        ? (rmAllListeners(proc), res(procObj))
        : console.log(data)
    proc.stdout.on('data', logUntilPred)
}))

 

Of which the tl;dr: is that I'm sending it a process name and the object to pluck the actual child-process from, as well as a predicate function used to decide for how long to console log the stdout of whichever child-process had been thrown at it. The predicates just look for something specific in the string from stdout. So it console logs the output when the predicate function returns false, else it stops logging, removes the listener and that should be that!

 

And here then is the rmAllListeners function:

 

//    rmAllListeners :: childProc -> childProc           
const rmAllListeners = proc => (proc.removeAllListeners(), proc.stdout.unref())

 

Which latter is clearly the issue. Listeners, despite being namespaced and supposedly obliterated by the above, are not being. And I don't know why?!? Help!

 

Further reading:

There's a repo of it too for those interested in seeing the entire thing: you can find it here.

 

like image 894
gskapka Avatar asked Oct 28 '22 06:10

gskapka


1 Answers

You're removing listeners from the proc instead of from stdout. The doubles are appearing because you're attaching a second copy of your listener to the 'data' events on the proc.stdout.

Adding .stdout in rmAllListeners fixes it for me:

diff --git a/why-log-twice.js b/why-log-twice.js
index 276d15c..6c15467 100644
--- a/why-log-twice.js
+++ b/why-log-twice.js
@@ -7,7 +7,7 @@ const PROC_ONE_PATH = `node child-proc "Proc 1 log # "`
 const PROC_TWO_PATH = `node child-proc "Proc 2 log # "`

 //    rmAllListeners :: childProc -> childProc           
-const rmAllListeners = proc => (proc.removeAllListeners(), proc.stdout.unref())
+const rmAllListeners = proc => (proc.stdout.removeAllListeners(), proc.stdout.unref())

 //    procIsReady :: string -> bool
 const procIsReady = str => str.includes('5')
like image 193
Ethan Herbertson Avatar answered Nov 12 '22 20:11

Ethan Herbertson