Node.js Promise setTimeout resolves quicker than expected - javascript

I have the following code in Node.js.
const timeout = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
I'm trying to test this code with the following test:
it("Should wait for given time before resolving", async () => {
const MS = 100;
const start = process.hrtime();
await timeout(MS);
const diff = process.hrtime(start);
expect(((diff[0] * NS_PER_SEC) + diff[1]) / 1000000).to.at.least(MS);
});
The problem is sometimes (rarely), this test fails:
Should wait for given time before resolving:
AssertionError: expected 99.595337 to be at least 100
+ expected - actual
-99.595337
+100
Obviously this is some type of timing issue with Node.js or something. If anything I expect await timeout(MS); to take slightly longer than MS. In no case do I expect it to take less time.
What is it about the internals of JavaScript/Node.js that causes this to happen?
This occurred on macOS 10.14.3 running Node.js version 8.15.1.

Related

Jasmine: how to test async function?

In my NodeJS app I've got the following heplers.ts file with one method, wait:
export const wait = async (ms: number) => {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
};
I'm currently writing a unit test to this file and this is what I have now:
import { setProcessEnv } from 'spec/helpers';
import { wait } from '../../util/helpers';
describe('Helper methods', () => {
beforeEach(() => {
jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000;
setProcessEnv();
});
it('should wait a specified amount of time', async () => {
const TIMEOUT = 10000;
jasmine.clock().install(); // First install the clock
await wait(TIMEOUT);
jasmine.clock().tick(10000); // waits till 10000 milliseconds
expect(wait).toHaveBeenCalled();
jasmine.clock().uninstall(); // uninstall clock when done
});
});
But I'm constantly receiving
Error: Timeout - Async function did not complete within 20000ms (set
by jasmine.DEFAULT_TIMEOUT_INTERVAL)
I've increased jasmine.DEFAULT_TIMEOUT_INTERVAL to 20000 ms, but it still didn't work.
How such async functions could be tested?
I've found coupes examples, but they didn't work in my case: How to test a function which has a setTimeout with jasmine?
UPDATE
This is my latest version that doesn't throw an error, but the problem is that it doesn't cover return statement lines (return new Promise(...):
it('should wait a specified amount of time', async () => {
const TIMEOUT = 10000;
// jasmine.clock().install(); // First install the clock
const wait = jasmine.createSpy();
await wait(TIMEOUT);
// jasmine.clock().tick(1000); // waits till 10000 milliseconds
expect(wait).toHaveBeenCalled();
expect(wait).toHaveBeenCalledWith(TIMEOUT);
// jasmine.clock().uninstall(); // uninstall clock when done
});
The problem is that by using jasmine's custom clock you need to manually call .tick() to move the time forward. Since you immediately await the wait call, the setTimeout within wait will not be reached. Thus, the promise never resolves, as you call .tick() after awaiting the promise.
You can fix this by not immediately awaiting the the promise returned from wait but instead assigning it to a variable and then moving the time ahead. Then, await the promise and check if wait has been called:
describe('Helper methods', () => {
it('should wait a specified amount of time', async () => {
const TIMEOUT = 10000;
jasmine.clock().install(); // First install the clock
const waitPromise = wait(TIMEOUT);
jasmine.clock().tick(10000); // waits till 10000 milliseconds
await waitPromise; // now await the promise
expect(wait).toHaveBeenCalled();
jasmine.clock().uninstall(); // uninstall clock when done
});
});
Note that in the above code no spy has been set up on wait, so .toHaveBeenCalled will probably fail. Check this link if you need help setting up a spy on a function: https://stackoverflow.com/a/43532075/3761628
To answer the updated question: You've incorrectly set up the spy. You need to change it to something like:
import { setProcessEnv } from 'spec/helpers';
import * as WaitFunction from from '../../util/helpers';
...
it('should wait a specified amount of time', async () => {
const TIMEOUT = 10000;
const waitSpy = spyOn(WaitFunction, 'wait').and.callThrough();
await WaitFunction.wait(TIMEOUT);
expect(waitSpy).toHaveBeenCalled();
expect(waitSpy).toHaveBeenCalledWith(TIMEOUT);
});

Node.js child process hanging, need debugging ideas

This question might be kinda weird because I'm not quite sure how to ask it. I'm looking for help isolating a bug in some Node.js code I'm working with that spawns some child processes that run some jobs in parallel. The bug is essentially that the last process spawned by the code hangs indefinitely when the code is run.
To clarify what I mean by "hangs indefinitely", I mean that the process never exits, or at least never appears to do so at the terminal.
The method implemented for this was taken from a SO answer to begin with, though I can't find it to link it at the moment. I've coded a dummy version without the implementation specifics:
const { spawn } = require('child_process');
const tasks = ['task.js', 'task.js', 'task.js', 'task.js', 'task.js', 'task.js', 'task.js'];
function doThings(tasks, pool){
let numRunning = 0;
let tasksRun = 0;
function doMoreThings(){
while (tasksRun < tasks.length && numRunning < pool){
const runnerCmd = `node`
const params = [`${tasks[tasksRun]}`]
console.log(`Starting task ${tasksRun + 1} out of ${tasks.length}`)
const child = spawn(runnerCmd, params, {
stdio: 'inherit',
detached: true
})
++numRunning;
++tasksRun;
child.on('exit', code=>{
--numRunning;
doMoreThings();
}).on('error', err=>{
console.log(`${err}`);
doMoreThings();
})
}
}
doMoreThings();
}
doThings(tasks, 3);
And here is the task.js file:
console.log('Task starting...');
const max = 5000;
const min = 1000;
const sleep = (waitTimeInMs) => new Promise(resolve => setTimeout(resolve, waitTimeInMs));
const timeval = Math.floor(Math.random() * (max - min) + min);
console.log(`Task interval: ${timeval}ms`);
sleep(timeval).then(() => {
console.log(`Task completing after ${timeval}ms`);
});
The above dummy code all works as expected. The processes run, their output is printed to the terminal, and when the last process exits, the terminal returns to a command prompt from the running state.
So, finally, what I'm hoping for are ideas from anyone more experienced with Node child processes or parallel job execution in general on how a bug could be introduced to the above code that would cause the last job in the chain to never exit. Or maybe the bug is already there in some race condition I'm not perceiving that just doesn't manifest with such a simple test? Been trying to debug this for a while and I don't have any more ideas about what to try. Thanks in advance to anyone who even attempts to help.

Repeat function itself without setInterval based on its finish

I need my program to repeat itself continuously. My program starts to fetch proxies from servers and saves them to a database and then send those saved proxies to another server again. So I don't know how long it takes for my program to do this task.
I wanna know what happens if any problem happens that makes this startJob() function take more than 30 seconds.
Does setInterval call it again or waits for function to finish?
What's the best approach for my program to repeat itself after it's done without setInterval?
(for exmaple startJob() being called again after it's done.)
I was wondering if it is ok to put this function in a loop with a big number like:
for ( let i = 0 ; i < 999999999 ; i ++ ) {
await startJob()
}
Here is my code:
const startJob = async () => {
await postProxyToChannel()
grabProxies()
}
setInterval(function(){
startJob()
}, (30000))
grabProxies() takes about 10 seconds and postProxyToChannel() takes about 5 seconds on my server.
No matter what happens inside startJob, setInterval will call it every 30 seconds. This means that postProxyToChannel will be called every 30 seconds. If that function throws, you'll get an unhandled Promise rejection, but the interval will continue.
Even if postProxyToChannel takes, say, 45 seconds, that won't prevent startJob from being called again before the prior startJob has completed.
If you want to make sure that startJob is only called 30 seconds after it finishes, you could await it in your for loop, then await a Promise that resolves every 30 seconds:
(async () => {
for ( let i = 0 ; i < 999999999 ; i ++ ) {
await startJob();
await new Promise(resolve => setTimeout(resolve, 30000));
}
})()
.catch((err) => {
console.log('There was an error', err);
});
But it would probably make more sense just to have a recursive call of startJob, eg:
const startJob = async () => {
try {
await postProxyToChannel();
} catch(e) {
// handle error
}
grabProxies();
setTimeout(startJob, 30000);
};
startJob();
Yup an infinite loop sounds good, that can be compared with a timer to pause the loop:
const timer = ms => new Promise(resolve => setTimeout(resolve, ms));
(async function() {
while(true) {
await postProxyToChannel();
await grabProxies();
await timer(30000);
}
})();
Now that loop will run the task, wait 30secs, then do that again. Therefore the loop will not run every 30secs but will usually take longer. To adjust that, you could measure the time the task took, then await the rest of the time:
const start = Date.now();
await postProxyToChannel();
await grabProxies();
await timer(30000 - (Date.now() - start));

Time delay in Truffle Tests

I am writing test cases for my contract and have to delay the assertion check because it is time sensitive. getCompletedCampaigns() will have the address of the Campaign whose deadline has passed.
it("Time sensitive check", async () => {
var deadLine = Math.round(Date.now() / 1000) + 3;
let eventDetails = await contract.createCampaign("Campaign name",deadLine,
{
from: accounts[0]
});
addressFromEvent = eventDetails['logs'][1]['args']['campaignAddress'];
async function checker() {
let deployedCampaigns = await factory.getCompletedCampaigns();
assert.equal(addressFromEvent, deployedCampaigns[0]);
}
function timeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function sleep() {
await timeout(5000);
return checker();
}
sleep();
});
The test passes even if the assertion is supposed to fail. The assertion happens after the test suite has finished executing all tests and forces the prompt to come out of truffle develop console because if it had failed. In the below testing I ve failed the test on purpose.
Contract: Testing CrowdCoin
✓ CampaignFactory deployment
✓ Create a new Campaign (168ms)
✓ Get ongoing Campaigns (246ms)
✓ Get completed Campaigns (189ms)
4 passing (1s)
truffle(develop)>
/home/vagrant/code/test/crowdcoin.test.js:82
assert.equal(0, deployedCampaigns[1]);
^
AssertionError: expected 0 to equal '0x806ea81c279b6000b9fd9f14d2845dec87fc3544'
at checker (/home/vagrant/code/test/crowdcoin.test.js:82:11)
at process._tickCallback (internal/process/next_tick.js:68:7)
How do I make sure the test check happens along with time delay?
it("Time sensitive check", async () => {
var deadLine = Math.round(Date.now() / 1000) + 3;
let eventDetails = await contract.createCampaign("Campaign name",deadLine,
{
from: accounts[0]
});
function timeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
await timeout(5000);
addressFromEvent = eventDetails['logs'][1]['args']['campaignAddress'];
let deployedCampaigns = await factory.getCompletedCampaigns();
await assert.equal(addressFromEvent, deployedCampaigns[0]);
});
By moving the entire delay logic to a different method and all logical statements preceded by await, the execution of the said logical statements and test completion is not done unless timeout() has completed.
Actually you can try using time in #openzeppelin/test-helper to speed up your test case as you don't necessary wait for 3 secs running on this test case.
const { time } = require("#openzeppelin/test-helpers");
it("Time sensitive check", async () => {
let duration = time.duration.seconds(3);
let eventDetails = await contract.createCampaign("Campaign name",duration,
{
from: accounts[0]
});
await time.increase(duration);
addressFromEvent = eventDetails['logs'][1]['args']['campaignAddress'];
let deployedCampaigns = await factory.getCompletedCampaigns();
await assert.equal(addressFromEvent, deployedCampaigns[0]);
});
which make more sense on testing timestamp in blockchain.
Doc: https://docs.openzeppelin.com/test-helpers/0.5/api#time
An example of how #openzeppelin team work on test case that you can look into:
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/24a0bc23cfe3fbc76f8f2510b78af1e948ae6651/test/token/ERC20/utils/TokenTimelock.test.js

How to test rate limited HTTP request function?

I have an external service I am making HTTP requests to from Node.js. The service has current limitations that only 10 requests per second can be made. I have a naive rate limiter I wrote that I am trying to test, but falling down on timing of it. I know that Javascript times are not very accurate, but I'm getting wildly different swings, in the range of up to 50 milliseconds different.
Here's the gist of what I'm doing:
var RATE_LIMIT_MS = 100 // No more than 10 requests per second
var NEXT_WAIT_MS = 0
function goGetRequest(...) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
function complete() {
// Rollback next time after this request finishes
NEXT_WAIT_MS = Math.max(0, NEXT_WAIT_MS - RATE_LIMIT_MS)
}
// ... requests are fired off here asynchronously...
http.get(...).then(complete)
}, NEXT_WAIT_MS)
// Push time back on the queue
NEXT_WAIT_MS += RATE_LIMIT_MS
})
}
var chai = require('chai')
var expect = chai.expect
it('should rate limit requests properly', function() {
var iterations = [0, 1, 2, 3, 4]
var lastResponseMs = 0
var promises = iterations.map(function(i) {
return goGetRequest(...).
then(function(result) {
// Diff the times
var thisResponseMs = Date.now()
var thisDiffMs = Math.abs(thisResponseMs - lastResponseMs)
expect(wrapper.results).to.not.be.empty
expect(thisDiffMs, 'failed on pass ' + i).to.be.at.least(RATE_LIMIT_MS)
// Move last response time forward
lastResponseMs = thisResponseMs
})
})
return Promise.all(promises)
})
What happens next is that the tests will fail at random passes. A time diff on 92 milliseconds on pass 2, a time diff of 68 milliseconds on pass 4.... what am I missing here? Thanks!
Javascript setTimeout and setInterval (as with any non-realtime code) is never precise. If you're using NodeJS however, you can try using Nanotimer:
https://www.npmjs.com/package/nanotimer
var NanoTimer = require('nanotimer');
var timerA = new NanoTimer();
timerA.setTimeout(yourFunction, '', NEXT_WAIT_MS);
Alternatively, I suggest simply testing that rate limit occurs and not to worry too much about exact precision. If you spam it with 11 requests consecutively (which should hopefully take less than one second), and it gets blocked, and one second later it is fine, then your test can be considered passing.
One final solution is to use exec() and call the OS sleep command, which is significantly more precise.

Categories

Resources