proxygen
EvbHandshakeHelper.h
Go to the documentation of this file.
1 /*
2  * Copyright 2017-present 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 #pragma once
17 
18 #include <memory>
19 #include <utility>
20 
24 
25 namespace wangle {
36  public:
39  folly::EventBase* handshakeEvb)
40  : helper_(std::move(helper)),
42  handshakeEvb_(handshakeEvb) {}
43 
44  void start(
46  AcceptorHandshakeHelper::Callback* callback) noexcept override;
47 
48  void dropConnection(SSLErrorEnum reason = SSLErrorEnum::NO_ERROR) override;
49 
50  // Exposing these for tests, do not directly call these callbacks.
51  virtual void connectionReady(
53  std::string nextProtocol,
54  SecureTransportType secureTransportType,
55  folly::Optional<SSLErrorEnum> sslErr) noexcept override;
56 
57  virtual void connectionError(
60  folly::Optional<SSLErrorEnum> sslErr) noexcept override;
61 
62  protected:
63  enum class HandshakeState : unsigned {
64  Invalid,
65  Started,
66  Dropped,
67  Callback,
68  };
69 
71 
74 
77 
78  // state_ is used to prevent dropConnection(), connectionReady(),
79  // and connectionError() from being called at the same time.
80  //
81  // State machine diagram:
82  // dropConnection()
83  // .--------> Dropped
84  // start() /
85  // Invalid ------> Started ---+
86  // \
87  // `--------> Callback
88  // connectionReady()
89  // connectionError()
90  //
91  //
92  // We primarily rely on the atomic updating of state_ to prevent the following
93  // dangerous scenario:
94  //
95  // ---------------D--C'------------ Acceptor Thread (originalEvb_)
96  // \/
97  // /\
98  // ---------------C--D'------------ Handshake Thread (handshakeEvb_)
99  //
100  // Notation:
101  // D is the EvbHandshakeHelper::dropConnection() call
102  // D' is the helper->dropConnection() call scheduled by D to run on the
103  // handshake thread.
104  // C is an incoming connectionReady()/connectionError() callback
105  // C' is the call to callback_->connectionReady()/connectionError() on the
106  // acceptor thread.
107  //
108  // We explicitly disallow this scenario: if this scenario were to happen, then
109  // D' would call helper->dropConnection() on the handshake thread when the
110  // underlying transport's eventbase is no longer attached to the handshake
111  // thread.
112  //
113  // Because dropConnection(), connectionReady()/connectionError() will race
114  // to update this atomic with cmpxchg; only one path will "win". So, there
115  // only TWO cases to consider.
116  //
117  // (Remember that in all of these cases, the handshake thread is invisible to
118  // an observer. As far as wangle::Acceptor is concerned, there is only one
119  // acceptor thread. This means that from an observer's point of view, as long
120  // as some event has not occured on the originalEvb_ thread, then that event
121  // has never occured at all. (Trees falling in forests when the observer
122  // doesn't live in the forest))
123  //
124  // # Case 1: D and C race, and D wins:
125  //
126  // --------------D----------------- Acceptor Thread (originalEvb_)
127  // \
128  // \
129  // ---------------C--D'------------ Handshake Thread (handshakeEvb_)
130  //
131  // From an observer's point of view, dropConnection() is the only call that
132  // happens on the acceptor thread; C occurs "later" than D (w.r.t updating
133  // the atomic state), so C will never schedule anything to run on
134  // the acceptor thread.
135  //
136  //
137  // # Case 2: D and C race; C wins, but D comes first on the acceptor thread
138  //
139  // -----------------D-C'----------- Acceptor Thread (originalEvb_)
140  // /
141  // /
142  // ---------------C---------------- Handshake Thread (handshakeEvb_)
143  //
144  // In this case, C won the race to update the state, so it schedules a
145  // callback to occur on the acceptor thread. HOWEVER, before that callback
146  // actually runs on the acceptor thread, the observer calls dropConnection().
147  //
148  // From the observer's point of view, at the time it made the dropConnection()
149  // call, it had never seen a callback before.
150  //
151  // Therefore, in this case, in order to ensure that the state of the world
152  // is consistent with the observer, we need to ensure that when C' actually
153  // runs, *it doesn't fire the callback*.
154  //
155  // So, D realizes it lost the race against C for updating the state.
156  // Therefore, D will not schedule anything to run in the handshake thread.
157  // HOWEVER, it will set dropConnectionGuard_, which will serve as an
158  // indicator to C' that dropConnection() was called first, so C' should not
159  // run.
160  //
161  // These are the only two valid cases that can happen. In a sense, state_'s
162  // primary purpose is to ensure that only one path can mutate the transport's
163  // eventbase through attachEventBase() and detachEventBase().
164  //
165  // Here are some other states that we do not have to consider, for
166  // completeness:
167  //
168  // # Invalid CASE 1: D comes after C'.
169  //
170  // -------------------C'-D--------- Acceptor Thread (originalEvb_)
171  // /
172  // /
173  // ---------------C---------------- Handshake Thread (handshakeEvb_)
174  //
175  // What if we reverse the order of events on the acceptor thread in Case 2,
176  // so that C' runs before D?
177  //
178  // This is an invalid case. When C' runs, it will notice that no
179  // dropConnectionGuard_ was set, which means that from the point of view
180  // of an "observer", C' occurs first, and D never happened.
181  //
182  // When C' runs, it will call the callback's connectionReady, which will
183  // delete the entire helper. D cannot possibly run, because any upstream
184  // callers will see that this helper doesn't exist.
185  //
186  //
187  // # Invalid CASE 2: D occurs much earlier than C
188  //
189  // ---D---------------------------- Acceptor Thread (originalEvb_)
190  // \
191  // \
192  // ------D'-------C---------------- Handshake Thread (handshakeEvb_)
193  //
194  // In this case, D will obviously be able to update the state_ to Dropped.
195  // Therefore, it will be able to schedule D' to run on the handshake thread.
196  //
197  // When D' runs, the transport will be closed and cleaned up, so it is
198  // impossible for a C to ever arrive on the handshake thread after D' (the
199  // handshake callback will never fire!). Of course, in the process of running
200  // D', the underlying handshake helper may synchronously call
201  // connectionError. But since connectionError will fail to update the state_,
202  // it does nothing and immediately returns.
203  //
204  std::atomic<HandshakeState> state_{HandshakeState::Invalid};
205 
206  // dropConnectionGuard_ is used to keep the EvbHandshakeHelper alive when
207  // dropConnection() is called until after the underlying helper's
208  // dropConnection is called.
211 
212  private:
213  std::pair<bool, HandshakeState> tryTransition(
214  HandshakeState expected,
216 };
217 }
virtual void connectionError(folly::AsyncTransportWrapper *transport, folly::exception_wrapper ex, folly::Optional< SSLErrorEnum > sslErr) noexceptoverride
SSLErrorEnum
Definition: SSLUtil.h:42
constexpr detail::Map< Move > move
Definition: Base-inl.h:2567
STL namespace.
void start(folly::AsyncSSLSocket::UniquePtr sock, AcceptorHandshakeHelper::Callback *callback) noexceptoverride
requires E e noexcept(noexcept(s.error(std::move(e))))
#define nullptr
Definition: http_parser.c:41
AcceptorHandshakeHelper::Callback * callback_
folly::EventBase * originalEvb_
std::unique_ptr< AsyncSSLSocket, Destructor > UniquePtr
std::unique_ptr< AsyncTransportWrapper, Destructor > UniquePtr
void dropConnection(SSLErrorEnum reason=SSLErrorEnum::NO_ERROR) override
std::pair< bool, HandshakeState > tryTransition(HandshakeState expected, HandshakeState next)
folly::EventBase * handshakeEvb_
AcceptorHandshakeHelper::UniquePtr helper_
virtual void connectionReady(folly::AsyncTransportWrapper::UniquePtr transport, std::string nextProtocol, SecureTransportType secureTransportType, folly::Optional< SSLErrorEnum > sslErr) noexceptoverride
const char * string
Definition: Conv.cpp:212
EvbHandshakeHelper(AcceptorHandshakeHelper::UniquePtr helper, folly::EventBase *handshakeEvb)
std::atomic< HandshakeState > state_
std::unique_ptr< AcceptorHandshakeHelper, folly::DelayedDestruction::Destructor > UniquePtr
folly::Optional< folly::DelayedDestruction::DestructorGuard > dropConnectionGuard_
def next(obj)
Definition: ast.py:58