{"id":103833,"date":"2020-06-04T07:00:00","date_gmt":"2020-06-04T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=103833"},"modified":"2020-06-03T20:19:57","modified_gmt":"2020-06-04T03:19:57","slug":"20200604-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20200604-00\/?p=103833","title":{"rendered":"Using fibers to expand a thread&#8217;s stack at runtime, part 3"},"content":{"rendered":"<p>We&#8217;ve been working on a <code>Run\u00adOn\u00adFiber<\/code> function that creates a fiber with a large stack and runs the lambda on it. This is handy if you have a function that requires a lot of stack, and you&#8217;re not sure whether your caller provided enough.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20200603-00\/?p=103824\"> The version we developed<\/a> assumed that the only thing of interest that is returned from the lambda is its return value. But you might have additional context that needs to be returned, such as the Win32 last-error code or the C runtime <code>errno<\/code>.<\/p>\n<p>We can adapt our existing version to capture additional state about the fiber so it can be transferred to the original thread.<\/p>\n<pre><span style=\"color: blue;\">DWORD RunOnFiberTypeNeutral(\r\n    LPFIBER_START_ROUTINE fiberProc,<\/span>\r\n    void* parameter,\r\n    <span style=\"color: blue;\">HANDLE* originalFiber<\/span>)\r\n{\r\n  unique_fiber workFiber{ CreateFiberEx(0, EXTRA_STACK_SIZE, 0,\r\n      <span style=\"color: blue;\">fiberProc<\/span>, parameter) };\r\n\r\n  if (!workFiber) return <span style=\"color: blue;\">GetLastError()<\/span>;\r\n\r\n  unique_thread_as_fiber threadFiber;\r\n  if (!IsThreadAFiber()) {\r\n    threadFiber.reset(ConvertThreadToFiber(nullptr));\r\n    if (!threadFiber) {\r\n      return <span style=\"color: blue;\">GetLastError()<\/span>;\r\n    }\r\n  }\r\n\r\n  <span style=\"color: blue;\">*originalFiber = GetCurrentFiber();<\/span>\r\n\r\n  SwitchToFiber(workFiber.get());\r\n  return <span style=\"color: blue;\">ERROR_SUCCESS<\/span>;\r\n}\r\n\r\ntemplate&lt;typename RetType&gt;\r\nRetType RunOnFiberWorker(\r\n    <span style=\"color: blue;\">RetType<\/span> (*callback)(void*),\r\n    void* parameter,\r\n    RetType failureValue)\r\n{\r\n  <span style=\"color: blue;\">static_assert(std::is_trivially_copyable_v&lt;RetType&gt;);\r\n  static_assert(std::is_trivially_destructible_v&lt;RetType&gt;);<\/span>\r\n\r\n  struct State\r\n  {\r\n    <span style=\"color: blue;\">RetType<\/span> (*callback)(void*);\r\n    void* parameter;\r\n    <span style=\"color: blue;\">RetType capturedValue;\r\n    int capturedErrno{};\r\n    DWORD capturedError{};<\/span>\r\n    HANDLE originalFiber;\r\n\r\n    void FiberProc()\r\n    {\r\n      <span style=\"color: blue;\">capturedValue = callback(parameter);\r\n      capturedErrno = errno;\r\n      capturedError = GetLastError();<\/span>\r\n      SwitchToFiber(originalFiber);\r\n    }\r\n  } state{ callback, parameter,\r\n                 <span style=\"color: blue;\">std::move(failureValue), errno<\/span> };\r\n\r\n  <span style=\"color: blue;\">DWORD error = RunOnFiberTypeNeutral(\r\n    [](void* parameter)\r\n    {\r\n      reinterpret_cast&lt;State*&gt;(parameter)-&gt;FiberProc();\r\n    }, &amp;state, &amp;state.originalFiber);\r\n  if (error != ERROR_SUCCESS) {\r\n    state.capturedError = error;\r\n  }<\/span>\r\n\r\n  <span style=\"color: blue;\">SetLastError(state.capturedError);\r\n  errno = state.capturedErrno;\r\n  return std::move(state.capturedValue);<\/span>\r\n}\r\n<\/pre>\n<p>Since this template may be used with different <code>RetType<\/code>s, we factor out the part that is type-independent into a <code>Run\u00adOn\u00adFiber\u00adType\u00adNeutral<\/code> helper. There is a bit of trickiness in the helper function: We make sure to capture the error code immediately and return it explicitly. This is important because the destructors for the <code>unique_fiber<\/code> and <code>unique_thread_as_fiber<\/code> may change the value of <code>Get\u00adLast\u00adError()<\/code>, so we need to grab it while we still can.<\/p>\n<p>We expand the state shared with the fiber to include not only the return value of the callback, but also the <code>Get\u00adLast\u00adError()<\/code> and <code>errno<\/code> values as they were at the completion of the callback.<\/p>\n<p>When all is said and done, we restore the error code and <code>errno<\/code> to the current thread and then return the value that was captured from the execution of the callback.<\/p>\n<p>If <code>Run\u00adOn\u00adFiber\u00adType\u00adNeutral<\/code> fails, then we take the error code and put it into the <code>capturedError<\/code> so that the normal cleanup code will apply it to the thread before returning.<\/p>\n<p>I&#8217;m assuming that if anything goes wrong with <code>Create\u00adFiber<\/code> or <code>Convert\u00adThread\u00adTo\u00adFiber<\/code>, then the <code>failureValue<\/code> and existing Win32 error code are sufficient to explain what went wrong. I leave setting the <code>errno<\/code> as an exercise.<\/p>\n<p>Note that we require that the return value type be trivially copyable and trivially destructible. Trivial copyability is required so that we can return it without disturbing the Win32 last-error code or <code>errno<\/code>. Trivial destructibility is required so that we can destruct the <code>failureValue<\/code> parameter and the <code>capturedValue<\/code> without disturbing the Win32 last-error code or <code>errno<\/code>.<\/p>\n<p>This requirement is not a problem in practice, because a return type that requires nontrivial copy or destructor operations is going to trigger invisible code execution for temporary objects, which is likely to affect ephemeral thread-local state. In that case, you cannot rely on the Win32 last-error code or the <code>errno<\/code> value anyway, so there&#8217;s no reason to try to preserve that value from the fiber back to the original thread.<\/p>\n<p>We do not, however, require that the return value type be trivially constructible. Instead, we accept a <code>failureValue<\/code> and use that if something goes wrong with setting up the fiber. We are careful to <code>std::move<\/code> the value around, even though that doesn&#8217;t really mean anything for trivially-copyable types. But it&#8217;ll come in handy later. Typically, the return type in these cases is a simple scalar, like an integer.<\/p>\n<p>Here&#8217;s the new <code>Run\u00adOn\u00adFiber<\/code> function that accepts a <code>failure\u00adValue<\/code>:<\/p>\n<pre>template&lt;typename Lambda<span style=\"color: blue;\">, typename RetType<\/span>&gt;\r\n<span style=\"color: blue;\">RetType<\/span> RunOnFiber(\r\n    Lambda&amp;&amp; lambda,\r\n    <span style=\"color: blue;\">RetType failureValue<\/span>)\r\n{\r\n  using Type = std::remove_reference_t&lt;Lambda&gt;;\r\n  <span style=\"color: blue;\">using RetType = decltype(lambda());<\/span>\r\n  return RunOnFiberWorker<span style=\"color: blue;\">&lt;RetType&gt;<\/span>([](void* parameter)\r\n    {\r\n      return (*reinterpret_cast&lt;Type*&gt;(parameter))();\r\n    }, &amp;lambda, <span style=\"color: blue;\">std::move(failureValue)<\/span>);\r\n}\r\n<\/pre>\n<p>Note that the above code does not work if the lambda returns a reference. I&#8217;ll leave that as an exercise.<\/p>\n<p>Next time, we&#8217;ll look at another error-reporting mechanism: C++ exceptions.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Propagating additional thread state.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-103833","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Propagating additional thread state.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/103833","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=103833"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/103833\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=103833"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=103833"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=103833"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}