Skip to content

Commit 3098a06

Browse files
committedSep 5, 2024·
WIP: mariadb-async
1 parent c8cb993 commit 3098a06

File tree

3 files changed

+127
-55
lines changed

3 files changed

+127
-55
lines changed
 

‎src/samples/techempower/database.c

+93-34
Original file line numberDiff line numberDiff line change
@@ -19,28 +19,36 @@
1919
*/
2020

2121
#include <assert.h>
22-
#include <string.h>
2322
#include <mysql.h>
2423
#include <sqlite3.h>
24+
#include <stdarg.h>
2525
#include <stddef.h>
2626
#include <stdlib.h>
27-
#include <stdarg.h>
27+
#include <string.h>
2828

2929
#include "database.h"
3030
#include "lwan-status.h"
3131

32+
/* Including "lwan.h" introduces namespace conflicts (linked-list
33+
* functions from CCAN and MySQL headers.) */
34+
struct lwan_request;
35+
int lwan_request_await_read(struct lwan_request *r, int fd);
36+
int lwan_request_await_write(struct lwan_request *r, int fd);
37+
int lwan_request_await_read_write(struct lwan_request *r, int fd);
38+
3239
struct db_stmt {
33-
bool (*bind)(const struct db_stmt *stmt,
34-
struct db_row *rows);
40+
bool (*bind)(const struct db_stmt *stmt, struct db_row *rows);
3541
bool (*step)(const struct db_stmt *stmt, va_list ap);
3642
void (*finalize)(struct db_stmt *stmt);
3743
const char *param_signature;
3844
const char *result_signature;
45+
void *ctx;
3946
};
4047

4148
struct db {
4249
void (*disconnect)(struct db *db);
4350
struct db_stmt *(*prepare)(const struct db *db,
51+
void *ctx,
4452
const char *sql,
4553
const char *param_signature,
4654
const char *result_signature);
@@ -58,13 +66,13 @@ struct db_stmt_mysql {
5866
MYSQL_STMT *stmt;
5967
MYSQL_BIND *param_bind;
6068
MYSQL_BIND *result_bind;
69+
int db_fd;
6170
bool must_execute_again;
6271
bool results_are_bound;
6372
MYSQL_BIND param_result_bind[];
6473
};
6574

66-
static bool db_stmt_bind_mysql(const struct db_stmt *stmt,
67-
struct db_row *rows)
75+
static bool db_stmt_bind_mysql(const struct db_stmt *stmt, struct db_row *rows)
6876
{
6977
struct db_stmt_mysql *stmt_mysql = (struct db_stmt_mysql *)stmt;
7078
const char *signature = stmt->param_signature;
@@ -95,16 +103,55 @@ static bool db_stmt_bind_mysql(const struct db_stmt *stmt,
95103
return !mysql_stmt_bind_param(stmt_mysql->stmt, stmt_mysql->param_bind);
96104
}
97105

98-
static bool db_stmt_step_mysql(const struct db_stmt *stmt,
99-
va_list ap)
106+
static bool db_stmt_step_mysql(const struct db_stmt *stmt, va_list ap)
100107
{
101108
struct db_stmt_mysql *stmt_mysql = (struct db_stmt_mysql *)stmt;
102109

103110
if (stmt_mysql->must_execute_again) {
104111
stmt_mysql->must_execute_again = false;
105112
stmt_mysql->results_are_bound = false;
106-
if (mysql_stmt_execute(stmt_mysql->stmt))
107-
return false;
113+
114+
int status;
115+
116+
mysql_stmt_execute_start(&status, stmt_mysql->stmt);
117+
while (status) {
118+
/* FIXME: Handle MYSQL_WAIT_TIMEOUT and MYSQL_WAIT_EXCEPT
119+
* properly.
120+
*
121+
* TIMEOUT is handled by calling mysql_get_timeout_value()
122+
* and passing it to the await functions; that's not currently
123+
* supported in Lwan, at least not easily (one could set a
124+
* timeout by hand, similar to how lwan_request_sleep() does,
125+
* but instead of yielding with CONN_CORO_SUSPEND, one would
126+
* forward the arguments to the actual called await function;
127+
* upon return, one would check what caused the coroutine to
128+
* be resumed, and return -ETIMEDOUT or something.
129+
*
130+
* EXCEPT is handled like EPOLLPRI. Currently unsupported
131+
* in Lwan too, although it's easier to implement than timeout.
132+
*
133+
* For now, ignore both flags. */
134+
status &= ~(MYSQL_WAIT_TIMEOUT | MYSQL_WAIT_EXCEPT);
135+
136+
switch (status) {
137+
case MYSQL_WAIT_READ:
138+
lwan_request_await_read(stmt_mysql->base.ctx,
139+
stmt_mysql->db_fd);
140+
break;
141+
case MYSQL_WAIT_WRITE:
142+
lwan_request_await_write(stmt_mysql->base.ctx,
143+
stmt_mysql->db_fd);
144+
break;
145+
case MYSQL_WAIT_READ | MYSQL_WAIT_WRITE:
146+
/* FIXME: how do we know what will cause the coroutine
147+
* to resume, a read or a write? */
148+
lwan_request_await_read_write(stmt_mysql->base.ctx,
149+
stmt_mysql->db_fd);
150+
break;
151+
}
152+
153+
status = mysql_stmt_execute_cont(&status, stmt_mysql->stmt, status);
154+
}
108155
}
109156

110157
if (!stmt_mysql->results_are_bound) {
@@ -154,15 +201,16 @@ static void db_stmt_finalize_mysql(struct db_stmt *stmt)
154201
free(stmt_mysql);
155202
}
156203

157-
static struct db_stmt *
158-
db_prepare_mysql(const struct db *db,
159-
const char *sql,
160-
const char *param_signature,
161-
const char *result_signature)
204+
static struct db_stmt *db_prepare_mysql(const struct db *db,
205+
void *ctx,
206+
const char *sql,
207+
const char *param_signature,
208+
const char *result_signature)
162209
{
163210
const struct db_mysql *db_mysql = (const struct db_mysql *)db;
164211
const size_t n_bounds = strlen(param_signature) + strlen(result_signature);
165-
struct db_stmt_mysql *stmt_mysql = malloc(sizeof(*stmt_mysql) + n_bounds * sizeof(MYSQL_BIND));
212+
struct db_stmt_mysql *stmt_mysql =
213+
malloc(sizeof(*stmt_mysql) + n_bounds * sizeof(MYSQL_BIND));
166214

167215
if (!stmt_mysql)
168216
return NULL;
@@ -175,19 +223,24 @@ db_prepare_mysql(const struct db *db,
175223
goto out_close_stmt;
176224

177225
assert(strlen(param_signature) == mysql_stmt_param_count(stmt_mysql->stmt));
178-
assert(strlen(result_signature) == mysql_stmt_field_count(stmt_mysql->stmt));
226+
assert(strlen(result_signature) ==
227+
mysql_stmt_field_count(stmt_mysql->stmt));
179228

180229
stmt_mysql->base.bind = db_stmt_bind_mysql;
181230
stmt_mysql->base.step = db_stmt_step_mysql;
182231
stmt_mysql->base.finalize = db_stmt_finalize_mysql;
183232
stmt_mysql->param_bind = &stmt_mysql->param_result_bind[0];
184-
stmt_mysql->result_bind = &stmt_mysql->param_result_bind[strlen(param_signature)];
233+
stmt_mysql->result_bind =
234+
&stmt_mysql->param_result_bind[strlen(param_signature)];
185235
stmt_mysql->must_execute_again = true;
186236
stmt_mysql->results_are_bound = false;
187237

188238
stmt_mysql->base.param_signature = param_signature;
189239
stmt_mysql->base.result_signature = result_signature;
190240

241+
stmt_mysql->db_fd = mysql_get_socket(db_mysql->con);
242+
stmt_mysql->base.ctx = ctx;
243+
191244
memset(stmt_mysql->param_result_bind, 0, n_bounds * sizeof(MYSQL_BIND));
192245

193246
return (struct db_stmt *)stmt_mysql;
@@ -231,6 +284,11 @@ struct db *db_connect_mysql(const char *host,
231284
if (mysql_set_character_set(db_mysql->con, "utf8"))
232285
goto error;
233286

287+
if (mysql_optionsv(db_mysql->con, MYSQL_OPT_NONBLOCK, 0)) {
288+
lwan_status_error("Could not enable non-blocking mode");
289+
goto error;
290+
}
291+
234292
db_mysql->base.disconnect = db_disconnect_mysql;
235293
db_mysql->base.prepare = db_prepare_mysql;
236294

@@ -254,8 +312,7 @@ struct db_stmt_sqlite {
254312
sqlite3_stmt *sqlite;
255313
};
256314

257-
static bool db_stmt_bind_sqlite(const struct db_stmt *stmt,
258-
struct db_row *rows)
315+
static bool db_stmt_bind_sqlite(const struct db_stmt *stmt, struct db_row *rows)
259316
{
260317
const struct db_stmt_sqlite *stmt_sqlite =
261318
(const struct db_stmt_sqlite *)stmt;
@@ -270,8 +327,8 @@ static bool db_stmt_bind_sqlite(const struct db_stmt *stmt,
270327

271328
switch (signature[row]) {
272329
case 's':
273-
ret = sqlite3_bind_text(stmt_sqlite->sqlite, (int)row + 1, r->u.s, -1,
274-
NULL);
330+
ret = sqlite3_bind_text(stmt_sqlite->sqlite, (int)row + 1, r->u.s,
331+
-1, NULL);
275332
break;
276333
case 'i':
277334
ret = sqlite3_bind_int(stmt_sqlite->sqlite, (int)row + 1, r->u.i);
@@ -287,8 +344,7 @@ static bool db_stmt_bind_sqlite(const struct db_stmt *stmt,
287344
return true;
288345
}
289346

290-
static bool db_stmt_step_sqlite(const struct db_stmt *stmt,
291-
va_list ap)
347+
static bool db_stmt_step_sqlite(const struct db_stmt *stmt, va_list ap)
292348
{
293349
const struct db_stmt_sqlite *stmt_sqlite =
294350
(const struct db_stmt_sqlite *)stmt;
@@ -328,11 +384,11 @@ static void db_stmt_finalize_sqlite(struct db_stmt *stmt)
328384
free(stmt_sqlite);
329385
}
330386

331-
static struct db_stmt *
332-
db_prepare_sqlite(const struct db *db,
333-
const char *sql,
334-
const char *param_signature,
335-
const char *result_signature)
387+
static struct db_stmt *db_prepare_sqlite(const struct db *db,
388+
void *ctx,
389+
const char *sql,
390+
const char *param_signature,
391+
const char *result_signature)
336392
{
337393
const struct db_sqlite *db_sqlite = (const struct db_sqlite *)db;
338394
struct db_stmt_sqlite *stmt_sqlite = malloc(sizeof(*stmt_sqlite));
@@ -354,6 +410,8 @@ db_prepare_sqlite(const struct db *db,
354410
stmt_sqlite->base.param_signature = param_signature;
355411
stmt_sqlite->base.result_signature = result_signature;
356412

413+
stmt_sqlite->base.ctx = ctx;
414+
357415
return (struct db_stmt *)stmt_sqlite;
358416
}
359417

@@ -414,10 +472,11 @@ inline void db_stmt_finalize(struct db_stmt *stmt) { stmt->finalize(stmt); }
414472

415473
inline void db_disconnect(struct db *db) { db->disconnect(db); }
416474

417-
inline struct db_stmt *db_prepare_stmt(const struct db *db,
418-
const char *sql,
419-
const char *param_signature,
420-
const char *result_signature)
475+
inline struct db_stmt *db_prepare_stmt_ctx(const struct db *db,
476+
void *ctx,
477+
const char *sql,
478+
const char *param_signature,
479+
const char *result_signature)
421480
{
422-
return db->prepare(db, sql, param_signature, result_signature);
481+
return db->prepare(db, ctx, sql, param_signature, result_signature);
423482
}

‎src/samples/techempower/database.h

+12-1
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,21 @@ struct db_row {
3434
};
3535

3636

37-
struct db_stmt *db_prepare_stmt(const struct db *db,
37+
struct db_stmt *db_prepare_stmt_ctx(const struct db *db,
38+
void *ctx,
3839
const char *sql,
3940
const char *param_signature,
4041
const char *result_signature);
42+
43+
static inline struct db_stmt *db_prepare_stmt(const struct db *db,
44+
const char *sql,
45+
const char *param_signature,
46+
const char *result_signature)
47+
{
48+
return db_prepare_stmt_ctx(db, NULL, sql, param_signature,
49+
result_signature);
50+
}
51+
4152
void db_stmt_finalize(struct db_stmt *stmt);
4253

4354
bool db_stmt_bind(const struct db_stmt *stmt, struct db_row *rows);

‎src/samples/techempower/techempower.c

+22-20
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@
2222
#include <stdlib.h>
2323
#include <string.h>
2424

25-
#include "lwan-private.h"
25+
#include "int-to-str.h"
2626
#include "lwan-cache.h"
2727
#include "lwan-config.h"
28-
#include "lwan-template.h"
2928
#include "lwan-mod-lua.h"
30-
#include "int-to-str.h"
29+
#include "lwan-private.h"
30+
#include "lwan-template.h"
3131

3232
#include "database.h"
3333
#include "json.h"
@@ -63,6 +63,8 @@ struct Fortune {
6363
int id;
6464
char *message;
6565
} item;
66+
67+
struct lwan_request *request;
6668
};
6769

6870
DEFINE_ARRAY_TYPE_INLINEFIRST(fortune_array, struct Fortune)
@@ -219,7 +221,8 @@ static inline bool db_query(struct db_stmt *stmt, struct db_json *out)
219221

220222
LWAN_HANDLER(db)
221223
{
222-
struct db_stmt *stmt = db_prepare_stmt(get_db(), random_number_query, "i", "ii");
224+
struct db_stmt *stmt =
225+
db_prepare_stmt_ctx(get_db(), request, random_number_query, "i", "ii");
223226
struct db_json db_json;
224227

225228
if (UNLIKELY(!stmt)) {
@@ -237,7 +240,7 @@ LWAN_HANDLER(db)
237240
request->flags |= RESPONSE_NO_EXPIRES;
238241

239242
return json_response_obj(response, db_json_desc, N_ELEMENTS(db_json_desc),
240-
&db_json);
243+
&db_json);
241244
}
242245

243246
static long get_number_of_queries(struct lwan_request *request)
@@ -253,7 +256,8 @@ LWAN_HANDLER(queries)
253256
enum lwan_http_status ret = HTTP_INTERNAL_ERROR;
254257
long queries = get_number_of_queries(request);
255258

256-
struct db_stmt *stmt = db_prepare_stmt(get_db(), random_number_query, "i", "ii");
259+
struct db_stmt *stmt =
260+
db_prepare_stmt_ctx(get_db(), request, random_number_query, "i", "ii");
257261
if (UNLIKELY(!stmt))
258262
return HTTP_INTERNAL_ERROR;
259263

@@ -283,10 +287,8 @@ struct db_json_cached {
283287
struct db_json db_json;
284288
};
285289

286-
static struct cache_entry *cached_queries_new(const void *keyptr,
287-
void *context,
288-
void *create_ctx
289-
__attribute__((unused)))
290+
static struct cache_entry *
291+
cached_queries_new(const void *keyptr, void *context, void *create_ctx)
290292
{
291293
struct db_json_cached *entry;
292294
struct db_stmt *stmt;
@@ -296,7 +298,8 @@ static struct cache_entry *cached_queries_new(const void *keyptr,
296298
if (UNLIKELY(!entry))
297299
return NULL;
298300

299-
stmt = db_prepare_stmt(get_db(), cached_random_number_query, "i", "ii");
301+
stmt = db_prepare_stmt_ctx(get_db(), create_ctx, cached_random_number_query,
302+
"i", "ii");
300303
if (UNLIKELY(!stmt)) {
301304
free(entry);
302305
return NULL;
@@ -327,8 +330,8 @@ LWAN_HANDLER(cached_queries)
327330
int key = (int)lwan_random_uint64() % 10000;
328331
int error;
329332

330-
jc = (struct db_json_cached *)cache_get_and_ref_entry(
331-
cached_queries_cache, (void *)(intptr_t)key, &error);
333+
jc = (struct db_json_cached *)cache_get_and_ref_entry_with_ctx(
334+
cached_queries_cache, (void *)(intptr_t)key, request, &error);
332335

333336
qj.queries[i] = jc->db_json;
334337

@@ -392,7 +395,8 @@ static int fortune_list_generator(struct coro *coro, void *data)
392395
struct fortune_array fortunes;
393396
struct db_stmt *stmt;
394397

395-
stmt = db_prepare_stmt(get_db(), fortune_query, "", "is");
398+
stmt = db_prepare_stmt_ctx(get_db(), fortune->request, fortune_query, "",
399+
"is");
396400
if (UNLIKELY(!stmt))
397401
return 0;
398402

@@ -426,7 +430,7 @@ static int fortune_list_generator(struct coro *coro, void *data)
426430

427431
LWAN_HANDLER(fortunes)
428432
{
429-
struct Fortune fortune;
433+
struct Fortune fortune = {.request = request};
430434

431435
lwan_strbuf_grow_to(response->buffer, 1500);
432436

@@ -484,11 +488,9 @@ int main(void)
484488
if (!fortune_tpl)
485489
lwan_status_critical("Could not compile fortune templates");
486490

487-
cached_queries_cache = cache_create_full(cached_queries_new,
488-
cached_queries_free,
489-
hash_int_new,
490-
NULL,
491-
3600 /* 1 hour */);
491+
cached_queries_cache =
492+
cache_create_full(cached_queries_new, cached_queries_free, hash_int_new,
493+
NULL, 3600 /* 1 hour */);
492494
if (!cached_queries_cache)
493495
lwan_status_critical("Could not create cached queries cache");
494496
/* Pre-populate the cache and make it read-only to avoid locking in the fast

0 commit comments

Comments
 (0)
Please sign in to comment.