Reply to messages in child processes - javascript

I am looking for an effective way to reply to a message sent to a child process. Currently, I am using the following code:
const { fork } = require('child_process');
const child = fork(path.join(__dirname, 'sub.js'));
async function run() {
console.log('Requesting status....');
child.send('status');
const status = await awaitMessage(child);
console.log(status);
}
function awaitMessage(childProcess) {
return new Promise((resolve) => {
childProcess.on('message', (m) => {
resolve(m);
});
});
}
The problem of this code is that it creates a new event listener every single time the awaitMessage() function is called, which is prone to memory leaks. Is there an elegant way of receiving a reply from the child process?

This isn't really "prone to memory leaks" in that a leak is something that is supposed to get freed (per the rules of the garbage collector), but isn't. In this case, you've left a promise hooked up to an event handler that can still get called so the system simply can't know that you intend for it to be freed.
So, the system is retaining exactly what your code told it to retain. It's the consequence of how your code works that it is retaining every promise ever created in awaitMessage() and also firing a bunch of extra event handlers too. Because you keep the event listener, the garbage collector sees that the promise is still "reachable" by that listener and thus cannot and should not remove the promise even if there are no outside references to it (per the rules of the Javascript garbage collector).
If you're going to add an event listener inside a promise, then you have to remove that event listener when the promise resolves so that the promise can eventually be freed. A promise is no magic object in Javascript, it's just a regular object so as long as you have an object that can be referenced by a live event listener, that object can't be garbage collected.
In addition, this is subject to race conditions if you ever call awaitMessage() twice in a row as both promises will then respond to the next message that comes. In general, this is just not a good design approach. If you want to wait for a message, then you have to somehow tag your messages so you know which message response is the actual one you're waiting for to avoid your race conditions and you have to remove the event listener after you get your message.
To avoid the memory build-up because of the accumulation of listeners, you can do this:
function awaitMessage(childProcess) {
return new Promise((resolve) => {
function handleMsg(m) {
childProcess.removeListener(handleMsg);
resolve(m);
}
childProcess.on('message', handleMsg);
});
}

Related

Why does the browser not freeze when awaiting these promises? [duplicate]

When using Javascript promises, does the event loop get blocked?
My understanding is that using await & async, makes the stack stop until the operation has completed. Does it do this by blocking the stack or does it act similar to a callback and pass of the process to an API of sorts?
When using Javascript promises, does the event loop get blocked?
No. Promises are only an event notification system. They aren't an operation themselves. They simply respond to being resolved or rejected by calling the appropriate .then() or .catch() handlers and if chained to other promises, they can delay calling those handlers until the promises they are chained to also resolve/reject. As such a single promise doesn't block anything and certainly does not block the event loop.
My understanding is that using await & async, makes the stack stop
until the operation has completed. Does it do this by blocking the
stack or does it act similar to a callback and pass of the process to
an API of sorts?
await is simply syntactic sugar that replaces a .then() handler with a bit simpler syntax. But, under the covers the operation is the same. The code that comes after the await is basically put inside an invisible .then() handler and there is no blocking of the event loop, just like there is no blocking with a .then() handler.
Note to address one of the comments below:
Now, if you were to construct code that overwhelms the event loop with continually resolving promises (in some sort of infinite loop as proposed in some comments here), then the event loop will just over and over process those continually resolved promises from the microtask queue and will never get a chance to process macrotasks waiting in the event loop (other types of events). The event loop is still running and is still processing microtasks, but if you are stuffing new microtasks (resolved promises) into it continually, then it may never get to the macrotasks. There seems to be some debate about whether one would call this "blocking the event loop" or not. That's just a terminology question - what's more important is what is actually happening. In this example of an infinite loop continually resolving a new promise over and over, the event loop will continue processing those resolved promises and the other events in the event queue will not get processed because they never get to the front of the line to get their turn. This is more often referred to as "starvation" than it is "blocking", but the point is that macrotasks may not get serviced if you are continually and infinitely putting new microtasks in the queue.
This notion of an infinite loop continually resolving a new promise should be avoided in Javascript. It can starve other events from getting a chance to be serviced.
Do Javascript promises block the stack
No, not the stack. The current job will run until completion before the Promise's callback starts executing.
When using Javascript promises, does the event loop get blocked?
Yes it does.
Different environments have different event-loop processing models, so I'll be talking about the one in browsers, but even though nodejs's model is a bit simpler, they actually expose the same behavior.
In a browser, Promises' callbacks (PromiseReactionJob in ES terms), are actually executed in what is called a microtask.
A microtask is a special task that gets queued in the special microtask-queue.
This microtask-queue is visited various times during a single event-loop iteration in what is called a microtask-checkpoint, and every time the JS call stack is empty, for instance after the main task is done, after rendering events like resize are executed, after every animation-frame callback, etc.
These microtask-checkpoints are part of the event-loop, and will block it the time they run just like any other task.
What is more about these however is that a microtask scheduled from a microtask-checkpoint will get executed by that same microtask-checkpoint.
This means that the simple fact of using a Promise doesn't make your code let the event-loop breath, like a setTimeout() scheduled task could do, and even though the js stack has been emptied and the previous task has been executed entirely before the callback is called, you can still very well lock completely the event-loop, never allowing it to process any other task or even update the rendering:
const log = document.getElementById( "log" );
let now = performance.now();
let i = 0;
const promLoop = () => {
// only the final result will get painted
// because the event-loop can never reach the "update the rendering steps"
log.textContent = i++;
if( performance.now() - now < 5000 ) {
// this doesn't let the event-loop loop
return Promise.resolve().then( promLoop );
}
else { i = 0; }
};
const taskLoop = () => {
log.textContent = i++;
if( performance.now() - now < 5000 ) {
// this does let the event-loop loop
postTask( taskLoop );
}
else { i = 0; }
};
document.getElementById( "prom-btn" ).onclick = start( promLoop );
document.getElementById( "task-btn" ).onclick = start( taskLoop );
function start( fn ) {
return (evt) => {
i = 0;
now = performance.now();
fn();
};
}
// Posts a "macro-task".
// We could use setTimeout, but this method gets throttled
// to 4ms after 5 recursive calls.
// So instead we use either the incoming postTask API
// or the MesageChannel API which are not affected
// by this limitation
function postTask( task ) {
// Available in Chrome 86+ under the 'Experimental Web Platforms' flag
if( window.scheduler ) {
return scheduler.postTask( task, { priority: "user-blocking" } );
}
else {
const channel = postTask.channel ||= new MessageChannel();
channel.port1
.addEventListener( "message", () => task(), { once: true } );
channel.port2.postMessage( "" );
channel.port1.start();
}
}
<button id="prom-btn">use promises</button>
<button id="task-btn">use postTask</button>
<pre id="log"></pre>
So beware, using a Promise doesn't help at all with letting the event-loop actually loop.
Too often we see code using a batching pattern to not block the UI that fails completely its goal because it is assuming Promises will let the event-loop loop. For this, keep using setTimeout() as a mean to schedule a task, or use the postTask API if you are in a near future.
My understanding is that using await & async, makes the stack stop until the operation has completed.
Kind of... when awaiting a value it will add the remaining of the function execution to the callbacks attached to the awaited Promise (which can be a new Promise resolving the non-Promise value).
So the stack is indeed cleared at this time, but the event loop is not blocked at all here, on the contrary it's been freed to execute anything else until the Promise resolves.
This means that you can very well await for a never resolving promise and still let your browser live correctly.
async function fn() {
console.log( "will wait a bit" );
const prom = await new Promise( (res, rej) => {} );
console.log( "done waiting" );
}
fn();
onmousemove = () => console.log( "still alive" );
move your mouse to check if the page is locked
An await blocks only the current async function, the event loop continues to run normally. When the promise settles, the execution of the function body is resumed where it stopped.
Every async/await can be transformed in an equivalent .then(…)-callback program, and works just like that from the concurrency perspective. So while a promise is being awaited, other events may fire and arbitrary other code may run.
As other mentioned above... Promises are just like an event notification system and async/await is the same as then(). However, be very careful, You can "block" the event loop by executing a blocking operation. Take a look to the following code:
function blocking_operation_inside_promise(){
return new Promise ( (res, rej) => {
while( true ) console.log(' loop inside promise ')
res();
})
}
async function init(){
let await_forever = await blocking_operation_inside_promise()
}
init()
console.log('END')
The END log will never be printed. JS is single threaded and that thread is busy right now. You could say that whole thing is "blocked" by the blocking operation. In this particular case the event loop is not blocked per se, but it wont deliver events to your application because the main thread is busy.
JS/Node can be a very useful programming language, very efficient when using non-blocking operations (like network operations). But do not use it to execute very intense CPU algorithms. If you are at the browser consider to use Web Workers, if you are at the server side use Worker Threads, Child Processes or a Microservice Architecture.

Potential race conditions when Promise used in subscriptions in Javascript / TypeScript

I recently dived into subscriptions of Subject/BehaviorSubject/ etc and I am looking for the goto approach when used in combinations with Promises.
Given is the example code below:
firebase.user.subscribe((user: any | null) => {
fs.readFile('path/to/file')
.then((buf: Buffer) => {
this.modifySomeData = buf;
});
});
I subscribe to a Subject that triggers whenever the user logs in or out of their service. Whenever this happens, I read a file from disk. This readFile event could potentially take longer than the next "login/logout" event. Of course, I am in JS and in an asynchronous environment. This means, my user code is not multithreaded, but still, the 2nd user event and 2nd readFile could theoretically be faster than the first readFile.
First user event fired
First readFile is executed
Second user event is fired
Second readFile is executed
Second readFile is resolved <---
First readFile is resolved <---
The order is mixed up. The silliest approach I could think of is to create a uuid before reading the file and check inside the promise if this is still the same. If it's not I discard the data.
Is there a better solution?
If i have a process where older requests can be discarded i often keep a variable in scope to track the latest request and compare, similar to your UUID idea:
let lastRead: Promise<Buffer> | null = null;
firebase.user.subscribe((user: any | null) => {
const read = lastRead = fs.readFile('path/to/file');
read.then((buf: Buffer) => {
if (read != lastRead)
return;
this.modifySomeData = buf;
});
});
In this specific case, readFile also supports an abort signal. So you might also be able to abort the last request instead; you will still need to track it though.
The first approach is to see if your event generation logic could handle waiting for event handling. For example, you can use a promise to wait for the event OR generate another event, say doneReadFile and only then send the next event. Usually, this is not the case for a generic (distributed) environment.
If event generation does not care about how long it took to handle events, you can still use the above approach but check for the intermediate event doneReadFile in the next event handler (login/logout). This can be achieved by implementing some kind of polling or busy-wait/sleep

What does it mean that all event handlers are fired synchronously?

I am confused about some terms. I am trying to find out how the event system of Node.js actually works, and in a lot of places I read that the event handlers are totally synchronous.
For me that seemed really strange, because one of the advantages of using an event-driven approach would be that the main thread would not be blocked by events. So I tried to come up with my own example, and it seems like that what did happen was what I actually expected:
const fs = require('fs')
const util = require('util')
const readFile = util.promisify(fs.readFile)
const events = require('events')
const emitter = new events.EventEmitter()
emitter.on('fire', () => {
readFile('bigFile.txt')
.then(() => console.log('Done reading bigFile.txt'))
.catch(error => console.log(error))
console.log('Sync thing in handler')
})
emitter.on('fire', () => {
console.log('Second handler')
})
console.log('First outside')
emitter.emit('fire')
console.log('Last outside')
Note that bigFile.txt is an actually large text file, processing it takes a few hundred milliseconds on my machine.
Here I first log out 'First outside' synchronously. Then I raise the event which starts the event handling process. The event handler does seem to be asynchronous, because even though we first log out the synchronous 'Sync thing in handler' text, we start using the thread pool in the background to return back with the result of reading the file later. After running the first handler, the second handler runs printing out its message, and finally we print out the last sync message, 'Last outside'.
So I started with trying to prove what some people say, which is that event handlers are by nature synchronous, and then I found them to be asynchronous. My best guess is that either people saying that the event system is synchronous mean something else, or that I have some conceptual misunderstanding. Please help me understand this issue!
The EventEmitter class is synchronous in regard to the emit function: event handlers are called synchronously from within the .emit() call, as you've demonstrated with the fire event you fired yourself.
In general, events that come from the operating system (file and network operations, timers etc) through node's event loop are fired asynchronously. You're not firing them yourself, some native API does fire them. When you listen to these events, you can be sure that they will occur not before the next tick.
The event handler does seem to be asynchronous, because even though we first log out the synchronous 'Sync thing in handler' text, we start using the thread pool in the background to return back with the result of reading the file later
Yes, you are calling the asynchronous function readFile (that will notify you later), but that doesn't make your event listener function or the .emit('fire') call asynchronous. Even "asynchronous functions" that start a background process will immediately (synchronously) return something - often nothing (undefined) or a promise.

defer an async call in node until all awaits from caller finish

TL;DR: is it possible to say "call that function once all awaits in the current context finish" in JS/node?
A very simplified example:
a frontend-facing service creates a new user, then does another async task ([1]) and returns
a user service validates & saves the new user, then fires an event ([2]) that can trigger some other logic (unrelated to the current request)
goal: [1] should always finish before handlers for [2] start running
class Service1 {
// say this is called from a controller / express route handler
async createUser(userData: NewUserData): Promise<UserWithAdditionalData> {
const user = await this.userSerivce.validateAndSaveUser(userData);
// [1] this is also async, and should finish before [2] handlers start running
const userWithData = await this.getSomeAdditionalData(user);
return userWithData;
}
}
class UserService {
async validateAndSaveUser(userData: NewUserData): Promise<User> {
const validatedData = await this.validateNewUserData(userData);
await this.dbService.saveNew(validatedData)
// [2] trigger an event/hook to be executed later
this.eventBus.onUserCreated();
}
}
The question: is it possible to implement/replace [2] in a way to achieve the mentioned goal? Preferably in a better way than scheduling the event a few seconds in the future :D
In my current implementation, I'm using an event bus library, that calls registered event consumers when an event is triggered. However, since it's probably just pushing the callback onto the event loop under the hood, it's likely to be executed before [1] because both will just be queued onto the event loop. For the same reason, the same happens if I wrap the handlers in setTimeout(..., 0) or setImmediate. What I want to achieve, is that the event handlers should be fired after all the awaits from the caller are finished.
// please let's avoid discussing if the pattern above is a good design -- like most things, it can be used both in good and bad ways ;)

Javascript: What happens to the old object when I create a new instance?

I'm not even sure how to phrase this question so I'll have to go with examples. This might not look like useful code here, and indeed it isn't, but it's an example of a problem I've just encountered, stripped down to the bare essentials.
Lets' assume I have
function myObject(params) {
.. do some time-consuming asynchronous stuff
with AJAX and the like ...
return (before all the above is completed);
}
function myFunction(params) {
var doTheSlowStuff = new myObject(params);
}
myFunction(firstParams);
myFunction(moreParams);
What happens to the first myObject when I make the second call to myFunction()? Does it get a chance to complete its work (and if so will it be garbage collected when it has)? Or does it get unceremoniously dumped before it has a chance to finish what it started?
All the time-consuming asynchronous stuff will happen asynchronously :)
That means that the async calls (such as XHR or setTimeout) return instantly and allow execution to continue. In other words, the myObject constructor will return very quickly so there will be no delay between constructing the two myObjects. After both myFunctions return, then finally control will return to the event loop and the JavaScript engine will continue processing events, like mouse clicks, WebSocket events, timers like setTimeout, or XHR requests coming back. Your asynchronous callbacks won't be executed until you return control to the event loop, so don't do anything crazy like
while(true) {
// Check XHR status
}
Don't worry about garbage collection; if you have a DOM event like an AJAX (XHR) request with your myObject in scope then it won't be garbage collected until the event handler itself is garbage collected.
In this specific instance, you will create two instances of myObject which will be retained in memory until your application exits.
You can prove this by running something asynchronously to test this behaviour:
function myObject(params) {
// do something async, like output
// every second ...
var callback = function () {
console.log("I am object " + params);
setTimeout(callback, 1000);
};
callback ();
}
function myFunction(params) {
var doTheSlowStuff = new myObject(params);
}
myFunction(1);
myFunction(2);
// etc.
See a working example at: http://jsbin.com/osEFuWib/1/edit
The asynchronous code has access to a callback function. The callback will run and is independent of any other object. "Losing" the object or creating a new object does not change this. As such, pending asynchronous operations must be explicitly cancelled or the callbacks must be guarded against performing unwanted effects when they are invoked.
The thing about objects in JavaScript is simple: as long as any code - including the callback - can access the object (e.g. assigned to a variable in scope, a window property, or bound to the DOM), they remain accessible. Otherwise, they are unreachable and will be reclaimed at some point.

Categories

Resources