Let's say I have a long running loop:
// Let's say this loop takes 10 seconds to execute
for(let i = 0; i <= 1000000; ++i) {
const garbage = { i };
// some other code
}
Can the garbage collector run during the loop, or it can only run when the application is idle?
I didn't find any documentation related to this, but because Node.js has the --nouse-idle-notification which in theory disables GC, makes me think that the GC only runs when the idle notification is sent (when the main thread is not busy).
I am asking this because my loop sometimes has spikes in execution time and want to know if it's possible that the GC might run during the loop, resulting in the lag spike.
V8 developer here. The short answer is that the GC can run at any time and will run whenever it needs to.
Note that the GC is a fairly complex system: it performs several different tasks, and does most of them in incremental steps and/or concurrently with the main thread. In particular, every allocation can trigger a bit of incremental GC work. (Which implies that by very carefully avoiding all allocations, you can construct loops that won't cause GC activity while they run; but it's never the case that loops accumulate garbage that can't get collected -- unless you have a leak in your code of course, where objects are unintentionally being kept reachable.)
Can the garbage collector run during the loop, or it can only run when the application is idle?
It absolutely can and will run during the loop.
Node.js has the --nouse-idle-notification which in theory disables GC
No, it does not. There is no way to disable GC. That flag disables one particular mechanism for triggering GC activity, but that only means that GC will be triggered by other mechanisms.
the GC only runs when the idle notification is sent (when the main thread is not busy)
No, the idea is to run some extra GC cycles when there is idle time, to save some memory when the application is not busy.
my loop sometimes has spikes in execution time and want to know if it's possible that the GC might run during the loop, resulting in the lag spike
That could be. It could possibly also have to do with optimization or deoptimization of the function. Or it could be something else -- the operating system interrupting your process or assigning it to another CPU core, for example, or hundreds of other reasons. Computers are complex machines ;-)
if you set a variable to null -- garbage collection is done immediately
No, it is not. Garbage collection is never done immediately (at least not in V8).
As a concept the garbage collector works in a separate thread since in this way it will not block the main thread (UI thread in most cases).
As for your example, there is no problem for the garbage collection thread running in "parallel" to this loop since the value there const garbage = {key: i} will not get removed as long as it is being referenced.
Also note that there are several generations that the garbage collector passes your values through before removing them completely.
Related
Any idea on garbage collector timer in javascript?
Suppose i run below script, will function and associated scope chained variable will go
for garbage collection exactly after 100ms? Or some margin?
I read one thread about garbage collection in stackoverflow, still i have this question.
Below are my questions?
Does any SYSTEM TIMER run for garbage collection task?
If no, is it EVENT based?, means if reference is no more present, garbage
collector will reclaim memory INSTANTLY.
function call_me() {
//calculate elapsed_time - code not given
if(elapsed_time <100)
{
setTimeout(call_me,25);
}
else{
final_call();
}
}
call_me();
Every user agent implements garbage collection differently. All user agents use the mark-and-sweep method on a periodic repetition, so there is no "instantly" about it; it will happen when it happens.
Each agent has different thresholds and mechanisms to determine when the GC does a pass. It isn't necessarily event-driven (purhaps you might say it is benchmark-driven, event-initiated), and certainly not based on a timer.
A function that passes out of scope is instantly eligible for garbage collection, but there's really no telling when it would happen.
This is really something that, from the developer perspective, you are not intended to think about. There isn't any way to stop or start GC, or any indication that it happened at all. Check out about:memory in Firefox for some interesting trivia (and there's a couple of dubious buttons down there to "control" the GC). That's about all you're going to get as far as it goes under the hood, and that data isn't available to scripts.
The garbage collector is non-deterministic.
Garbage will be collected some time after it becomes garbage.
A closure object passed to setTimeout will become garbage after it executes.
Anything beyond that is implementation-specific.
I need help understanding how Bull Queue (bull.js) processes concurrent jobs.
Suppose I have 10 Node.js instances that each instantiate a Bull Queue connected to the same Redis instance:
const bullQueue = require('bull');
const queue = new bullQueue('taskqueue', {...})
const concurrency = 5;
queue.process('jobTypeA', concurrency, job => {...do something...});
Does this mean that globally across all 10 node instances there will be a maximum of 5 (concurrency) concurrently running jobs of type jobTypeA? Or am I misunderstanding and the concurrency setting is per-Node instance?
What happens if one Node instance specifies a different concurrency value?
Can I be certain that jobs will not be processed by more than one Node instance?
The TL;DR is: under normal conditions, jobs are being processed only once. If things go wrong (say Node.js process crashes), jobs may be double processed.
Quoting from Bull's official README.md:
Important Notes
The queue aims for an "at least once" working strategy. This means that in some situations, a job could be processed more than once. This mostly happens when a worker fails to keep a lock for a given job during the total duration of the processing.
When a worker is processing a job it will keep the job "locked" so other workers can't process it.
It's important to understand how locking works to prevent your jobs from losing their lock - becoming stalled - and being restarted as a result. Locking is implemented internally by creating a lock for lockDuration on interval lockRenewTime (which is usually half lockDuration). If lockDuration elapses before the lock can be renewed, the job will be considered stalled and is automatically restarted; it will be double processed. This can happen when:
The Node process running your job processor unexpectedly terminates.
Your job processor was too CPU-intensive and stalled the Node event loop, and as a result, Bull couldn't renew the job lock (see #488 for how we might better detect this). You can fix this by breaking your job processor into smaller parts so that no single part can block the Node event loop. Alternatively, you can pass a larger value for the lockDuration setting (with the tradeoff being that it will take longer to recognize a real stalled job).
As such, you should always listen for the stalled event and log this to your error monitoring system, as this means your jobs are likely getting double-processed.
As a safeguard so problematic jobs won't get restarted indefinitely (e.g. if the job processor aways crashes its Node process), jobs will be recovered from a stalled state a maximum of maxStalledCount times (default: 1).
Bull is designed for processing jobs concurrently with "at least once" semantics, although if the processors are working correctly, i.e. not stalling or crashing, it is in fact delivering "exactly once". However you can set the maximum stalled retries to 0 (maxStalledCount https://github.com/OptimalBits/bull/blob/develop/REFERENCE.md#queue) and then the semantics will be "at most once".
Having said that I will try to answer to the 2 questions asked by the poster:
What happens if one Node instance specifies a different concurrency value?
I will assume you mean "queue instance". If so, the concurrency is specified in the processor. If the concurrency is X, what happens is that at most X jobs will be processed concurrently by that given processor.
Can I be certain that jobs will not be processed by more than one Node instance?
Yes, as long as your job does not crash or your max stalled jobs setting is 0.
I spent a bunch of time digging into it as a result of facing a problem with too many processor threads.
The short story is that bull's concurrency is at a queue object level, not a queue level.
If you dig into the code the concurrency setting is invoked at the point in which you call .process on your queue object. This means that even within the same Node application if you create multiple queues and call .process multiple times they will add to the number of concurrent jobs that can be processed.
One contributor posted the following:
Yes, It was a little surprising for me too when I used Bull first
time. Queue options are never persisted in Redis. You can have as many
Queue instances per application as you want, each can have different
settings. The concurrency setting is set when you're registering a
processor, it is in fact specific to each process() function call, not
Queue. If you'd use named processors, you can call process() multiple
times. Each call will register N event loop handlers (with Node's
process.nextTick()), by the amount of concurrency (default is 1).
So the answer to your question is: yes, your processes WILL be processed by multiple node instances if you register process handlers in multiple node instances.
Ah Welcome! This is a meta answer and probably not what you were hoping for but a general process for solving this:
Read the documentation ultra carefully to identify which guarantees your solution aims to provide:
You can specify a concurrency argument. Bull will then call your
handler in parallel respecting this maximum value.
I personally don't really understand this or the guarantees that bull provides. Since it's not super clear:
Dive into source to better understand what is actually happening. I usually just trace the path to understand:
https://github.com/OptimalBits/bull/blob/f05e67724cc2e3845ed929e72fcf7fb6a0f92626/lib/queue.js#L629
https://github.com/OptimalBits/bull/blob/f05e67724cc2e3845ed929e72fcf7fb6a0f92626/lib/queue.js#L651
https://github.com/OptimalBits/bull/blob/f05e67724cc2e3845ed929e72fcf7fb6a0f92626/lib/queue.js#L658
... more this is pretty big :p
If the implementation and guarantees offered are still not clear than create test cases to try and invalidate assumptions it sounds like:
Initialize process for the same queue with 2 different concurrency values
Create a queue and two workers, set a concurrent level of 1, and a callback that logs message process then times out on each worker, enqueue 2 events and observe if both are processed concurrently or if it is limited to 1
IMO the biggest thing is:
Can I be certain that jobs will not be processed by more than one Node
instance?
If exclusive message processing is an invariant and would result in incorrectness for your application, even with great documentation, I would highly recommend to perform due diligence on the library :p
Looking into it more, I think Bull doesn't handle being distributed across multiple Node instances at all, so the behavior is at best undefined.
Lets assume I run this piece of code.
var score = 0;
for (var i = 0; i < arbitrary_length; i++) {
async_task(i, function() { score++; }); // increment callback function
}
In theory I understand that this presents a data race and two threads trying to increment at the same time may result in a single increment, however, nodejs(and javascript) are known to be single threaded. Am I guaranteed that the final value of score will be equal to arbitrary_length?
Am I guaranteed that the final value of score will be equal to
arbitrary_length?
Yes, as long as all async_task() calls call the callback once and only once, you are guaranteed that the final value of score will be equal to arbitrary_length.
It is the single-threaded nature of Javascript that guarantees that there are never two pieces of Javascript running at the exact same time. Instead, because of the event driven nature of Javascript in both browsers and node.js, one piece of JS runs to completion, then the next event is pulled from the event queue and that triggers a callback which will also run to completion.
There is no such thing as interrupt driven Javascript (where some callback might interrupt some other piece of Javascript that is currently running). Everything is serialized through the event queue. This is an enormous simplification and prevents a lot of stickly situations that would otherwise be a lot of work to program safely when you have either multiple threads running concurrently or interrupt driven code.
There still are some concurrency issues to be concerned about, but they have more to do with shared state that multiple asynchronous callbacks can all access. While only one will ever be accessing it at any given time, it is still possible that a piece of code that contains several asynchronous operations could leave some state in an "in between" state while it was in the middle of several async operations at a point where some other async operation could run and could attempt to access that data.
You can read more about the event driven nature of Javascript here: How does JavaScript handle AJAX responses in the background? and that answer also contains a number of other references.
And another similar answer that discusses the kind of shared data race conditions that are possible: Can this code cause a race condition in socket io?
Some other references:
how do I prevent event handlers to handle multiple events at once in javascript?
Do I need to be concerned with race conditions with asynchronous Javascript?
JavaScript - When exactly does the call stack become "empty"?
Node.js server with multiple concurrent requests, how does it work?
To give you an idea of the concurrency issues that can happen in Javascript (even without threads and without interrupts, here's an example from my own code.
I have a Raspberry Pi node.js server that controls the attic fans in my house. Every 10 seconds it checks two temperature probes, one inside the attic and one outside the house and decides how it should control the fans (via relays). It also records temperature data that can be presented in charts. Once an hour, it saves the latest temperature data that was collected in memory to some files for persistence in case of power outage or server crash. That saving operation involves a series of async file writes. Each one of those async writes yields control back to the system and then continues when the async callback is called signaling completion. Because this is a low memory system and the data can potentially occupy a significant portion of the available RAM, the data is not copied in memory before writing (that's simply not practical). So, I'm writing the live in-memory data to disk.
At any time during any of these async file I/O operations, while waiting for a callback to signify completion of the many file writes involved, one of my timers in the server could fire, I'd collect a new set of temperature data and that would attempt to modify the in-memory data set that I'm in the middle of writing. That's a concurrency issue waiting to happen. If it changes the data while I've written part of it and am waiting for that write to finish before writing the rest, then the data that gets written can easily end up corrupted because I will have written out one part of the data, the data will have gotten modified from underneath me and then I will attempt to write out more data without realizing it's been changed. That's a concurrency issue.
I actually have a console.log() statement that explicitly logs when this concurrency issue occurs on my server (and is handled safely by my code). It happens once every few days on my server. I know it's there and it's real.
There are many ways to work around those types of concurrency issues. The simplest would have been to just make a copy in memory of all the data and then write out the copy. Because there are not threads or interrupts, making a copy in memory would be safe from concurrency (there would be no yielding to async operations in the middle of the copy to create a concurrency issue). But, that wasn't practical in this case. So, I implemented a queue. Whenever I start writing, I set a flag on the object that manages the data. Then, anytime the system wants to add or modify data in the stored data while that flag is set, those changes just go into a queue. The actual data is not touched while that flag is set. When the data has been safely written to disk, the flag is reset and the queued items are processed. Any concurrency issue was safely avoided.
So, this is an example of concurrency issues that you do have to be concerned about. One great simplifying assumption with Javascript is that a piece of Javascript will run to completion without any thread of getting interrupted as long as it doesn't purposely return control back to the system. That makes handling concurrency issues like described above lots, lots easier because your code will never be interrupted except when you consciously yield control back to the system. This is why we don't need mutexes and semaphores and other things like that in our own Javascript. We can use simple flags (just a regular Javascript variable) like I described above if needed.
In any entirely synchronous piece of Javascript, you will never be interrupted by other Javascript. A synchronous piece of Javascript will run to completion before the next event in the event queue is processed. This is what is meant by Javascript being an "event-driven" language. As an example of this, if you had this code:
console.log("A");
// schedule timer for 500 ms from now
setTimeout(function() {
console.log("B");
}, 500);
console.log("C");
// spin for 1000ms
var start = Date.now();
while(Data.now() - start < 1000) {}
console.log("D");
You would get the following in the console:
A
C
D
B
The timer event cannot be processed until the current piece of Javascript runs to completion, even though it was likely added to the event queue sooner than that. The way the JS interpreter works is that it runs the current JS until it returns control back to the system and then (and only then), it fetches the next event from the event queue and calls the callback associated with that event.
Here's the sequence of events under the covers.
This JS starts running.
console.log("A") is output.
A timer event is schedule for 500ms from now. The timer subsystem uses native code.
console.log("C") is output.
The code enters the spin loop.
At some point in time part-way through the spin loop the previously set timer is ready to fire. It is up to the interpreter implementation to decide exactly how this works, but the end result is that a timer event is inserted into the Javascript event queue.
The spin loop finishes.
console.log("D") is output.
This piece of Javascript finishes and returns control back to the system.
The Javascript interpreter sees that the current piece of Javascript is done so it checks the event queue to see if there are any pending events waiting to run. It finds the timer event and a callback associated with that event and calls that callback (starting a new block of JS execution). That code starts running and console.log("B") is output.
That setTimeout() callback finishes execution and the interpreter again checks the event queue to see if there are any other events that are ready to run.
Node uses an event loop. You can think of this as a queue. So we can assume, that your for loop puts the function() { score++; } callback arbitrary_length times on this queue. After that the js engine runs these one by one and increase score each time. So yes. The only exception if a callback is not called or the score variable is accessed from somewhere else.
Actually you can use this pattern to do tasks parallel, collect the results and call a single callback when every task is done.
var results = [];
for (var i = 0; i < arbitrary_length; i++) {
async_task(i, function(result) {
results.push(result);
if (results.length == arbitrary_length)
tasksDone(results);
});
}
No two invocations of the function can happen at the same time (b/c node is single threaded) so that will not be a problem. The only problem would be ifin some cases async_task(..) drops the callback. But if, e.g., 'async_task(..)' was just calling setTimeout(..) with the given function, then yes, each call will execute, they will never collide with each other, and 'score' will have the value expected, 'arbitrary_length', at the end.
Of course, the 'arbitrary_length' can't be so great as to exhaust memory, or overflow whatever collection is holding these callbacks. There is no threading issue however.
I do think it’s worth noting for others that view this, you have a common mistake in your code. For the variable i you either need to use let or reassign to another variable before passing it into the async_task(). The current implementation will result in each function getting the last value of i.
I have a number of functions calling the next one in a chain, processing a rather large set of data to an equally large set of different data:
function first_step(input_data, second_step_callback)
{
result = ... // do some processing
second_step_callback(result, third_step);
}
function second_step(intermediate_data, third_step_callback)
{
result = ... // do some processing
third_step_callback(result);
}
function third_step(intermediate_data) { }
first_step(huge_data, second_step);
In third_step I am running out of memory (Chrome seems to kill the tab when memory usage reaches about 1.5 GB).
I think, when reaching third_step(), the input_data from first_step() is still retained, because first_step() is on the call stack, isn't it? At least when the debugger is running, I can see the data.
Obviously I don't need it anymore. In first_step() there is no code after second_step_callback(result, third_step);. Maybe if I could free that memory, my tab might survive processing a data set of this size. Can I do this?
Without seeing a lot more of what you're really doing that is using memory, it's hard for us to tell whether you're just using too much memory or whether you just need to let earlier memory get freed up.
And, memory in Javascript is not "owned" by stack frames so the premise of the question seems to make a bit of a wrong assumption. Memory in Javascript is garbage collected and is eligible for GC when no live, reachable code still has a reference to the data and will get garbage collected the next time the garbage collector gets to run (during JS idle time).
That said, if you have code that makes a succession of nested function calls like your question shows, you can reduce the amount of memory usage, by doing some of these things:
Clear variables that hold large data (just set them to null) that are no longer needed.
Reduce the use of intermediate variables that hold large data.
Reduce the copying of data.
Reduce string manipulations with intermediate results because each one creates block of memory that then has to be reclaimed.
Clear the stack by using setTimeout() to run the next step in the chain and allow the garbage collector a chance to do its thing on earlier temporary variables.
Restructure how you process or store the data to fundamentally use less memory.
I'm making a game using canvas, and am running into strange Chrome behavior that I can't pin down. Sometimes when I load the game, the Chrome Task Manager will report that it's allocating +300MB a second, ramping up to over a few GB of memory in just a matter of 10 seconds or so, and it doesn't stop until the tab crashes.
When I try to run the javascript profiler, the problem stops. When I load the tab with the profiler running, it is perfectly stable. When the problem happens, and then I start the profiler, it will go from 1.5GB to a stable 40MB immediately. And the heap snapshot shows me what I would expect if the game was running stably.
My game is running on window.setInterval (I've tried requestAnimationFrame and recursive setTimeout and the problem still happens), and it happens more often with this is set high, meaning that when I set the game to 30FPS, this rarely ever happens, and when I set it to 60FPS, it happens over half the time. This is only happening on Chrome, Firefox seems fine.
How do I debug this, when Chrome seems to do garbage collection only when the profiler is running?
Also, I've noticed that some of my animations and keyboard inputs are a little funny when I push the FPS to 60. I assume that this could be related, but this is also the case in Firefox.
JavaScript is single-threaded which means all the work needs to be done on the same thread including queuing events (from setTimeout/rAF, keys etc.), rendering to canvas and so forth.
If the loop is very tight (time-budget-wise) there will simply not be any room for the browser to do other tasks such as GC - for Chrome this task seem to be secondary versus Firefox which gives this higher priority (likely to get more performance out of its engine). Basically the running code will block the browser from doing other things than executing the code itself.
A good indicator of this is when you lower the FPS leaving more space for event queue, clean-up etc. When the profiler is running it get more priority in order to catch all sort of things so for some reason GC gets to "sneak" in earlier when profiler is running (in lack of better term). But this is very browser specific and I do not know every underlying details here.
If the browser cannot purge events in the event queue it will eventually stack up and in worst case block/freeze/crash the browser.
In any case, it's hard to debug this (for pin-pointing reasons) as you won't, programmatically, have access to memory or CPU usage etc.
The closest thing is to use a high-resolution timer at the beginning and end of the code inside the loop to see if it comes close to the frame rate time.
For example:
function loop() {
var startTime = performance.now();
... other code ...
var innerLoopTime = performance.now() - startTime;
requestAnimationFrame(loop);
}
If your frame rate is 60 FPS then the time per frame would be 1000/60, or about 16.667ms.
If your innerLoopTime is very close to this time you will know that you need to optimize the code executed inside the loop, or lower the frame rate.
You could use the debugger to get time-cost per step inside the function but the debugger itself will add an overhead to the total. So do measuring the time, but the cost is lower.. it will be a matter of compromise no matter how one twist and turn this one.
I've found that a huge source of memory leaking in javascript code is usually closures.
If you're accessing a variable inside your setInterval that was declared outside, then you're most likely leaking some memory. As to whether or not this is the actual root cause of the issue is another question.
If you want to understand closures more and how they affect the performance of your js in action, look at this article by IBM on the topic. It gives good examples and ways to avoid memory leaks using them, as well as a few other possible sources of memory leaks.
We've noticed chrome + canvas is not as performant as firefox + canvas. As to GC occurring when you open chrome dev tools, well I'd guess you may have some code that nudges chrome in just the right way to do a GC. Do you have some sort of window resize handler? There might be something else that is doing something similar.
When in doubt bisect the code till it doesn't happen anymore.