changeset 2176:d2a98983f502

Start of support for surviving database-server restarts, for Postgres
author Adam Chlipala <adam@chlipala.net>
date Sat, 17 Oct 2015 10:49:25 -0400
parents 3ffef52d549c
children 00cf8214c2e3
files include/urweb/urweb_cpp.h src/c/urweb.c src/mysql.sml src/postgres.sml
diffstat 4 files changed, 217 insertions(+), 102 deletions(-) [+]
line wrap: on
line diff
--- a/include/urweb/urweb_cpp.h	Thu Oct 15 07:52:37 2015 -0400
+++ b/include/urweb/urweb_cpp.h	Sat Oct 17 10:49:25 2015 -0400
@@ -40,6 +40,7 @@
 uw_loggers* uw_get_loggers(struct uw_context *ctx);
 failure_kind uw_begin(struct uw_context *, char *path);
 void uw_ensure_transaction(struct uw_context *);
+int uw_try_reconnecting_if_at_most_one(struct uw_context *);
 failure_kind uw_begin_onError(struct uw_context *, char *msg);
 void uw_login(struct uw_context *);
 int uw_commit(struct uw_context *);
--- a/src/c/urweb.c	Thu Oct 15 07:52:37 2015 -0400
+++ b/src/c/urweb.c	Sat Oct 17 10:49:25 2015 -0400
@@ -797,10 +797,37 @@
   return r;
 }
 
+static void uw_try_reconnecting(uw_context ctx) {
+  // Hm, error starting transaction.
+  // Maybe the database server died but has since come back up.
+  // Let's try starting from scratch.
+  if (ctx->db) {
+    ctx->app->db_close(ctx);
+    ctx->db = NULL;
+  }
+  ctx->app->db_init(ctx);
+
+  if (!ctx->db)
+    uw_error(ctx, FATAL, "Error reopening database connection");
+}
+
+int uw_try_reconnecting_if_at_most_one(uw_context ctx) {
+  if (ctx->at_most_one_query) {
+    uw_try_reconnecting(ctx);
+    return 1;
+  } else
+    return 0;
+}
+
 void uw_ensure_transaction(uw_context ctx) {
   if (!ctx->transaction_started && !ctx->at_most_one_query) {
-    if (ctx->app->db_begin(ctx, ctx->could_write_db))
-      uw_error(ctx, BOUNDED_RETRY, "Error running SQL BEGIN");
+    if (!ctx->db || ctx->app->db_begin(ctx, ctx->could_write_db)) {
+      uw_try_reconnecting(ctx);
+
+      if (ctx->app->db_begin(ctx, ctx->could_write_db))
+        uw_error(ctx, FATAL, "Error running SQL BEGIN");
+    }
+
     ctx->transaction_started = 1;
   }
 }
--- a/src/mysql.sml	Thu Oct 15 07:52:37 2015 -0400
+++ b/src/mysql.sml	Sat Oct 17 10:49:25 2015 -0400
@@ -546,7 +546,7 @@
                   newline,
                   string "mysql_close(mysql);",
                   newline,
-                  string "uw_error(ctx, BOUNDED_RETRY, ",
+                  string "uw_error(ctx, FATAL, ",
                   string "\"Connection to MySQL server failed: %s\", msg);"],
              newline,
              string "}",
--- a/src/postgres.sml	Thu Oct 15 07:52:37 2015 -0400
+++ b/src/postgres.sml	Sat Oct 17 10:49:25 2015 -0400
@@ -1,4 +1,4 @@
-(* Copyright (c) 2008-2010, Adam Chlipala
+(* Copyright (c) 2008-2010, 2015, Adam Chlipala
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -520,7 +520,7 @@
               newline,
               string "PQfinish(conn);",
               newline,
-              string "uw_error(ctx, BOUNDED_RETRY, ",
+              string "uw_error(ctx, FATAL, ",
               string "\"Connection to Postgres server failed: %s\", msg);"],
          newline,
          string "}",
@@ -612,12 +612,24 @@
         getter t
     end
 
-fun queryCommon {loc, query, cols, doCols} =
+fun queryCommon {loc, query, cols, doCols, runit} =
     box [string "int n, i;",
          newline,
          newline,
 
-         string "if (res == NULL) uw_error(ctx, FATAL, \"Out of memory allocating query result.\");",
+         string "if (res == NULL) {",
+         box [newline,
+              string "if (uw_try_reconnecting_if_at_most_one(ctx)) {",
+              box [newline,
+                   string "conn = uw_get_db(ctx);",
+                   newline,
+                   runit,
+                   newline],
+              string "}",
+              newline,
+              string "if (res == NULL) uw_error(ctx, FATAL, \"Can't allocate query result; database server might be down.\");",
+              newline],
+         string "}",
          newline,
          newline,
 
@@ -687,12 +699,18 @@
          newline]
 
 fun query {loc, cols, doCols} =
-    box [string "PGconn *conn = uw_get_db(ctx);",
-         newline,
-         string "PGresult *res = PQexecParams(conn, query, 0, NULL, NULL, NULL, NULL, 0);",
-         newline,
-         newline,
-         queryCommon {loc = loc, cols = cols, doCols = doCols, query = string "query"}]
+    let
+        val runit = string "res = PQexecParams(conn, query, 0, NULL, NULL, NULL, NULL, 0);"
+    in
+        box [string "PGconn *conn = uw_get_db(ctx);",
+             newline,
+             string "PGresult *res;",
+             newline,
+             runit,
+             newline,
+             newline,
+             queryCommon {loc = loc, cols = cols, doCols = doCols, query = string "query", runit = runit}]
+    end
 
 fun p_ensql t e =
     case t of
@@ -756,33 +774,52 @@
          newline]
 
 fun queryPrepared {loc, id, query, inputs, cols, doCols, nested = _} =
-    box [string "PGconn *conn = uw_get_db(ctx);",
-         newline,
+    let
+        val runit =
+            box [string "res = ",
+                 if #persistent (Settings.currentProtocol ()) then
+                     box [string "PQexecPrepared(conn, \"uw",
+                          string (Int.toString id),
+                          string "\", ",
+                          string (Int.toString (length inputs)),
+                          string ", paramValues, paramLengths, paramFormats, 0);"]
+                 else
+                     box [string "PQexecParams(conn, \"",
+                          string (Prim.toCString query),
+                          string "\", ",
+                          string (Int.toString (length inputs)),
+                          string ", NULL, paramValues, paramLengths, paramFormats, 0);"]]
+    in
+        box [string "PGconn *conn = uw_get_db(ctx);",
+             newline,
+             
+             makeParams inputs,
+             
+             newline,
+             string "PGresult *res;",
+             runit,
+             newline,
+             newline,
+             queryCommon {loc = loc, cols = cols, doCols = doCols, query = box [string "\"",
+                                                                                string (Prim.toCString query),
+                                                                                string "\""],
+                          runit = runit}]
+    end
 
-         makeParams inputs,
-
-         newline,
-         string "PGresult *res = ",
-         if #persistent (Settings.currentProtocol ()) then
-             box [string "PQexecPrepared(conn, \"uw",
-                  string (Int.toString id),
-                  string "\", ",
-                  string (Int.toString (length inputs)),
-                  string ", paramValues, paramLengths, paramFormats, 0);"]
-         else
-             box [string "PQexecParams(conn, \"",
-                  string (Prim.toCString query),
-                  string "\", ",
-                  string (Int.toString (length inputs)),
-                  string ", NULL, paramValues, paramLengths, paramFormats, 0);"],
-         newline,
-         newline,
-         queryCommon {loc = loc, cols = cols, doCols = doCols, query = box [string "\"",
-                                                                            string (Prim.toCString query),
-                                                                            string "\""]}]
-
-fun dmlCommon {loc, dml, mode} =
-    box [string "if (res == NULL) uw_error(ctx, FATAL, \"Out of memory allocating DML result.\");",
+fun dmlCommon {loc, dml, mode, runit} =
+    box [string "if (res == NULL) {",
+         box [newline,
+              string "if (uw_try_reconnecting_if_at_most_one(ctx)) {",
+              box [newline,
+                   string "conn = uw_get_db(ctx);",
+                   newline,
+                   runit,
+                   newline],
+              string "}",
+              newline,
+              string "if (res == NULL) uw_error(ctx, FATAL, \"Can't allocate DML result; database server might be down.\");",
+              newline],
+         string "}",
          newline,
          newline,
 
@@ -818,7 +855,11 @@
 
                                         string "res = PQexec(conn, \"ROLLBACK TO s\");",
                                         newline,
-                                        string "if (res == NULL) uw_error(ctx, FATAL, \"Out of memory allocating DML result.\");",
+                                        string "if (res == NULL) {",
+                                        box [newline,
+                                             string "uw_error(ctx, FATAL, \"Can't allocate DML ROLLBACK result; database server might be down.\");",
+                                             newline],
+                                        string "}",
                                         newline,
                                         newline,
 
@@ -851,7 +892,7 @@
                               newline,
                               string "res = PQexec(conn, \"RELEASE s\");",
                               newline,
-                              string "if (res == NULL) uw_error(ctx, FATAL, \"Out of memory allocating DML result.\");",
+                              string "if (res == NULL) uw_error(ctx, FATAL, \"Out of memory allocating DML RELEASE result.\");",
                               newline,
                               newline,
 
@@ -877,7 +918,7 @@
         Error => box []
       | None => box [string "res = PQexec(conn, \"SAVEPOINT s\");",
                      newline,
-                     string "if (res == NULL) uw_error(ctx, FATAL, \"Out of memory allocating DML result.\");",
+                     string "if (res == NULL) uw_error(ctx, FATAL, \"Out of memory allocating DML SAVEPOINT result.\");",
                      newline,
                      newline,
                      string "if (PQresultStatus(res) != PGRES_COMMAND_OK) {",
@@ -893,52 +934,71 @@
                      newline]
 
 fun dml (loc, mode) =
-    box [string "PGconn *conn = uw_get_db(ctx);",
-         newline,
-         string "PGresult *res;",
-         newline,
+    let
+        val runit = string "res = PQexecParams(conn, dml, 0, NULL, NULL, NULL, NULL, 0);"
+    in
+        box [string "PGconn *conn = uw_get_db(ctx);",
+             newline,
+             string "PGresult *res;",
+             newline,
 
-         makeSavepoint mode,
+             makeSavepoint mode,
 
-         string "res = PQexecParams(conn, dml, 0, NULL, NULL, NULL, NULL, 0);",
-         newline,
-         newline,
-         dmlCommon {loc = loc, dml = string "dml", mode = mode}]
+         runit,
+             newline,
+             newline,
+             dmlCommon {loc = loc, dml = string "dml", mode = mode, runit = runit}]
+    end
 
 fun dmlPrepared {loc, id, dml, inputs, mode} =
-    box [string "PGconn *conn = uw_get_db(ctx);",
-         newline,
+    let
+        val runit =
+            box [string "res = ",
+                 if #persistent (Settings.currentProtocol ()) then
+                     box [string "PQexecPrepared(conn, \"uw",
+                          string (Int.toString id),
+                          string "\", ",
+                          string (Int.toString (length inputs)),
+                          string ", paramValues, paramLengths, paramFormats, 0);"]
+                 else
+                     box [string "PQexecParams(conn, \"",
+                          string (Prim.toCString dml),
+                          string "\", ",
+                          string (Int.toString (length inputs)),
+                          string ", NULL, paramValues, paramLengths, paramFormats, 0);"]]
+    in
+        box [string "PGconn *conn = uw_get_db(ctx);",
+             newline,
 
-         makeParams inputs,
+             makeParams inputs,
 
-         newline,
-         string "PGresult *res;",
-         newline,
-         newline,
+             newline,
+             string "PGresult *res;",
+             newline,
+             newline,
 
-         makeSavepoint mode,
+             makeSavepoint mode,
 
-         string "res = ",
-         if #persistent (Settings.currentProtocol ()) then
-             box [string "PQexecPrepared(conn, \"uw",
-                  string (Int.toString id),
-                  string "\", ",
-                  string (Int.toString (length inputs)),
-                  string ", paramValues, paramLengths, paramFormats, 0);"]
-         else
-             box [string "PQexecParams(conn, \"",
-                  string (Prim.toCString dml),
-                  string "\", ",
-                  string (Int.toString (length inputs)),
-                  string ", NULL, paramValues, paramLengths, paramFormats, 0);"],
-         newline,
-         newline,
-         dmlCommon {loc = loc, dml = box [string "\"",
-                                          string (Prim.toCString dml),
-                                          string "\""], mode = mode}]
+             runit,
+             newline,
+             newline,
+             dmlCommon {loc = loc, dml = box [string "\"",
+                                              string (Prim.toCString dml),
+                                              string "\""], mode = mode, runit = runit}]
+    end
 
-fun nextvalCommon {loc, query} =
-    box [string "if (res == NULL) uw_error(ctx, FATAL, \"Out of memory allocating nextval result.\");",
+fun nextvalCommon {loc, query, runit} =
+    box [string "if (res == NULL) {",
+         box [newline,
+              string "if (uw_try_reconnecting_if_at_most_one(ctx))",
+              newline,
+              string "conn = uw_get_db(ctx);",
+              newline,
+              runit,
+              newline,
+              string "uw_error(ctx, FATAL, \"Out of memory allocating nextval result.\");",
+              newline],
+         string "}",
          newline,
          newline,
 
@@ -987,6 +1047,8 @@
                       | _ => box [string "uw_Basis_strcat(ctx, \"SELECT NEXTVAL('\", uw_Basis_strcat(ctx, ",
                                   seqE,
                                   string ", \"')\"))"]
+
+        val runit = string "res = PQexecParams(conn, query, 0, NULL, NULL, NULL, NULL, 0);"
     in
         box [string "char *query = ",
              query,
@@ -994,33 +1056,53 @@
              newline,
              string "PGconn *conn = uw_get_db(ctx);",
              newline,
-             string "PGresult *res = PQexecParams(conn, query, 0, NULL, NULL, NULL, NULL, 0);",
+             string "PGresult *res;",
              newline,
+             runit,
              newline,
-             nextvalCommon {loc = loc, query = string "query"}]
+             newline,
+             nextvalCommon {loc = loc, query = string "query", runit = runit}]
     end
 
 fun nextvalPrepared {loc, id, query} =
-    box [string "PGconn *conn = uw_get_db(ctx);",
-         newline,
-         newline,
-         string "PGresult *res = ",
-         if #persistent (Settings.currentProtocol ()) then
-             box [string "PQexecPrepared(conn, \"uw",
-                  string (Int.toString id),
-                  string "\", 0, NULL, NULL, NULL, 0);"]
-         else
-             box [string "PQexecParams(conn, \"",
-                  string (Prim.toCString query),
-                  string "\", 0, NULL, NULL, NULL, NULL, 0);"],
-         newline,
-         newline,
-         nextvalCommon {loc = loc, query = box [string "\"",
-                                                string (Prim.toCString query),
-                                                string "\""]}]
+    let
+        val runit =
+            box [string "res = ",
+                 if #persistent (Settings.currentProtocol ()) then
+                     box [string "PQexecPrepared(conn, \"uw",
+                          string (Int.toString id),
+                          string "\", 0, NULL, NULL, NULL, 0);"]
+                 else
+                     box [string "PQexecParams(conn, \"",
+                          string (Prim.toCString query),
+                          string "\", 0, NULL, NULL, NULL, NULL, 0);"]]
+    in
+        box [string "PGconn *conn = uw_get_db(ctx);",
+             newline,
+             newline,
 
-fun setvalCommon {loc, query} =
-    box [string "if (res == NULL) uw_error(ctx, FATAL, \"Out of memory allocating setval result.\");",
+             string "PGresult *res;",
+             newline,
+             runit,
+             newline,
+             newline,
+             nextvalCommon {loc = loc, query = box [string "\"",
+                                                    string (Prim.toCString query),
+                                                    string "\""], runit = runit}]
+    end
+
+fun setvalCommon {loc, query, runit} =
+    box [string "if (res == NULL) {",
+         box [newline,
+              string "if (uw_try_reconnecting_if_at_most_one(ctx))",
+              newline,
+              string "conn = uw_get_db(ctx);",
+              newline,
+              runit,
+              newline,
+              string "uw_error(ctx, FATAL, \"Out of memory allocating setval result.\");",
+              newline],
+         string "}",
          newline,
          newline,
 
@@ -1048,6 +1130,8 @@
                          string ", uw_Basis_strcat(ctx, \"', \", uw_Basis_strcat(ctx, uw_Basis_sqlifyInt(ctx, ",
                          count,
                          string "), \")\"))))"]
+
+        val runit = string "res = PQexecParams(conn, query, 0, NULL, NULL, NULL, NULL, 0);"
     in
         box [string "char *query = ",
              query,
@@ -1055,10 +1139,13 @@
              newline,
              string "PGconn *conn = uw_get_db(ctx);",
              newline,
-             string "PGresult *res = PQexecParams(conn, query, 0, NULL, NULL, NULL, NULL, 0);",
+
+             string "PGresult *res;",
              newline,
+             runit,
              newline,
-             setvalCommon {loc = loc, query = string "query"}]
+             newline,
+             setvalCommon {loc = loc, query = string "query", runit = runit}]
     end
 
 fun sqlifyString s = "E'" ^ String.translate (fn #"'" => "\\'"