Permalink
Browse files

Update stack limits

By default Fibers used to have a stack limit of 64k with 2k padding for
v8's native code. This was problematic because some v8 code can use at
least 240k of stack not even including the stack space client JS uses.
The new limits are 512k/1M per fiber with 128k/256k padding.

Additionally this implements libcoro's new stack management interface.
  • Loading branch information...
1 parent 58f3ff7 commit d22db0a31c5c14aad71c836a4cfeb8fc0cedca7c @laverdet committed Jan 11, 2013
Showing with 55 additions and 28 deletions.
  1. +1 −1 binding.gyp
  2. +15 −16 src/coroutine.cc
  3. +4 −6 src/coroutine.h
  4. +6 −5 src/fibers.cc
  5. +29 −0 test/stack-overflow2.js
View
@@ -17,7 +17,7 @@
# else
{
'cflags': ['-Wno-deprecated-declarations'],
- 'defines': ['USE_CORO'],
+ 'defines': ['USE_CORO', 'CORO_GUARDPAGES=1'],
'ldflags': ['-pthread'],
}
],
View
@@ -20,8 +20,8 @@
#include <iostream>
using namespace std;
-static pthread_key_t floor_thread_key = NULL;
-static pthread_key_t ceil_thread_key = NULL;
+static pthread_key_t floor_thread_key = 0;
+static pthread_key_t ceil_thread_key = 0;
static size_t stack_size = 0;
static size_t coroutines_created_ = 0;
@@ -52,11 +52,8 @@ Coroutine& Coroutine::current() {
return *current;
}
-void Coroutine::set_stack_size(size_t size) {
+void Coroutine::set_stack_size(unsigned int size) {
assert(!stack_size);
-#ifdef CORO_PTHREAD
- size += 1024 * 64;
-#endif
stack_size = size;
}
@@ -73,7 +70,7 @@ void Coroutine::trampoline(void* that) {
// creates the stack automatically we don't have access to the base. We can however grab the
// current esp position, and use that as an approximation. Padding is added for safety since the
// base is slightly different.
- static_cast<Coroutine*>(that)->stack_base = (char*)_AddressOfReturnAddress() - stack_size + 128;
+ static_cast<Coroutine*>(that)->stack_base = (size_t*)_AddressOfReturnAddress() - stack_size + 16;
#endif
while (true) {
static_cast<Coroutine*>(that)->entry(const_cast<void*>(static_cast<Coroutine*>(that)->arg));
@@ -84,30 +81,32 @@ Coroutine::Coroutine() :
entry(NULL),
arg(NULL) {
#ifdef USE_CORO
- coro_create(&context, NULL, NULL, NULL, NULL);
+ stack.sptr = NULL;
+ coro_create(&context, NULL, NULL, NULL, 0);
#endif
#ifdef USE_WINFIBER
context = ConvertThreadToFiber(NULL);
#endif
}
Coroutine::Coroutine(entry_t& entry, void* arg) :
-#ifndef USE_WINFIBER
- stack(stack_size),
-#endif
entry(entry),
arg(arg) {
#ifdef USE_CORO
- coro_create(&context, trampoline, this, &stack[0], stack_size);
+ coro_stack_alloc(&stack, stack_size);
+ coro_create(&context, trampoline, this, stack.sptr, stack.ssze);
#endif
#ifdef USE_WINFIBER
- context = CreateFiber(stack_size, trampoline, this);
+ context = CreateFiber(stack_size * sizeof(void*), trampoline, this);
#endif
}
Coroutine::~Coroutine() {
#ifdef USE_CORO
- coro_destroy(&context);
+ if (stack.sptr) {
+ coro_stack_free(&stack);
+ }
+ (void)coro_destroy(&context);
#endif
#ifdef USE_WINFIBER
DeleteFiber(context);
@@ -205,12 +204,12 @@ void Coroutine::finish(Coroutine& next) {
void* Coroutine::bottom() const {
#ifndef USE_WINFIBER
- return (char*)&stack[0];
+ return stack.sptr;
#else
return stack_base;
#endif
}
size_t Coroutine::size() const {
- return sizeof(Coroutine) + stack_size;
+ return sizeof(Coroutine) + stack_size * sizeof(void*);
}
View
@@ -18,10 +18,7 @@ class Coroutine {
private:
#ifdef USE_CORO
coro_context context;
- // vector<char> will 0 out the memory first which is not necessary; this hack lets us get
- // around that, as there is no constructor.
- struct char_noinit { char x; };
- std::vector<char_noinit> stack;
+ coro_stack stack;
#endif
#ifdef USE_WINFIBER
void* context;
@@ -74,9 +71,10 @@ class Coroutine {
/**
* Set the size of coroutines created by this library. Since coroutines are pooled the stack
- * size is global instead of per-coroutine.
+ * size is global instead of per-coroutine. Stack is measured in sizeof(void*), so
+ * set_stack_size(128) -> 512 bytes or 1kb
*/
- static void set_stack_size(size_t size);
+ static void set_stack_size(unsigned int size);
/**
* Get the number of coroutines that have been created.
View
@@ -332,11 +332,12 @@ class Fiber {
Isolate::Scope isolate_scope(that.isolate);
HandleScope scope;
- // Set the stack guard for this "thread"; allow 2k of padding past the JS limit for C++ code
- // to run
+ // Set the stack guard for this "thread"; allow 128k or 256k of padding past the JS limit for
+ // native v8 code to run
ResourceConstraints constraints;
constraints.set_stack_limit(reinterpret_cast<uint32_t*>(
- (char*)that.this_fiber->bottom() + 4 * 1024));
+ (size_t*)that.this_fiber->bottom() + 32 * 1024
+ ));
SetResourceConstraints(&constraints);
TryCatch try_catch;
@@ -535,8 +536,8 @@ extern "C" void init(Handle<Object> target) {
HandleScope scope;
Coroutine::init();
Fiber::Init(target);
- // Default stack size of 64kb. Perhaps make this configurable by the run time?
- Coroutine::set_stack_size(64 * 1024);
+ // Default stack size of either 512k or 1M. Perhaps make this configurable by the run time?
+ Coroutine::set_stack_size(128 * 1024);
}
NODE_MODULE(fibers, init)
@@ -0,0 +1,29 @@
+var Fiber = require('fibers');
+Fiber(function() {
+ // Because of how v8 handles strings, the call to new RegExp chews up a lot of stack space
+ // outside of JS.
+ function fn() {
+ var foo = '';
+ for (var ii = 0; ii < 1024; ++ii) {
+ foo += 'a';
+ }
+ new RegExp(foo, 'g');
+ }
+
+ // Calculate how far we can go recurse without hitting the JS stack limit
+ var max = 0;
+ function testRecursion(ii) {
+ ++max;
+ testRecursion(ii + 1);
+ }
+ try {
+ testRecursion();
+ } catch (err) {}
+
+ // Recurse to the limit and then invoke a stack-heavy C++ operation
+ function wasteStack(ii) {
+ ii ? wasteStack(ii - 1) : fn();
+ }
+ wasteStack(max - 32);
+ console.log('pass');
+}).run();

0 comments on commit d22db0a

Please sign in to comment.