Skip to content
This repository was archived by the owner on May 4, 2018. It is now read-only.

Conversation

alexcrichton
Copy link

On OSX, apparently directories with 0 files in them still allocate memory on
the invocation of scandir, so if the function returns early it ends up
leaking memory allocated in scandir. I have altered uv__fs_readdir to
instead go to the "deallocation exit area" when 0 files are found in the
directory, and continue to return early on a return value of -1.

I went to add a test for this functionality, but it appears that one already
exists (reading an empty directory), so I imagine that the valgrind builds
must only be happening on linux instead of OSX as well? I have confirmed
manually that a program without this fix will infinitely leak memory, and with
this fix the memory usage stays constant.

@Nodejs-Jenkins
Copy link

Thank you for contributing this pull request! Here are a few pointers to make sure your submission will be considered for inclusion.

Commit alexcrichton/libuv@74e759e has the following error(s):

  • Commit message line too long: 2
  • Commit message line too long: 8
  • Commit message line too long: 9
  • Commit message line too long: 11

You can fix all these things without opening another issue.

Please see CONTRIBUTING.md for more information

@@ -205,7 +205,7 @@ static int uv__fs_readdir_filter(const struct dirent* dent) {

/* This should have been called uv__fs_scandir(). */
static ssize_t uv__fs_readdir(uv_fs_t* req) {
struct dirent **dents;
struct dirent **dents = NULL;
Copy link
Contributor

Choose a reason for hiding this comment

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

Please put the assignment on a different line.

@saghul
Copy link
Contributor

saghul commented Dec 17, 2013

If n is 0, then we won't free any extra stuff in the out label because the for loop won't be entered, right? What is freed exactly? I seem to be missing something :-)

@alexcrichton
Copy link
Author

Yes, the dents array won't get traversed because n == 0, and your guess is as good as mine as to what needs to get freed. Apparently OSX must reserve space in the returned dents array for 256 things and them I guess reallocs if there's more? The source of the leak is internal to the scandir implementation on OSX (which I have no idea where to find), and the only possible pointer it can return is dents, and then free'ing dents fixed the leak so I presume that it's the thing that scandir is allocating.

Also updated with comments!

@saghul
Copy link
Contributor

saghul commented Dec 17, 2013

I think I found it :-) I downloaded the source for Libc from opensource.apple.com, here is the scandir implementation: https://gist.github.com/saghul/8013376

The dirent is allocated here: https://gist.github.com/saghul/8013376#file-scandir-c-L89

And it's returned even if the directory is empty, since this while loop won't run: https://gist.github.com/saghul/8013376#file-scandir-c-L93

So, you seem to be right :-)

@indutny, does it also LGTY?

@saghul
Copy link
Contributor

saghul commented Dec 17, 2013

FWIW, I had a look at glibc and though the implementation is different, if the returned dirent is not NULL it needs to be freed as well.

Maybe we should also reword the commit log and remove that comment about OSX. FreeBSD probably has the same implementation. We could say that "Some scandir implementations allocate the dirent struct even if the directory is empty, ...".

Some scandir implementations allocate the dirent struct even if the
directory is empty, so if `scandir` returns 0 there may still be memory
that needs to get deallocated. I have altered uv__fs_readdir to go to
the "deallocation exit area" when 0 files are found in the directory
and continue to return early on a return value of -1.

I went to add a test for this functionality, but it appears that one
already exists (reading an empty directory), so I imagine that the
valgrind builds must only be happening on linux instead of OSX as well?
I have confirmed manually that a program without this fix will
infinitely leak memory, and with this fix the memory usage stays
constant.
@alexcrichton
Copy link
Author

Amended the message to be a be more general.

@saghul
Copy link
Contributor

saghul commented Dec 17, 2013

Ok, I'll wait for @indutny's thumbs up, just in case my reasoning is totally broken (let's hope not ;-) ) but LGTM.

@alexcrichton
Copy link
Author

Thanks for looking into this and the other libc implementations!

@indutny
Copy link
Contributor

indutny commented Dec 18, 2013

LGTM!

@indutny
Copy link
Contributor

indutny commented Dec 18, 2013

Landed in v0.10 in 7c6bddb

@indutny indutny closed this Dec 18, 2013
@indutny
Copy link
Contributor

indutny commented Dec 18, 2013

Thank you!

@alexcrichton alexcrichton deleted the fix-osx-leak branch December 18, 2013 08:12
@saghul
Copy link
Contributor

saghul commented Dec 18, 2013

Thanks @alexcrichton! :-)

alexcrichton added a commit to alexcrichton/rust that referenced this pull request Dec 19, 2013
I haven't landed this fix upstream just yet, but it's opened as
joyent/libuv#1048. For now, I've locally merged it into my fork, and I've
upgraded our repo to point to the new revision.

Closes rust-lang#11027
bors added a commit to rust-lang/rust that referenced this pull request Dec 19, 2013
I haven't landed this fix upstream just yet, but it's opened as
joyent/libuv#1048. For now, I've locally merged it into my fork, and I've
upgraded our repo to point to the new revision.

Closes #11027
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants