Skip to content

Commit 4231fbb

Browse files
Docs: add a design document for the Client
Add a document that states the requirements for the Client, and then proposes a design that fulfills them.
1 parent ac98986 commit 4231fbb

File tree

1 file changed

+306
-0
lines changed

1 file changed

+306
-0
lines changed

docs/client-design.md

+306
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
# Tntcxx Client Design
2+
3+
## Scope
4+
5+
This document describes the design of the Tntcxx Client. First we state the
6+
requirements and use cases we have in mind, and then we propose a design that
7+
fulfills them.
8+
9+
## Requirements
10+
11+
### Functional Requirements
12+
13+
We envision Tntcxx to be used primarily as a part of network applications (e.g.,
14+
an HTTP server), the Tntcxx Client backing requests to Tarantool. Such
15+
applications are usually built around a central event processing loop, which is
16+
responsible for multiplexing network I/O.
17+
18+
#### Application Event Processing Loop Integration
19+
20+
The first and foremost requirement is that there must be a convenient
21+
way to integrate the Tntcxx Client into the event processing loop used by the
22+
application. Moreover, the Tntcxx Client must never run the event processing
23+
loop itself.
24+
25+
At the same time, since event processing loops are inherently single threaded,
26+
we do expect the Tntcxx Client to be used in a multithreaded environment, i.e.,
27+
when connections and reqeust futures are in different threads. So we do not aim
28+
to design the Tntcxx Client to be thread-safe.
29+
30+
#### Asynchronous Request Processing
31+
32+
Since the Tntcxx Client is constrained from running the event processing loop,
33+
the Tntcxx Client must support asynchronous request processing through
34+
application-provided callbacks or futures.
35+
36+
#### Connection State
37+
38+
The application must be able to check the state of a Tntcxx Client.
39+
40+
#### Connection Error Handling
41+
42+
There must be a convenient way for the application to handle errors arising
43+
throughout the Tntcxx Client lifecycle. A connection error must be returned
44+
through the request callback and through the request object.
45+
46+
#### Request Handling
47+
48+
In order for the application to be able to manage a request, a request object is
49+
always returned. The application can check the request status, cancel the
50+
request, handle request errors and retrieve the response through this handle
51+
(only once). However, if the response was retrieved by other means (either
52+
returned through a callback or collected through scatter-gather), the handle
53+
cannot return the response.
54+
55+
#### Request Status
56+
57+
The application must be able to check the status of a Tntcxx Client request.
58+
59+
#### Request Timeout
60+
61+
Since the Tntcxx Client does not have control over the application's event
62+
processing loop, the application must implement its own request timeouts. The
63+
application can cancel stale requests.
64+
65+
#### Request Cancelling
66+
67+
The application must be able to cancel a Tntcxx Client request. Cancelling a
68+
request explicitly ends the lifetime of the corresponding response.
69+
70+
#### Request Retrying
71+
72+
Since the Tntcxx Client does not have control over the application's event
73+
processing loop, the application must implement its own request retrying.
74+
75+
#### Request Fan-Out
76+
77+
A common Tntcxx Client use case is fan-out to multiple Tarantool instances, and
78+
collection of responses received after some deadline, and discarding of requests
79+
that are not ready by the deadline.
80+
81+
#### Response Lifetime
82+
83+
The response lifetime is managed implicitly through the lifetime of the request.
84+
The response is not copyable. It can be retrieved only once, and the response
85+
ownership is moved to the application.
86+
87+
#### Reconnection
88+
89+
The Tntcxx Client must support implicit reconnection with the same session
90+
settings it was created with.
91+
92+
#### Connection Pool
93+
94+
TBD.
95+
96+
#### Transactions
97+
98+
TBD.
99+
100+
#### Failover
101+
102+
TBD.
103+
104+
## Design
105+
106+
### I/O Event Providers
107+
108+
```c++
109+
/** Callback called on a read event. */
110+
using read_ready_cb_f = void (*)(int fd, Data *data);
111+
/** Callback called on a write event. */
112+
using write_ready_cb_f = void (*)(int fd, Data *data);
113+
114+
/**
115+
* An I/O event provider encapsulates the notification about events for a
116+
* collection of file descriptors.
117+
*
118+
* `Data` is an opaque context type passed to the notification callbacks.
119+
*/
120+
template<class Data *>
121+
class IOEventProvider {
122+
public:
123+
/**
124+
* Register a file descriptor. Returns 0 on success, -1 on failure.
125+
*/
126+
int register(int fd, Data *data, read_ready_cb_f read_ready_cb,
127+
write_ready_cb_f write_ready_cb);
128+
129+
/**
130+
* Unregister a file descriptor. Returns 0 on success, -1 on failure.
131+
*/
132+
int unregister(int fd);
133+
};
134+
```
135+
136+
#### Epoll
137+
138+
```c++
139+
/**
140+
* Since `epoll` does not have a facility for storing callbacks, we delegate
141+
* calling the notification callbacks to the application.
142+
*
143+
* In order to distinguish between application file descriptors and tntcxx
144+
* sockets, we provide a wrapper class around the `Data`, which should be passed
145+
* as the `ptr` argument of `epoll_data` to `epoll` by the application for its
146+
* own file descriptors.
147+
*/
148+
template<class Data *>
149+
class EpollIOEventProviderData {
150+
public:
151+
EpollIOEventProviderData(int type, Data *data);
152+
153+
/**
154+
* Type of file descriptor, needed for distinguishing application file
155+
* descriptors from tntcxx sockets.
156+
*/
157+
int type;
158+
Data *data;
159+
};
160+
161+
/**
162+
* Encapsulates calling of notification callbacks from epoll for tntcxx.
163+
*/
164+
template<class Data *>
165+
class EpollIoEventProviderDataTntcxx : public EpollIOEventProviderData<Data *> {
166+
public:
167+
EpollIoEventProviderData(int fd, Data *data, read_ready_cb_f read_ready_cb, write_ready_cb_f write_ready_cb);
168+
169+
/** Needs to be called by the application on a read event. */
170+
void read_ready();
171+
/** Needs to be called by the application on a write event. */
172+
void write_ready();
173+
};
174+
```
175+
176+
### Connections
177+
178+
```c++
179+
/**
180+
* A connection encapsulates sending requests and receiving responses from one
181+
* Tarantool instance.
182+
*/
183+
template<class IOEventProvider>
184+
class Connection {
185+
public:
186+
Connection(IOEventProvider &net_provider,
187+
const ConnectionOptions &connection_options);
188+
189+
/**
190+
* Return the connection's state.
191+
*/
192+
enum ConnectionState get_state() const;
193+
194+
/**
195+
* If the connection is an erroneous state (see `status`), return the
196+
* connection error that caused it. The same error is also passed to
197+
* response callbacks, and the same error is returned by
198+
* `Request::get_error`.
199+
*/
200+
std::optional<ConnectionError> &get_error() & const;
201+
202+
/* An abstract request's interface. A request object is always returned. */
203+
Request some_request(/* options */);
204+
};
205+
```
206+
207+
#### Connection State
208+
209+
```c++
210+
enum ConnectionState {
211+
CONNECTION_INITIAL = 0,
212+
CONNECTION_AUTH = 1,
213+
CONNECTION_ACTIVE = 2,
214+
CONNECTION_ERROR = 3,
215+
CONNECTION_ERROR_RECONNECT = 4,
216+
};
217+
```
218+
219+
#### Connect Options
220+
221+
```c++
222+
/* Extend `ConnectionOptions` with an option for the reconnection feature. */
223+
struct ConnectOptions {
224+
/* All existing options. */
225+
226+
/**
227+
* In the event of a broken connection, the interval in which the stream
228+
* tries to re-establish the connection.
229+
*/
230+
static constexpr size_t DEFAULT_RECONNECTION_INTERVAL = 2;
231+
size_t reconnection_interval = DEFAULT_RECONNECTION_INTERVAL;
232+
};
233+
```
234+
235+
### Requests
236+
237+
```c++
238+
/** Encapsulates management of a request issued through a connection. */
239+
class Request {
240+
public:
241+
/**
242+
* Callback called when response is ready. Since the callback has a fixed
243+
* signature, we need to allow for capturing additional context using
244+
* lambdas. Hence, we use `std::function` for type erasure.
245+
*
246+
* See `Connection::get_error` for details about the `error` parameter.
247+
*/
248+
using request_cb_f =
249+
std::function<void(Request &&request, Connection &connection)>;
250+
251+
/** Set a callback called when the response is ready. */
252+
void set_callback(request_cb_f request_cb) &&;
253+
254+
/** Return the request's status. */
255+
enum RequestStatus get_status();
256+
257+
/**
258+
* Return a connection error, if any. See `Connection::get_error` for
259+
* details.
260+
*/
261+
std::optional<ConnectionError> &get_error() & const;
262+
263+
/**
264+
* Cancel the request, ending the lifetime of the corresponding response.
265+
*/
266+
void cancel();
267+
268+
/**
269+
* Return the response, if any. A response is available, iff:
270+
* 1. The response has been received by the connection.
271+
* 2. The response has not been dispatched to a callback.
272+
* 3. The response has not already been retrieved previously.
273+
*/
274+
std::optional<Response> get_response();
275+
};
276+
```
277+
278+
#### Request Status
279+
280+
```c++
281+
enum RequestStatus {
282+
REQUEST_SUCCESS = 0,
283+
REQUEST_ERROR = -1,
284+
REQUEST_IN_PROGRESS = 1,
285+
};
286+
```
287+
288+
#### Request Fan-Out
289+
290+
```c++
291+
/**
292+
* A fan-out encapsulates the collection of responses for a collection of
293+
* requests.
294+
*/
295+
class FanOut {
296+
template<class InputIt>
297+
FanOut(InputIt first, InputIt last)
298+
FanOut(std::initializer_list<Request> requests);
299+
300+
/**
301+
* Return a list of ready request, and cancel the requests for which a
302+
* response is not available.
303+
*/
304+
tnt::List<Request> collect();
305+
};
306+
```

0 commit comments

Comments
 (0)