b4fea854afbea52ab793907c36855480ebba7e9f
[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 <cstring>
23 #include <stdexcept>
24 #include <glog/logging.h>
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   // __thread doesn't allow non-const initialization.
38   // OSX doesn't support thread_local.
39   static FOLLY_TLS pid_t threadId = 0;
40   if (UNLIKELY(threadId == 0)) {
41     threadId = syscall(SYS_gettid);
42   }
43   return threadId;
44 }
45
46 /* Size of the region from p + nBytes down to the last non-magic value */
47 static size_t nonMagicInBytes(const FContext& context) {
48   uint64_t* begin = static_cast<uint64_t*>(context.stackLimit());
49   uint64_t* end = static_cast<uint64_t*>(context.stackBase());
50
51   auto firstNonMagic = std::find_if(
52     begin, end,
53     [](uint64_t val) {
54       return val != kMagic8Bytes;
55     }
56   );
57
58   return (end - firstNonMagic) * sizeof(uint64_t);
59 }
60
61 }  // anonymous namespace
62
63 void Fiber::setData(intptr_t data) {
64   DCHECK_EQ(state_, AWAITING);
65   data_ = data;
66   state_ = READY_TO_RUN;
67
68   if (fiberManager_.observer_) {
69     fiberManager_.observer_->runnable(reinterpret_cast<uintptr_t>(this));
70   }
71
72   if (LIKELY(threadId_ == localThreadId())) {
73     fiberManager_.readyFibers_.push_back(*this);
74     fiberManager_.ensureLoopScheduled();
75   } else {
76     fiberManager_.remoteReadyInsert(this);
77   }
78 }
79
80 Fiber::Fiber(FiberManager& fiberManager) :
81     fiberManager_(fiberManager) {
82
83   auto size = fiberManager_.options_.stackSize;
84   auto limit = fiberManager_.stackAllocator_.allocate(size);
85
86   fcontext_ = makeContext(limit, size, &Fiber::fiberFuncHelper);
87 }
88
89 void Fiber::init(bool recordStackUsed) {
90 // It is necessary to disable the logic for ASAN because we change
91 // the fiber's stack.
92 #ifndef FOLLY_SANITIZE_ADDRESS
93   recordStackUsed_ = recordStackUsed;
94   if (UNLIKELY(recordStackUsed_ && !stackFilledWithMagic_)) {
95     auto limit = fcontext_.stackLimit();
96     auto base = fcontext_.stackBase();
97
98     std::fill(static_cast<uint64_t*>(limit),
99               static_cast<uint64_t*>(base),
100               kMagic8Bytes);
101
102     // newer versions of boost allocate context on fiber stack,
103     // need to create a new one
104     auto size = fiberManager_.options_.stackSize;
105     fcontext_ = makeContext(limit, size, &Fiber::fiberFuncHelper);
106
107     stackFilledWithMagic_ = true;
108   }
109 #endif
110 }
111
112 Fiber::~Fiber() {
113   fiberManager_.stackAllocator_.deallocate(
114     static_cast<unsigned char*>(fcontext_.stackLimit()),
115     fiberManager_.options_.stackSize);
116 }
117
118 void Fiber::recordStackPosition() {
119   int stackDummy;
120   auto currentPosition = static_cast<size_t>(
121      static_cast<unsigned char*>(fcontext_.stackBase()) -
122      static_cast<unsigned char*>(static_cast<void*>(&stackDummy)));
123   fiberManager_.stackHighWatermark_ =
124     std::max(fiberManager_.stackHighWatermark_, currentPosition);
125   VLOG(4) << "Stack usage: " << currentPosition;
126 }
127
128 void Fiber::fiberFuncHelper(intptr_t fiber) {
129   reinterpret_cast<Fiber*>(fiber)->fiberFunc();
130 }
131
132 /*
133  * Some weird bug in ASAN causes fiberFunc to allocate boundless amounts of
134  * memory inside __asan_handle_no_return.  Work around this in ASAN builds by
135  * tricking the compiler into thinking it may, someday, return.
136  */
137 #ifdef FOLLY_SANITIZE_ADDRESS
138 volatile bool loopForever = true;
139 #else
140 static constexpr bool loopForever = true;
141 #endif
142
143 void Fiber::fiberFunc() {
144   while (loopForever) {
145     DCHECK_EQ(state_, NOT_STARTED);
146
147     threadId_ = localThreadId();
148     state_ = RUNNING;
149
150     try {
151       if (resultFunc_) {
152         DCHECK(finallyFunc_);
153         DCHECK(!func_);
154
155         resultFunc_();
156       } else {
157         DCHECK(func_);
158         func_();
159       }
160     } catch (...) {
161       fiberManager_.exceptionCallback_(std::current_exception(),
162                                        "running Fiber func_/resultFunc_");
163     }
164
165     if (UNLIKELY(recordStackUsed_)) {
166       fiberManager_.stackHighWatermark_ =
167         std::max(fiberManager_.stackHighWatermark_,
168                  nonMagicInBytes(fcontext_));
169       VLOG(3) << "Max stack usage: " << fiberManager_.stackHighWatermark_;
170       CHECK(fiberManager_.stackHighWatermark_ <
171               fiberManager_.options_.stackSize - 64) << "Fiber stack overflow";
172     }
173
174     state_ = INVALID;
175
176     fiberManager_.activeFiber_ = nullptr;
177
178     auto context = jumpContext(&fcontext_, &fiberManager_.mainContext_, 0);
179     DCHECK_EQ(reinterpret_cast<Fiber*>(context), this);
180   }
181 }
182
183 intptr_t Fiber::preempt(State state) {
184   DCHECK_EQ(fiberManager_.activeFiber_, this);
185   DCHECK_EQ(state_, RUNNING);
186   DCHECK_NE(state, RUNNING);
187
188   fiberManager_.activeFiber_ = nullptr;
189   state_ = state;
190
191   recordStackPosition();
192
193   auto ret = jumpContext(&fcontext_, &fiberManager_.mainContext_, 0);
194
195   DCHECK_EQ(fiberManager_.activeFiber_, this);
196   DCHECK_EQ(state_, READY_TO_RUN);
197   state_ = RUNNING;
198
199   return ret;
200 }
201
202 Fiber::LocalData::LocalData(const LocalData& other) : data_(nullptr) {
203   *this = other;
204 }
205
206 Fiber::LocalData& Fiber::LocalData::operator=(const LocalData& other) {
207   reset();
208   if (!other.data_) {
209     return *this;
210   }
211
212   dataSize_ = other.dataSize_;
213   dataType_ = other.dataType_;
214   dataDestructor_ = other.dataDestructor_;
215   dataCopyConstructor_ = other.dataCopyConstructor_;
216
217   if (dataSize_ <= kBufferSize) {
218     data_ = &buffer_;
219   } else {
220     data_ = allocateHeapBuffer(dataSize_);
221   }
222
223   dataCopyConstructor_(data_, other.data_);
224
225   return *this;
226 }
227
228 void Fiber::LocalData::reset() {
229   if (!data_) {
230     return;
231   }
232
233   dataDestructor_(data_);
234   data_ = nullptr;
235 }
236
237 void* Fiber::LocalData::allocateHeapBuffer(size_t size) {
238   return new char[size];
239 }
240
241 void Fiber::LocalData::freeHeapBuffer(void* buffer) {
242   delete[] reinterpret_cast<char*>(buffer);
243 }
244
245 }}