7adb1a9c3d978024b4539e5ccfbd9f422669c505
[folly.git] / folly / io / async / Request.h
1 /*
2  * Copyright 2016 Facebook, Inc.
3  *
4  * Licensed to the Apache Software Foundation (ASF) under one
5  * or more contributor license agreements. See the NOTICE file
6  * distributed with this work for additional information
7  * regarding copyright ownership. The ASF licenses this file
8  * to you under the Apache License, Version 2.0 (the
9  * "License"); you may not use this file except in compliance
10  * with the License. You may obtain a copy of the License at
11  *
12  *   http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing,
15  * software distributed under the License is distributed on an
16  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17  * KIND, either express or implied. See the License for the
18  * specific language governing permissions and limitations
19  * under the License.
20  */
21 #pragma once
22
23 #include <map>
24 #include <memory>
25 #include <glog/logging.h>
26 #include <folly/RWSpinLock.h>
27 #include <folly/SingletonThreadLocal.h>
28
29 namespace folly {
30
31 // Some request context that follows an async request through a process
32 // Everything in the context must be thread safe
33
34 class RequestData {
35  public:
36   virtual ~RequestData() = default;
37   virtual void onSet(){};
38   virtual void onUnset(){};
39 };
40
41 class RequestContext;
42
43 // If you do not call create() to create a unique request context,
44 // this default request context will always be returned, and is never
45 // copied between threads.
46 class RequestContext {
47  public:
48   // Create a unique request context for this request.
49   // It will be passed between queues / threads (where implemented),
50   // so it should be valid for the lifetime of the request.
51   static void create() {
52     getStaticContext() = std::make_shared<RequestContext>();
53   }
54
55   // Get the current context.
56   static RequestContext* get() {
57     auto context = getStaticContext();
58     if (!context) {
59       static RequestContext defaultContext;
60       return std::addressof(defaultContext);
61     }
62     return context.get();
63   }
64
65   // The following API may be used to set per-request data in a thread-safe way.
66   // This access is still performance sensitive, so please ask if you need help
67   // profiling any use of these functions.
68   void setContextData(
69     const std::string& val, std::unique_ptr<RequestData> data) {
70     folly::RWSpinLock::WriteHolder guard(lock);
71     if (data_.find(val) != data_.end()) {
72       LOG_FIRST_N(WARNING, 1) <<
73         "Called RequestContext::setContextData with data already set";
74
75       data_[val] = nullptr;
76     } else {
77       data_[val] = std::move(data);
78     }
79   }
80
81   // Unlike setContextData, this method does not panic if the key is already
82   // present. Returns true iff the new value has been inserted.
83   bool setContextDataIfAbsent(const std::string& val,
84                               std::unique_ptr<RequestData> data) {
85     folly::RWSpinLock::UpgradedHolder guard(lock);
86     if (data_.find(val) != data_.end()) {
87       return false;
88     }
89
90     folly::RWSpinLock::WriteHolder writeGuard(std::move(guard));
91     data_[val] = std::move(data);
92     return true;
93   }
94
95   bool hasContextData(const std::string& val) {
96     folly::RWSpinLock::ReadHolder guard(lock);
97     return data_.find(val) != data_.end();
98   }
99
100   RequestData* getContextData(const std::string& val) {
101     folly::RWSpinLock::ReadHolder guard(lock);
102     auto r = data_.find(val);
103     if (r == data_.end()) {
104       return nullptr;
105     } else {
106       return r->second.get();
107     }
108   }
109
110   void onSet() {
111     folly::RWSpinLock::ReadHolder guard(lock);
112     for (auto const& ent : data_) {
113       if (RequestData* data = ent.second.get()) {
114         data->onSet();
115       }
116     }
117   }
118
119   void onUnset() {
120     folly::RWSpinLock::ReadHolder guard(lock);
121     for (auto const& ent : data_) {
122       if (RequestData* data = ent.second.get()) {
123         data->onUnset();
124       }
125     }
126   }
127
128   void clearContextData(const std::string& val) {
129     folly::RWSpinLock::WriteHolder guard(lock);
130     data_.erase(val);
131   }
132
133   // The following API is used to pass the context through queues / threads.
134   // saveContext is called to get a shared_ptr to the context, and
135   // setContext is used to reset it on the other side of the queue.
136   //
137   // Whenever possible, use RequestContextScopeGuard instead of setContext
138   // to make sure that RequestContext is reset to the original value when
139   // we exit the scope.
140   //
141   // A shared_ptr is used, because many request may fan out across
142   // multiple threads, or do post-send processing, etc.
143   static std::shared_ptr<RequestContext>
144   setContext(std::shared_ptr<RequestContext> ctx) {
145     auto& prev = getStaticContext();
146     if (ctx != prev) {
147       using std::swap;
148       if (ctx) {
149         ctx->onSet();
150       }
151       if (prev) {
152         prev->onUnset();
153       }
154       swap(ctx, prev);
155     }
156     return ctx;
157   }
158
159   static std::shared_ptr<RequestContext> saveContext() {
160     return getStaticContext();
161   }
162
163  private:
164   static std::shared_ptr<RequestContext>& getStaticContext();
165
166   folly::RWSpinLock lock;
167   std::map<std::string, std::unique_ptr<RequestData>> data_;
168 };
169
170 class RequestContextScopeGuard {
171  private:
172   std::shared_ptr<RequestContext> prev_;
173
174  public:
175   // Create a new RequestContext and reset to the original value when
176   // this goes out of scope.
177   RequestContextScopeGuard() : prev_(RequestContext::saveContext()) {
178     RequestContext::create();
179   }
180
181   // Set a RequestContext that was previously captured by saveContext(). It will
182   // be automatically reset to the original value when this goes out of scope.
183   explicit RequestContextScopeGuard(std::shared_ptr<RequestContext> ctx)
184       : prev_(RequestContext::setContext(std::move(ctx))) {}
185
186   ~RequestContextScopeGuard() {
187     RequestContext::setContext(std::move(prev_));
188   }
189 };
190 }