Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: reduce memory use in test stringbytes #3697

Closed
wants to merge 1 commit into from
Closed

test: reduce memory use in test stringbytes #3697

wants to merge 1 commit into from

Conversation

mcornac
Copy link
Contributor

@mcornac mcornac commented Nov 6, 2015

When I run the string bytes tests dealing with strings of length kStringMaxLength or more on a Windows test machine with 4GB memory I see the tests skipped or failed with the following outputs respectively.

  • 1..0 # Skipped: intensive toString tests due to memory confinements
  • RangeError: Invalid array buffer length

The code being used to determine if the test should be skipped is:

 try {
  new Buffer(kStringMaxLength * 3);
 } catch(e) {
  assert.equal(e.message, 'Invalid array buffer length');
  console.log(
       '1..0 # Skipped: intensive toString tests due to memory confinements');
   return;
 }

A problem with this approach is that the garbage collector does not necessarily run between the tried allocation and the necessary allocations later. That means that even though no exception is thrown in the try block since the machine has enough memory, the later allocations fail since the memory is now taken.

To verify this, I ran the test with Release\node.exe --expose-gc test\parallel\test-stringbytes-external-at-max.js to expose global.gc() which I used after the try catch block. With this setup the test would either skip or succeed as desired.

To solve the issue, I removed the Buffer allocations that were used to check if the tests should be skipped and moved the actual Buffer allocations into the try block so the test can still be skipped if memory allocation fails here. The tests should now use less total memory and be less likely to be skipped and more importantly, less likely to fail due to a memory constraint when they are not skipped.

@targos
Copy link
Member

targos commented Nov 6, 2015

The fix looks good, but why did you change the assertions in the catch blocks?

@mscdex mscdex added test Issues and PRs related to the tests. buffer Issues and PRs related to the buffer subsystem. memory Issues and PRs related to the memory management or memory footprint. labels Nov 6, 2015
@mcornac
Copy link
Contributor Author

mcornac commented Nov 6, 2015

I'm not sure if there are disadvantages but I thought it could be helpful to rethrow the original exception in case a different exception occurs.
E.g.

/sandbox/cornacch/node/test/parallel/test-stringbytes-external-at-max.js:15
  if (e.message != 'Invalid array buffer length') throw (e);
                                                  ^
Error: toString failed
    at Buffer.toString (buffer.js:382:11)
    at Object.<anonymous> (/sandbox/cornacch/node/test/parallel/test-stringbytes-external-at-max.js:13:17)

Instead of

assert.js:89
  throw new assert.AssertionError({
  ^
AssertionError: 'toString failed' == 'Invalid array buffer length'
    at Object.<anonymous> (/sandbox/cornacch/node/test/parallel/test-stringbytes-external-at-max.js:15:10)

@mhdawson
Copy link
Member

mhdawson commented Nov 6, 2015

lgtm, I do think re-throwing makes sense so that we can see the original exception

@trevnorris
Copy link
Contributor

The reason behind the current implementation is that it helps account for the additional space in the v8 heap necessary to store all the strings that are created. I'd say the most reliable approach would be to run gc right after the try/catch.

@mcornac
Copy link
Contributor Author

mcornac commented Nov 9, 2015

@trevnorris I made some changes to the at-max test to use --expose-gc based on some other tests. Can you please take a look before I update the other tests?

@trevnorris
Copy link
Contributor

@thinkingdust Nice. Like the way you did that.

@mcornac
Copy link
Contributor Author

mcornac commented Nov 9, 2015

Thanks. I've pushed the changes to the other files. I also ran the tests on the Windows machine again and they always either skip or pass now as desired.

@trevnorris
Copy link
Contributor

I'd squash the commits and include a note about why the change to throwing instead of assert.

Other than that, LGTM.

} catch(e) {
assert.equal(e.message, 'Invalid array buffer length');
if (e.message != 'Invalid array buffer length') throw (e);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd add a comment explaining you don't use assert in order to preserve the original exception.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, and a femto style nit: !==

@bnoordhuis
Copy link
Member

Some style nits but basic premise LGTM.

@mcornac
Copy link
Contributor Author

mcornac commented Nov 10, 2015

@bnoordhuis Thanks for the suggestions, updated.

@bnoordhuis
Copy link
Member

@jasnell
Copy link
Member

jasnell commented Nov 12, 2015

Kicked off a new CI run... the previous run (693) was returning a 404: https://ci.nodejs.org/job/node-test-pull-request/712/

@targos
Copy link
Member

targos commented Nov 12, 2015

LGTM

@mcornac
Copy link
Contributor Author

mcornac commented Nov 12, 2015

I think there is only one related failure.
test-stringbytes-external-exceed-max-by-1-base64.js failed with a timeout here:
https://ci.nodejs.org/job/node-test-binary-arm/469/RUN_SUBSET=1,nodes=pi1-raspbian-wheezy/console
but the same test passed here:
https://ci.nodejs.org/job/node-test-binary-arm/469/RUN_SUBSET=1,nodes=pi2-raspbian-wheezy/console

@Fishrock123
Copy link
Contributor

Maybe just exclude this test on low memory machines? Something like

if (os.totalmem() <= 512000000 /* 512mb */) {
  // bail
  return;
}

(note: totalmem() returns bytes)

Edit actually maybe freemem() would be better? Idk.

@mhdawson
Copy link
Member

So if I understand correctly @thinkingdust we still saw a failure in the test even after the PR ?

@mcornac
Copy link
Contributor Author

mcornac commented Nov 16, 2015

It's a 404 now but there was a timeout failure in one of the tests on Raspberry Pi.
I don't know how v8 decides if a Buffer allocation is valid. The test needs about 756MB (3 * kStringMaxLength) while I'm guessing the Pi has 512MB but it seems like the test was not skipped but instead ran very slowly.

@trevnorris
Copy link
Contributor

@thinkingdust v8 doesn't have anything to do with memory allocation with Buffers. It's a simple malloc() call, and v8 uses whatever the system returns. In these cases I believe that the allocation is coming from swap space, hence why operations on the memory returned is incredibly slow. I think @Fishrock123's idea is worth trying out.

@mcornac
Copy link
Contributor Author

mcornac commented Nov 17, 2015

I added a freemem check for the at-max test and it works fine on my Linux machine. I can try it on the Windows machine tomorrow. Is this use of freemem reliable enough to remove the try catch blocks?

@mcornac
Copy link
Contributor Author

mcornac commented Nov 17, 2015

Using the freemem check works to always skip or pass on the original Windows machine.

@mcornac
Copy link
Contributor Author

mcornac commented Nov 17, 2015

@trevnorris I changed the at-max test to check using freemem instead of try/catch and gc. If that looks okay to you I'll update the other files too.

@mhdawson
Copy link
Member

Another failure on ARM today: https://ci.nodejs.org/job/node-test-binary-arm/81/RUN_SUBSET=4,nodes=pi2-raspbian-wheezy/console

Would be nice to get the fix in.

@jasnell
Copy link
Member

jasnell commented Nov 20, 2015

@trevnorris ... can you take another look at this when you get a moment? :-)

@Fishrock123
Copy link
Contributor

Some things: I think freemem may include cache and possibly swap, but I can confirm totalmem is just the hardware ram.

I think my original suggestion of just skipping on 512mb ram machines (ontop of the other protections) would be best.

@trevnorris
Copy link
Contributor

Had a follow up to @Fishrock123's comment, but still LGTM.

@mcornac
Copy link
Contributor Author

mcornac commented Dec 1, 2015

@trevnorris @Fishrock123 I added common.kMaxTestMemUsage. Can you take a look?

@jasnell
Copy link
Member

jasnell commented Dec 1, 2015

LGTM

@Fishrock123
Copy link
Contributor

Correct me if I'm wrong but I think kMaxTestMemUsage needs to be set-able from C++ to be of value?

@Fishrock123
Copy link
Contributor

Or at least an environment variable.

@trevnorris
Copy link
Contributor

@Fishrock123 Don't think so. We've honestly just selected the value based on past fail cases. Can't see why it would need to be configurable and/or need to be set from C++.

@Fishrock123
Copy link
Contributor

Ah, ok. I think exports.enoughTestMem = os.totalmem() > 0x20000000; might be better, but maybe don't hold the PR on it.

@trevnorris
Copy link
Contributor

So make the export simply a boolean? Can't say I'm against that, but also don't want to make @mhdawson @thinkingdust rage quit this PR. ;-)

EDIT: auto-select fail

@mcornac
Copy link
Contributor Author

mcornac commented Dec 2, 2015

@Fishrock123 @trevnorris

@Fishrock123
Copy link
Contributor

Rubber-stamp LGTM

@mcornac
Copy link
Contributor Author

mcornac commented Dec 2, 2015

🎆 ✨

@trevnorris
Copy link
Contributor

LGTM

@trevnorris
Copy link
Contributor

@thinkingdust i'm having a hard time segmenting these commits into discrete changes. Mind if I squash them all, or would you like to take care of it?

The current implementation of tests for strings with length at or
exceeding kStringMaxLength allocate a temporary buffer inside a try
block to skip the test if there is insufficient memory. This commit
adds an invocation of the garbage collector after the temporary buffer
is allocated so that memory is freed for later allocations.

Change the corresponding catch block to rethrow the original exception
instead of asserting the exception message to provide more information
about the exception.

Add an additional check before trying to allocate memory to immediately
skip the test on machines with insufficient total memory.
@mcornac
Copy link
Contributor Author

mcornac commented Dec 2, 2015

I think 1 commit works. Squashed.

@trevnorris
Copy link
Contributor

Thanks much! Landed in a40b9ca

@trevnorris trevnorris closed this Dec 2, 2015
trevnorris pushed a commit that referenced this pull request Dec 2, 2015
The current implementation of tests for strings with length at or
exceeding kStringMaxLength allocate a temporary buffer inside a try
block to skip the test if there is insufficient memory. This commit adds
an invocation of the garbage collector after the temporary buffer is
allocated so that memory is freed for later allocations.

Change the corresponding catch block to rethrow the original exception
instead of asserting the exception message to provide more information
about the exception.

Add an additional check before trying to allocate memory to immediately
skip the test on machines with insufficient total memory.

PR-URL: #3697
Reviewed-By: Trevor Norris <trev.norris@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Michaël Zasso <mic.besace@gmail.com>
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
rvagg pushed a commit that referenced this pull request Dec 5, 2015
The current implementation of tests for strings with length at or
exceeding kStringMaxLength allocate a temporary buffer inside a try
block to skip the test if there is insufficient memory. This commit adds
an invocation of the garbage collector after the temporary buffer is
allocated so that memory is freed for later allocations.

Change the corresponding catch block to rethrow the original exception
instead of asserting the exception message to provide more information
about the exception.

Add an additional check before trying to allocate memory to immediately
skip the test on machines with insufficient total memory.

PR-URL: #3697
Reviewed-By: Trevor Norris <trev.norris@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Michaël Zasso <mic.besace@gmail.com>
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
@rvagg rvagg mentioned this pull request Dec 17, 2015
MylesBorins pushed a commit that referenced this pull request Dec 29, 2015
The current implementation of tests for strings with length at or
exceeding kStringMaxLength allocate a temporary buffer inside a try
block to skip the test if there is insufficient memory. This commit adds
an invocation of the garbage collector after the temporary buffer is
allocated so that memory is freed for later allocations.

Change the corresponding catch block to rethrow the original exception
instead of asserting the exception message to provide more information
about the exception.

Add an additional check before trying to allocate memory to immediately
skip the test on machines with insufficient total memory.

PR-URL: #3697
Reviewed-By: Trevor Norris <trev.norris@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Michaël Zasso <mic.besace@gmail.com>
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
MylesBorins pushed a commit that referenced this pull request Jan 19, 2016
The current implementation of tests for strings with length at or
exceeding kStringMaxLength allocate a temporary buffer inside a try
block to skip the test if there is insufficient memory. This commit adds
an invocation of the garbage collector after the temporary buffer is
allocated so that memory is freed for later allocations.

Change the corresponding catch block to rethrow the original exception
instead of asserting the exception message to provide more information
about the exception.

Add an additional check before trying to allocate memory to immediately
skip the test on machines with insufficient total memory.

PR-URL: #3697
Reviewed-By: Trevor Norris <trev.norris@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Michaël Zasso <mic.besace@gmail.com>
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
@MylesBorins MylesBorins mentioned this pull request Jan 19, 2016
scovetta pushed a commit to scovetta/node that referenced this pull request Apr 2, 2016
The current implementation of tests for strings with length at or
exceeding kStringMaxLength allocate a temporary buffer inside a try
block to skip the test if there is insufficient memory. This commit adds
an invocation of the garbage collector after the temporary buffer is
allocated so that memory is freed for later allocations.

Change the corresponding catch block to rethrow the original exception
instead of asserting the exception message to provide more information
about the exception.

Add an additional check before trying to allocate memory to immediately
skip the test on machines with insufficient total memory.

PR-URL: nodejs#3697
Reviewed-By: Trevor Norris <trev.norris@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Michaël Zasso <mic.besace@gmail.com>
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
buffer Issues and PRs related to the buffer subsystem. memory Issues and PRs related to the memory management or memory footprint. test Issues and PRs related to the tests.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants