Skip to content

Commit 2433ec8

Browse files
committed
buffer: allocate memory with mmap()
Work around an issue with the glibc malloc() implementation where memory blocks are never returned to the operating system when they are allocated with brk() and have overlapping lifecycles. Fixes nodejs#4283.
1 parent 01db736 commit 2433ec8

4 files changed

Lines changed: 114 additions & 5 deletions

File tree

lib/buffer.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
// USE OR OTHER DEALINGS IN THE SOFTWARE.
2121

2222
var SlowBuffer = process.binding('buffer').SlowBuffer;
23+
var kPoolSize = process.binding('buffer').kPoolSize;
2324
var assert = require('assert');
2425

2526
exports.INSPECT_MAX_BYTES = 50;
@@ -297,7 +298,7 @@ Buffer.isEncoding = function(encoding) {
297298

298299

299300

300-
Buffer.poolSize = 8 * 1024;
301+
Buffer.poolSize = kPoolSize; // see src/node_buffer.h
301302
var pool;
302303

303304
function allocPool() {

src/node_buffer.cc

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@
2929
#include <assert.h>
3030
#include <string.h> // memcpy
3131

32+
#ifdef __POSIX__
33+
# include <sys/mman.h> // mmap
34+
# include <unistd.h> // sysconf
35+
# include <stdio.h> // perror
36+
#endif
37+
3238
#define MIN(a,b) ((a) < (b) ? (a) : (b))
3339

3440
#define BUFFER_CLASS_ID (0xBABE)
@@ -196,6 +202,67 @@ Buffer::~Buffer() {
196202
}
197203

198204

205+
#if defined(__POSIX__)
206+
207+
static unsigned int num_pool_buffers;
208+
static char* cached_pool_buffers[16];
209+
210+
211+
static inline void free_buf_mem(char* buf, size_t len) {
212+
if (len == Buffer::kPoolSize &&
213+
num_pool_buffers < ARRAY_SIZE(cached_pool_buffers)) {
214+
cached_pool_buffers[num_pool_buffers++] = buf;
215+
return;
216+
}
217+
218+
if (munmap(buf, len)) {
219+
perror("munmap");
220+
abort();
221+
}
222+
}
223+
224+
225+
static inline char* alloc_buf_mem(size_t len) {
226+
size_t pagesize = sysconf(_SC_PAGESIZE);
227+
228+
len = ROUND_UP(len, pagesize);
229+
if (len == Buffer::kPoolSize && num_pool_buffers > 0) {
230+
return cached_pool_buffers[--num_pool_buffers];
231+
}
232+
233+
int prot = PROT_READ | PROT_WRITE;
234+
int flags = MAP_ANONYMOUS | MAP_PRIVATE;
235+
char* buf = static_cast<char*>(mmap(NULL, len, prot, flags, -1, 0));
236+
237+
if (buf == NULL) {
238+
TryCatch try_catch;
239+
char errmsg[256];
240+
snprintf(errmsg,
241+
sizeof(errmsg),
242+
"Out of memory, mmap(len=%lu) failed.",
243+
static_cast<unsigned long>(len));
244+
ThrowError(errmsg);
245+
FatalException(try_catch);
246+
abort();
247+
}
248+
249+
return buf;
250+
}
251+
252+
#else // !__POSIX__
253+
254+
static inline void free_buf_mem(char* buf, size_t len) {
255+
delete[] buf;
256+
}
257+
258+
259+
static inline char* alloc_buf_mem(size_t len) {
260+
return new char[len];
261+
}
262+
263+
#endif // __POSIX__
264+
265+
199266
// if replace doesn't have a callback, data must be copied
200267
// const_cast in Buffer::New requires this
201268
void Buffer::Replace(char *data, size_t length,
@@ -205,7 +272,7 @@ void Buffer::Replace(char *data, size_t length,
205272
if (callback_) {
206273
callback_(data_, callback_hint_);
207274
} else if (length_) {
208-
delete [] data_;
275+
free_buf_mem(data_, length_);
209276
V8::AdjustAmountOfExternalAllocatedMemory(
210277
-static_cast<intptr_t>(sizeof(Buffer) + length_));
211278
}
@@ -217,9 +284,8 @@ void Buffer::Replace(char *data, size_t length,
217284
if (callback_) {
218285
data_ = data;
219286
} else if (length_) {
220-
data_ = new char[length_];
221-
if (data)
222-
memcpy(data_, data, length_);
287+
data_ = alloc_buf_mem(length_);
288+
if (data != NULL) memcpy(data_, data, length_);
223289
V8::AdjustAmountOfExternalAllocatedMemory(sizeof(Buffer) + length_);
224290
} else {
225291
data_ = NULL;
@@ -830,6 +896,8 @@ void Buffer::Initialize(Handle<Object> target) {
830896
Buffer::MakeFastBuffer);
831897

832898
target->Set(String::NewSymbol("SlowBuffer"), constructor_template->GetFunction());
899+
target->Set(String::NewSymbol("kPoolSize"),
900+
Integer::NewFromUnsigned(kPoolSize));
833901

834902
HeapProfiler::DefineWrapperClass(BUFFER_CLASS_ID, WrapperInfo);
835903
}

src/node_buffer.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ class NODE_EXTERN Buffer: public ObjectWrap {
6868
// mirrors deps/v8/src/objects.h
6969
static const unsigned int kMaxLength = 0x3fffffff;
7070

71+
// exported in lib/buffer.js as Buffer.poolSize
72+
static const unsigned int kPoolSize = 32768;
73+
7174
static v8::Persistent<v8::FunctionTemplate> constructor_template;
7275

7376
static bool HasInstance(v8::Handle<v8::Value> val);

test/simple/test-buffer-release.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright Joyent, Inc. and other Node contributors.
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a
4+
// copy of this software and associated documentation files (the
5+
// "Software"), to deal in the Software without restriction, including
6+
// without limitation the rights to use, copy, modify, merge, publish,
7+
// distribute, sublicense, and/or sell copies of the Software, and to permit
8+
// persons to whom the Software is furnished to do so, subject to the
9+
// following conditions:
10+
//
11+
// The above copyright notice and this permission notice shall be included
12+
// in all copies or substantial portions of the Software.
13+
//
14+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15+
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16+
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17+
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18+
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20+
// USE OR OTHER DEALINGS IN THE SOFTWARE.
21+
22+
var common = require('../common');
23+
var assert = require('assert');
24+
25+
function alloc() {
26+
var h = {};
27+
for (var i = 0; i < 10000; ++i) h[i] = new Buffer(20000);
28+
h = null;
29+
}
30+
31+
alloc();
32+
alloc();
33+
alloc();
34+
35+
// Note: this assertion fails when run under valgrind because valgrind
36+
// increases the RSS footprint of node with at least 50 MB.
37+
assert(process.memoryUsage().rss < 32*1024*1024);

0 commit comments

Comments
 (0)