Skip to content

Commit 0ffcfa0

Browse files
thread-local connection recipe: Document the recipe.
1 parent f8b8396 commit 0ffcfa0

2 files changed

Lines changed: 45 additions & 1 deletion

File tree

docs/recipes.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ sqlpp23 can be extended to make it more powerful or easy to use in a given conte
77
This section contains some recipes for doing so.
88

99
* [Add a custom SQL function](/docs/recipes/custom_function.md)
10-
* [Optimistic concurrency control](/docs/recipes/optimistic_concurrency_control.md)
1110
* [Mapping to/from key-value store](/docs/recipes/key_value_store.md)
11+
* [Optimistic concurrency control](/docs/recipes/optimistic_concurrency_control.md)
12+
* [Thread-local connection](/docs/recipes/thread_local_connection.md)
1213

1314
Additional ideas are welcome, of course. Please file issues or pull requests.
1415

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
[**\< Recipes**](/docs/recipes.md)
2+
3+
# Thread safety in the database connectors
4+
5+
As of the time of this writing, database connections created by the three main databases supported by sqlpp23 (MySQL, PostgreSQL, and SQLite3) are not thread-safe. The sqlpp23 library is thread-agnostic, which means that it does not add any requirements or guarantees to the thread safety of the underlying database objects and operations. So it is up to the library user to ensure the thread safety of the database operations performed through these database connections.
6+
7+
## Making thread-safe queries using thread-unsafe connectors
8+
9+
In this document we provide a simple pattern that allows us to make thread-safe database queries using thread-unsafe database connections. The pattern is based on the idea that each user thread is given its own database connection. When a thread wants to execute a database query, it uses its own database connection to execute the query, thus avoiding the need to implement complex and potentially expensive thread synchronization.
10+
11+
We define a database connection class called `lazy_connection`, which mimics the regular database connections provided by sqlpp23 and lets the user execute database queries, pretty much like a regular sqlpp connection does. In fact, our lazy connection creates an underlying sqlpp database connection and forwards all database queries to the sqlpp connection, but that sqlpp connection is not
12+
created immediately in the constructor of the lazy connection. Instead, its creation is postponed until the moment when the user tries to execute their first query through our lazy connection object, which is why our connection class is called "lazy".
13+
14+
There is only one, global instance of our `lazy_connection` class, called `g_dbc`, which is defined as
15+
```
16+
thread_local lazy_connection g_dbc{g_pool};
17+
```
18+
19+
As you can see from the definition of g_dbc, it is defined as `thread_local`, which means that each user thread gets its own copy of `g_dbc`, stored in the thread's TLS (Thread Local Storage). By using the `thread_local` keyword, we offload all the thread-related chores to the C++ compiler and runtime. When a user thread tries to execute a query through `g_dbc`, the C++ runtime automatically gets a thread-local lazy connection, creating it if necessary. The lazy connection in turn gets a new sqlpp connection from the thread pool and uses it to execute the query. The thread pool is merely an implementation detail; strictly speaking, we could skip the thread pool and create a new connection inside `lazy_connection`, but we use the connection pool for performance reasons.
20+
21+
## Why not make the connection object local?
22+
23+
One might be tempted to make our instance of `lazy_connection` local; after all, a local variable can also be declared as `thread_local`. So why did we make `g_dbc` local? It is because making it local does not work the way one might expect. Let's say that we try to define and use the thread-local lazy connection in block scope:
24+
```
25+
sqlpp::postgresql::connection_pool g_pool{...};
26+
27+
int main()
28+
{
29+
thread_local dbc{&g_pool};
30+
std::thread t{[&] {
31+
dbc(...);
32+
}};
33+
t.join ();
34+
}
35+
```
36+
37+
Attempting to use our lazy connection in this fashion will cause a runtime error, because the newly spawned thread uses an uninitialized copy of the lazy connection. While global thread-local variables are guaranteed to be initialized the moment when a thread tries to use them, the local thread-local variables are only initialized when execution passes through their definition. The newly spawned thread never actually entered the `main()` function, so its thread-local copy of the database connection was never initialized, and the attempt to use the uninitialized lazy connection caused the runtime error.
38+
39+
## Sample code
40+
41+
The sample source code, implementing this pattern, is available [here](/tests/postgresql/recipes/thread_local_connection.cpp).
42+
43+
[**\< Recipes**](/docs/recipes.md)

0 commit comments

Comments
 (0)