2

I'm using boost context 1.67 to create a fiber (fcontext API) with a minimal as possible stack size on Windows 10.

Probably this issue isn't only specific to boost context and applies to any scenario where we use a Windows thread with a minimal stack size.

I encountered issues when using really small stacks (below 10kb) through stackoverflow exceptions that are caused by the internal stack unwind exception thrown by boost context as shown below:

enter image description here

When using a larger stack (> 10 kb) I don't encounter any issues.

For a reproduction the following example is sufficient:

#include <memory>
#include <utility>
#include <boost/context/all.hpp>

#define STACK_SIZE 8000

struct my_allocator
{
  boost::context::stack_context allocate()
  {
    void* memory = std::malloc(STACK_SIZE);
    return {STACK_SIZE,
            static_cast<char*>(memory) +
                STACK_SIZE};
  }

  void deallocate(
      boost::context::stack_context& context)
  {
    std::free(static_cast<char*>(context.sp) -
              STACK_SIZE);
  }
};

int main(int, char**)
{
  boost::context::fiber fiber(
      std::allocator_arg, my_allocator{},
      [](boost::context::fiber&& sink) mutable {
        // ...
        return std::move(sink);
      });

  // Will cause a stack unwind exception and
  // reproduces the issue
  return 0;
}

Boost context is only used here for performing the context switch with a user allocated stack, probably the issue is caused through some limitations of MSVC C++ exceptions which probably require a certain minimal stack size to work. Also the SetThreadStackGuarantee WinAPI function doesn't have any effect on the issue.

The stack is allocated through malloc as depicted by the example.

Is it possible to use smaller stacks than 10kb on Windows when using C++ exceptions? Which circumstance possibly causes the limitation here?

Naios
  • 1,513
  • 1
  • 12
  • 26
  • in windows system allocate not less than *64 KB* for [Thread Stack](https://learn.microsoft.com/en-us/windows/desktop/ProcThread/thread-stack-size) – RbMm Jun 30 '18 at 08:07
  • @RbMm is there any reference for this limitation? The Go language for instance, uses 2kb (auto-growing) stacks by default (probably on Windows as well). – Naios Jul 03 '18 at 19:08
  • but i paste reference. 2kb stack can not be in principle. the page size is 4kb – RbMm Jul 03 '18 at 20:00
  • As @RbMm said, system rounds the stack size to a multiple of 64Kb .. see link. You can play with GetCurrentThreadStackLimits to see what happens there, try to call this with/without Boost and with/without exception to see where the bottleneck is. – AndreiM Jul 05 '18 at 09:34
  • @AndreiM how is it possible for the system to round my user provided stack up to 64kb? I've changed the example to reflect the circumstance that the stack is allocated through malloc (which can be backed by an allocator such as jemalloc). Probably the whole code will be executed in userspace and won't cause any syscall, since the context switch is [hardcoded in x86_64 by boost context](https://github.com/boostorg/context/blob/2f320aa68b4908eeb9aedba91ec8a546be20b009/src/asm/jump_x86_64_ms_pe_masm.asm). – Naios Jul 05 '18 at 15:07
  • @DenisBlank I think there's a misunderstanding. Win32 API does not expose any method of allocating your own stack space (one issue I see is that instead of Stack Overflow exception, you would get access denied/segmentation fault). But from what I see in boost fiber docu (), you allocate memory not for stack, but ON TOP of the stack. This would mean, in your example, that the more space you think you would allocate for the stack, you would actually allocate on top of the stack, thus less available stack space remains. Maybe I'm wrong so please have a look if this is the case. – AndreiM Jul 06 '18 at 07:50

1 Answers1

2

Unfortunately the Windows API does not provide a function or a constant returning the minimal reuired stack space.

Only on 32bit Windows exceptions (SEH) causes entries on the stack. Win x64 uses tables-based exception handling - entries for the exception handlers are stored in the pdata section. So exception handling on x64 should not influence the min. stack space. The x64 calling convention requires some space - for instance 32 bytes of "shadow space" + space for XMM registers ... but only few bytes are required to store the registers.

I guess that the min. stack space is limited by the 'instrumentation code' (for instance stack cookies etc.) - probably this can be controlled by the compiler flags.

boost.context allocates memory and uses it as stack (+ implements the context switching). boost.fiber uses the stack from boost.context and reserves space (placement new) for a control structure at the top of each fiber's stack (but this is < 1kB).

You have the option to use interval nesting in order to test what's the minimal required stack space of your app.

xlrg
  • 1,994
  • 1
  • 16
  • 14