]> granicus.if.org Git - pgbouncer/commitdiff
WAIT_CLOSE command
authorPeter Eisentraut <peter@eisentraut.org>
Thu, 26 Jul 2018 10:03:45 +0000 (12:03 +0200)
committerPetr Jelinek <pjmodos@pjmodos.net>
Fri, 3 Aug 2018 14:02:59 +0000 (16:02 +0200)
doc/usage.rst
include/admin.h
include/bouncer.h
src/admin.c
src/janitor.c
test/test.sh

index 3e62d8ccdcde5ed88e29f58e84ae22c31219c616..6721b69ecbb84afd2ba3969b4844be4be2442b6c 100644 (file)
@@ -664,6 +664,15 @@ connection is next released (according to the pooling mode), and new
 server connections will immediately use the updated connection
 parameters.
 
+WAIT_CLOSE [<db>];
+------------------
+
+Wait until all server connections, either of the specified database or
+of all databases, have cleared the "close_needed" state (see **SHOW
+SERVERS**).  This can be called after a **RECONNECT** or **RELOAD** to
+wait until the respective configuration change has been fully
+activated, for example in switchover scripts.
+
 Other commands
 ~~~~~~~~~~~~~~
 
index 1d0ee908ef801e96a80042fcae503bbc20f7e7a3..b6747f430ababfac2429a5327ee375cb0c44d46b 100644 (file)
@@ -21,6 +21,7 @@ bool admin_post_login(PgSocket *client)  _MUSTCHECK;
 void admin_setup(void);
 bool admin_error(PgSocket *console, const char *fmt, ...)  _PRINTF(2, 3) /* _MUSTCHECK */;
 void admin_pause_done(void);
+void admin_wait_close_done(void);
 bool admin_flush(PgSocket *admin, PktBuf *buf, const char *desc) /* _MUSTCHECK */;
 bool admin_ready(PgSocket *admin, const char *desc)  _MUSTCHECK;
 void admin_handle_cancel(PgSocket *client);
index 327c470e1f1cea15953e155629623f5a0b37fac3..1fa6ab61b28a16e65a5dbd399f034df39c7c6980 100644 (file)
@@ -285,6 +285,7 @@ struct PgDatabase {
        char name[MAX_DBNAME];  /* db name for clients */
 
        bool db_paused;         /* PAUSE <db>; was issued */
+       bool db_wait_close;     /* WAIT_CLOSE was issued for this database */
        bool db_dead;           /* used on RELOAD/SIGHUP to later detect removed dbs */
        bool db_auto;           /* is the database auto-created by autodb_connstr */
        bool db_disabled;       /* is the database accepting new connections? */
index a86fb45a7a3da29a4a4a1af800b4c8ca3bb56861..735b79f972ef826c3dc1306d4cf7ec03a307a5d4 100644 (file)
@@ -1184,6 +1184,49 @@ static bool admin_cmd_kill(PgSocket *admin, const char *arg)
        return admin_ready(admin, "KILL");
 }
 
+/* Command: WAIT_CLOSE */
+static bool admin_cmd_wait_close(PgSocket *admin, const char *arg)
+{
+       if (!admin->admin_user)
+               return admin_error(admin, "admin access needed");
+
+       if (!arg[0]) {
+              struct List *item;
+              PgPool *pool;
+              int active = 0;
+
+              log_info("WAIT_CLOSE command issued");
+              statlist_for_each(item, &pool_list) {
+                      PgDatabase *db;
+
+                      pool = container_of(item, PgPool, head);
+                      db = pool->db;
+                      db->db_wait_close = 1;
+                      active += count_db_active(db);
+              }
+              if (active > 0)
+                      admin->wait_for_response = 1;
+              else
+                      return admin_ready(admin, "WAIT_CLOSE");
+       } else {
+              PgDatabase *db;
+
+              log_info("WAIT_CLOSE '%s' command issued", arg);
+              db = find_or_register_database(admin, arg);
+              if (db == NULL)
+                      return admin_error(admin, "no such database: %s", arg);
+              if (db == admin->pool->db)
+                      return admin_error(admin, "cannot wait in admin db: %s", arg);
+              db->db_wait_close = 1;
+              if (count_db_active(db) > 0)
+                      admin->wait_for_response = 1;
+              else
+                      return admin_ready(admin, "WAIT_CLOSE");
+       }
+
+       return true;
+}
+
 /* extract substring from regex group */
 static bool copy_arg(const char *src, regmatch_t *glist,
                     int gnum, char *dst, unsigned dstmax,
@@ -1245,7 +1288,8 @@ static bool admin_show_help(PgSocket *admin, const char *arg)
                "\tRECONNECT [<db>]\n"
                "\tKILL <db>\n"
                "\tSUSPEND\n"
-               "\tSHUTDOWN", "");
+               "\tSHUTDOWN\n",
+               "\tWAIT_CLOSE [<db>]", "");
        if (res)
                res = admin_ready(admin, "SHOW");
        return res;
@@ -1325,6 +1369,7 @@ static struct cmd_lookup cmd_list [] = {
        {"show", admin_cmd_show},
        {"shutdown", admin_cmd_shutdown},
        {"suspend", admin_cmd_suspend},
+       {"wait_close", admin_cmd_wait_close},
        {NULL, NULL}
 };
 
@@ -1589,6 +1634,26 @@ void admin_pause_done(void)
        }
 }
 
+void admin_wait_close_done(void)
+{
+       struct List *item, *tmp;
+       PgSocket *admin;
+       bool res;
+
+       statlist_for_each_safe(item, &admin_pool->active_client_list, tmp) {
+               admin = container_of(item, PgSocket, head);
+               if (!admin->wait_for_response)
+                       continue;
+
+               res = admin_ready(admin, "WAIT_CLOSE");
+
+               if (!res)
+                       disconnect_client(admin, false, "dead admin");
+               else
+                       admin->wait_for_response = 0;
+       }
+}
+
 /* admin on console has pressed ^C */
 void admin_handle_cancel(PgSocket *admin)
 {
index 31b68ef9db7edd7b31efad2cd2852868d631033a..464a658993e977c1753ff917aa6e4bd8e72f2173 100644 (file)
@@ -253,6 +253,43 @@ static int per_loop_suspend(PgPool *pool, bool force_suspend)
        return active;
 }
 
+/*
+ * Count the servers in server_list that have close_needed set.
+ */
+static int count_close_needed(struct StatList *server_list)
+{
+       struct List *item;
+       PgSocket *server;
+       int count = 0;
+
+       statlist_for_each(item, server_list) {
+               server = container_of(item, PgSocket, head);
+               if (server->close_needed)
+                       count++;
+       }
+
+       return count;
+}
+
+/*
+ * Per-loop tasks for WAIT_CLOSE
+ */
+static int per_loop_wait_close(PgPool *pool)
+{
+       int count = 0;
+
+       if (pool->db->admin)
+               return 0;
+
+       count += count_close_needed(&pool->active_server_list);
+       count += count_close_needed(&pool->idle_server_list);
+       count += count_close_needed(&pool->new_server_list);
+       count += count_close_needed(&pool->tested_server_list);
+       count += count_close_needed(&pool->used_server_list);
+
+       return count;
+}
+
 /*
  * this function is called for each event loop.
  */
@@ -261,7 +298,9 @@ void per_loop_maint(void)
        struct List *item;
        PgPool *pool;
        int active = 0;
+       int waiting_count = 0;
        int partial_pause = 0;
+       int partial_wait = 0;
        bool force_suspend = false;
 
        if (cf_pause_mode == P_SUSPEND && cf_suspend_timeout > 0) {
@@ -290,6 +329,11 @@ void per_loop_maint(void)
                        active += per_loop_suspend(pool, force_suspend);
                        break;
                }
+
+               if (pool->db->db_wait_close) {
+                       partial_wait = 1;
+                       waiting_count += per_loop_wait_close(pool);
+               }
        }
 
        switch (cf_pause_mode) {
@@ -309,6 +353,9 @@ void per_loop_maint(void)
                        admin_pause_done();
                break;
        }
+
+       if (partial_wait && !waiting_count)
+               admin_wait_close_done();
 }
 
 /* maintaining clients in pool */
index fe1e7604f2d9d02c6ef21f5823e919de7f7bd0b0..8e5537bbeca0c6ac14d830129bc0b48f3b8a5860 100755 (executable)
@@ -491,6 +491,33 @@ test_fast_close() {
        test `wc -l <$LOGDIR/testout.tmp` -eq 1 && test `wc -l <$LOGDIR/testerr.tmp` -ge 1
 }
 
+# test wait_close
+test_wait_close() {
+       (
+               echo "select pg_backend_pid();"
+               sleep 2
+               echo "select pg_backend_pid();"
+               echo "\q"
+       ) | psql -X -tAq -f- -d p3 &
+       psql_pid=$!
+       sleep 1
+       admin "reconnect p3"
+       admin "wait_close p3"
+
+       # psql should no longer be running now.  (Without the wait it
+       # would still be running.)
+       kill -0 $psql_pid
+       psql_running=$?
+
+       wait
+
+       admin "show databases"
+       admin "show pools"
+       admin "show servers"
+
+       test $psql_running -ne 0
+}
+
 # test auth_user
 test_auth_user() {
        admin "set auth_type='md5'"
@@ -532,6 +559,7 @@ test_database_restart
 test_database_change
 test_reconnect
 test_fast_close
+test_wait_close
 "
 
 if [ $# -gt 0 ]; then