Skip to content

Commit d76f3ca

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 d76f3ca

File tree

1 file changed

+287
-0
lines changed

1 file changed

+287
-0
lines changed

docs/client-design.md

+287
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
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 Status
37+
38+
The application must be able to check the status 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, connection read_ready_cb_f read_ready_cb, write_ready_cb_f write_ready_cb);
127+
128+
/**
129+
* Deregister a file descriptor. Returns 0 on success, -1 on failure.
130+
*/
131+
int deregister(int fd);
132+
};
133+
```
134+
135+
#### Epoll
136+
137+
```c++
138+
/**
139+
* Since `epoll` does not have a facility for storing callbacks, we delegate
140+
* calling the notification callbacks to the application.
141+
*
142+
* In order to distinguish between application file descriptors and tntcxx
143+
* sockets, we provide a wrapper class around the `Data`, which should be passed
144+
* as the `ptr` argument of `epoll_data` to `epoll` by the application for its
145+
* own file descriptors.
146+
*/
147+
template<class Data *>
148+
class EpollIOEventProviderData {
149+
public:
150+
EpollIOEventProviderData(int type, Data *data);
151+
152+
/**
153+
* Type of file descriptor, needed for distinguishing application file
154+
* descriptors from tntcxx sockets.
155+
*/
156+
int type;
157+
Data *data;
158+
};
159+
160+
/**
161+
* Encapsulates calling of notification callbacks from epoll for tntcxx.
162+
*/
163+
template<class Data *>
164+
class EpollIoEventProviderDataTntcxx : public EpollIOEventProviderData<Data *> {
165+
public:
166+
EpollIoEventProviderData(int fd, Data *data, read_ready_cb_f read_ready_cb, write_ready_cb_f write_ready_cb);
167+
168+
/** Needs to be called by the application on a read event. */
169+
void read_ready();
170+
/** Needs to be called by the application on a write event. */
171+
void write_ready();
172+
};
173+
```
174+
175+
### Connections
176+
177+
```c++
178+
/**
179+
* A connection encapsulates sending requests and receiving responses from one
180+
* Tarantool instance.
181+
*/
182+
template<class IOEventProvider>
183+
class Connection {
184+
public:
185+
Connection(IOEventProvider &net_provider,
186+
const ConnectionOptions &connection_options);
187+
188+
/**
189+
* Return the connection's status.
190+
*/
191+
enum class ConnectionStatus get_status() const;
192+
193+
/**
194+
* If the connection is an erroneous state (see `status`), return the
195+
* connection error that caused it. The same error is also passed to
196+
* response callbacks, and the same error is returned by
197+
* `Request::get_error`.
198+
*/
199+
std::optional<ConnectionError> &get_error() & const;
200+
201+
/* An abstract request's interface. A request object is always returned. */
202+
Request some_request(/* options */);
203+
};
204+
```
205+
206+
#### Connect Options
207+
208+
```c++
209+
/* Extend `ConnectionOptions` with an option for the reconnection feature. */
210+
struct ConnectOptions {
211+
/* All existing options. */
212+
213+
/**
214+
* In the event of a broken connection, the interval in which the stream
215+
* tries to re-establish the connection.
216+
*/
217+
static constexpr size_t DEFAULT_RECONNECTION_INTERVAL = 2;
218+
size_t reconnection_interval = DEFAULT_RECONNECTION_INTERVAL;
219+
};
220+
```
221+
222+
### Requests
223+
224+
```c++
225+
/** Encapsulates management of a request issued through a connection. */
226+
class Request {
227+
public:
228+
/**
229+
* Callback called when response is ready. Since the callback has a fixed
230+
* signature, we need to allow for capturing additional context using
231+
* lambdas. Hence, we use `std::function` for type erasure.
232+
*
233+
* See `Connection::get_error` for details about the `error` parameter.
234+
*/
235+
using request_cb_f =
236+
std::function<void(const std::optional<ConnectionError> &error,
237+
Response &&response, Connection &connection)>;
238+
239+
/** Set a callback called when the response is ready. */
240+
int set_callback(request_cb_f request_cb);
241+
242+
/** Remove the callback associated with the request, if any. */
243+
void reset_callback();
244+
245+
/** Return the request's status. */
246+
enum class RequestStatus get_status();
247+
248+
/**
249+
* Return a connection error, if any. See `Connection::get_error` for
250+
* details.
251+
*/
252+
std::optional<ConnectionError> &get_error() & const;
253+
254+
/**
255+
* Cancel the request, ending the lifetime of the corresponding response.
256+
*/
257+
void cancel();
258+
259+
/**
260+
* Return the response, if any. A response is available, iff:
261+
* 1. The response has been received by the connection.
262+
* 2. The response has not been dispatched to a callback.
263+
* 3. The response has not already been retrieved previously.
264+
*/
265+
std::optional<Response> get_response();
266+
};
267+
```
268+
269+
#### Request Fan-Out
270+
271+
```c++
272+
/**
273+
* A fan-out encapsulates the collection of responses for a collection of
274+
* requests.
275+
*/
276+
class FanOut {
277+
template<class InputIt>
278+
FanOut(InputIt first, InputIt last)
279+
FanOut(std::initializer_list<Request> requests);
280+
281+
/**
282+
* Return a list of responses, and cancel the requests for which a response
283+
* is not available.
284+
*/
285+
tnt::List<Response> collect();
286+
};
287+
```

0 commit comments

Comments
 (0)