40e9464158958c4f3a62e21c3320cb7e9674550e
[folly.git] / folly / experimental / fibers / Fiber.cpp
1 /*
2  * Copyright 2015 Facebook, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #include "Fiber.h"
17
18 #include <sys/syscall.h>
19 #include <unistd.h>
20
21 #include <algorithm>
22 #include <cassert>
23 #include <cstring>
24 #include <stdexcept>
25
26 #include <folly/Likely.h>
27 #include <folly/Portability.h>
28 #include <folly/experimental/fibers/BoostContextCompatibility.h>
29 #include <folly/experimental/fibers/FiberManager.h>
30
31 namespace folly { namespace fibers {
32
33 namespace {
34 static const uint64_t kMagic8Bytes = 0xfaceb00cfaceb00c;
35
36 pid_t localThreadId() {
37   static thread_local pid_t threadId = syscall(SYS_gettid);
38   return threadId;
39 }
40
41 /* Size of the region from p + nBytes down to the last non-magic value */
42 static size_t nonMagicInBytes(const FContext& context) {
43   uint64_t* begin = static_cast<uint64_t*>(context.stackLimit());
44   uint64_t* end = static_cast<uint64_t*>(context.stackBase());
45
46   auto firstNonMagic = std::find_if(
47     begin, end,
48     [](uint64_t val) {
49       return val != kMagic8Bytes;
50     }
51   );
52
53   return (end - firstNonMagic) * sizeof(uint64_t);
54 }
55
56 }  // anonymous namespace
57
58 void Fiber::setData(intptr_t data) {
59   assert(state_ == AWAITING);
60   data_ = data;
61   state_ = READY_TO_RUN;
62
63   if (LIKELY(threadId_ == localThreadId())) {
64     fiberManager_.readyFibers_.push_back(*this);
65     fiberManager_.ensureLoopScheduled();
66   } else {
67     fiberManager_.remoteReadyInsert(this);
68   }
69 }
70
71 Fiber::Fiber(FiberManager& fiberManager) :
72     fiberManager_(fiberManager) {
73
74   auto size = fiberManager_.options_.stackSize;
75   auto limit = fiberManager_.stackAllocator_.allocate(size);
76
77   fcontext_ = makeContext(limit, size, &Fiber::fiberFuncHelper);
78 }
79
80 void Fiber::init(bool recordStackUsed) {
81 // It is necessary to disable the logic for ASAN because we change
82 // the fiber's stack.
83 #ifndef FOLLY_SANITIZE_ADDRESS
84   recordStackUsed_ = recordStackUsed;
85   if (UNLIKELY(recordStackUsed_ && !stackFilledWithMagic_)) {
86     auto limit = fcontext_.stackLimit();
87     auto base = fcontext_.stackBase();
88
89     std::fill(static_cast<uint64_t*>(limit),
90               static_cast<uint64_t*>(base),
91               kMagic8Bytes);
92
93     // newer versions of boost allocate context on fiber stack,
94     // need to create a new one
95     auto size = fiberManager_.options_.stackSize;
96     fcontext_ = makeContext(limit, size, &Fiber::fiberFuncHelper);
97
98     stackFilledWithMagic_ = true;
99   }
100 #endif
101 }
102
103 Fiber::~Fiber() {
104   fiberManager_.stackAllocator_.deallocate(
105     static_cast<unsigned char*>(fcontext_.stackLimit()),
106     fiberManager_.options_.stackSize);
107 }
108
109 void Fiber::recordStackPosition() {
110   int stackDummy;
111   auto currentPosition = static_cast<size_t>(
112      static_cast<unsigned char*>(fcontext_.stackBase()) -
113      static_cast<unsigned char*>(static_cast<void*>(&stackDummy)));
114   fiberManager_.stackHighWatermark_ =
115     std::max(fiberManager_.stackHighWatermark_, currentPosition);
116   VLOG(4) << "Stack usage: " << currentPosition;
117 }
118
119 void Fiber::fiberFuncHelper(intptr_t fiber) {
120   reinterpret_cast<Fiber*>(fiber)->fiberFunc();
121 }
122
123 /*
124  * Some weird bug in ASAN causes fiberFunc to allocate boundless amounts of
125  * memory inside __asan_handle_no_return.  Work around this in ASAN builds by
126  * tricking the compiler into thinking it may, someday, return.
127  */
128 #ifdef FOLLY_SANITIZE_ADDRESS
129 volatile bool loopForever = true;
130 #else
131 static constexpr bool loopForever = true;
132 #endif
133
134 void Fiber::fiberFunc() {
135   while (loopForever) {
136     assert(state_ == NOT_STARTED);
137
138     threadId_ = localThreadId();
139     state_ = RUNNING;
140
141     try {
142       if (resultFunc_) {
143         assert(finallyFunc_);
144         assert(!func_);
145
146         resultFunc_();
147       } else {
148         assert(func_);
149         func_();
150       }
151     } catch (...) {
152       fiberManager_.exceptionCallback_(std::current_exception(),
153                                        "running Fiber func_/resultFunc_");
154     }
155
156     if (UNLIKELY(recordStackUsed_)) {
157       fiberManager_.stackHighWatermark_ =
158         std::max(fiberManager_.stackHighWatermark_,
159                  nonMagicInBytes(fcontext_));
160       VLOG(3) << "Max stack usage: " << fiberManager_.stackHighWatermark_;
161       CHECK(fiberManager_.stackHighWatermark_ <
162               fiberManager_.options_.stackSize - 64) << "Fiber stack overflow";
163     }
164
165     state_ = INVALID;
166
167     fiberManager_.activeFiber_ = nullptr;
168
169     auto fiber = reinterpret_cast<Fiber*>(
170       jumpContext(&fcontext_, &fiberManager_.mainContext_, 0));
171     assert(fiber == this);
172   }
173 }
174
175 intptr_t Fiber::preempt(State state) {
176   assert(fiberManager_.activeFiber_ == this);
177   assert(state_ == RUNNING);
178   assert(state != RUNNING);
179
180   fiberManager_.activeFiber_ = nullptr;
181   state_ = state;
182
183   recordStackPosition();
184
185   auto ret = jumpContext(&fcontext_, &fiberManager_.mainContext_, 0);
186
187   assert(fiberManager_.activeFiber_ == this);
188   assert(state_ == READY_TO_RUN);
189   state_ = RUNNING;
190
191   return ret;
192 }
193
194 Fiber::LocalData::LocalData(const LocalData& other) : data_(nullptr) {
195   *this = other;
196 }
197
198 Fiber::LocalData& Fiber::LocalData::operator=(const LocalData& other) {
199   reset();
200   if (!other.data_) {
201     return *this;
202   }
203
204   dataSize_ = other.dataSize_;
205   dataType_ = other.dataType_;
206   dataDestructor_ = other.dataDestructor_;
207   dataCopyConstructor_ = other.dataCopyConstructor_;
208
209   if (dataSize_ <= kBufferSize) {
210     data_ = &buffer_;
211   } else {
212     data_ = allocateHeapBuffer(dataSize_);
213   }
214
215   dataCopyConstructor_(data_, other.data_);
216
217   return *this;
218 }
219
220 void Fiber::LocalData::reset() {
221   if (!data_) {
222     return;
223   }
224
225   dataDestructor_(data_);
226   data_ = nullptr;
227 }
228
229 void* Fiber::LocalData::allocateHeapBuffer(size_t size) {
230   return new char[size];
231 }
232
233 void Fiber::LocalData::freeHeapBuffer(void* buffer) {
234   delete[] reinterpret_cast<char*>(buffer);
235 }
236
237 }}