[OCaml] High Intensity Training Online
fix store sessions in a signed cookie, not a second SQLite pool
The server opened two independent connection pools on the one SQLite file: the repository's, and Dream.sql_pool for Dream.sql_sessions. SQLite has a single writer lock, so two pools contending on the same file risk SQLITE_BUSY under load. Cookie sessions need no store. The session rides in a signed, encrypted cookie and survives a restart whenever HITO_SECRET is set, which the sql back end also required. The dream_session table stays in the schema so an existing file keeps its rows; it is otherwise unused.
Changed files
bin/main.ml
@@ -67,8 +67,12 @@
67
67
Lwt_main.run
68
68
(Seed.single_user repo ~password:seed_password ~csv_path:seed_csv);
69
69
let handlers = Handlers.make ~repo () in
70
Added:
(* Sessions live in a signed, encrypted cookie, not in SQLite. The
71
Added:
repository already holds the one connection pool for this file; a
72
Added:
second pool for sessions would contend with it for SQLite's single
73
Added:
writer lock. Cookie sessions need no store, and they survive a restart
74
Added:
whenever HITO_SECRET is set. *)
70
75
Dream.run ~interface:"localhost" ~port
71
76
~error_handler:Handlers.error_handler
72
Removed:
@@ Dream.logger @@ Dream.set_secret secret @@ Dream.sql_pool db_uri
73
Removed:
@@ Dream.sql_sessions
77
Added:
@@ Dream.logger @@ Dream.set_secret secret @@ Dream.cookie_sessions
74
78
@@ Dream.router (Handlers.routes handlers)
lib/persistence/migration_data.ml
@@ -51,7 +51,9 @@
51
51
)|};
52
52
{|CREATE INDEX IF NOT EXISTS workout_by_trainee
53
53
ON workout (trainee_id, seq DESC)|};
54
Removed:
(* The table Dream's sql_sessions back end expects. *)
54
Added:
(* Retained from when Dream's sql_sessions back end stored sessions
55
Added:
here. Sessions now live in a signed cookie. Kept so an existing
56
Added:
file keeps its rows; otherwise unused. *)
55
57
{|CREATE TABLE IF NOT EXISTS dream_session (
56
58
id TEXT PRIMARY KEY,
57
59
label TEXT NOT NULL,
lib/persistence/migrations.mli
@@ -6,7 +6,9 @@
6
6
Every statement still uses [IF NOT EXISTS], so a database created by an
7
7
earlier, ledger-less build stays safe.
8
8
9
Removed:
Includes the [dream_session] table {!Dream.sql_sessions} expects. *)
9
Added:
Includes a [dream_session] table from when sessions were stored in SQLite.
10
Added:
Sessions now live in a signed cookie; the table is retained so an existing
11
Added:
file keeps its rows, and is otherwise unused. *)
10
12
11
13
type migration = { version : int; name : string; statements : string list }
12
14
(** One migration: a version, a human name, and the ordered statements it runs.
lib/web/handlers.mli
@@ -17,8 +17,8 @@
17
17
18
18
val routes : t -> Dream.route list
19
19
(** The route table. Session and CSRF middleware are applied per handler. Wrap
20
Removed:
with {!Dream.sql_sessions} or {!Dream.memory_sessions} and a secret at the
21
Removed:
top level. *)
20
Added:
with a session middleware such as {!Dream.cookie_sessions} or
21
Added:
{!Dream.memory_sessions} and a secret at the top level. *)
22
22
23
23
val error_handler : Dream.error_handler
24
24
(** Renders a branded page for every failure Dream routes here, including