Re: Proof of concept: standalone backend with full FE/BE protocol - Mailing list pgsql-hackers

From Tom Lane
Subject Re: Proof of concept: standalone backend with full FE/BE protocol
Date
Msg-id 24162.1347048615@sss.pgh.pa.us
Whole thread Raw
In response to Re: Proof of concept: standalone backend with full FE/BE protocol  (Heikki Linnakangas <hlinnaka@iki.fi>)
List pgsql-hackers
Heikki Linnakangas <hlinnaka@iki.fi> writes:
> On 07.09.2012 10:49, Tom Lane wrote:
>> I'm a bit tempted though to pull out and apply the portions of the
>> patch that replace libpq's assorted ad-hoc closesocket() calls with
>> a centralized pqDropConnection routine.  I think that's probably a good
>> idea independently of this feature.

> Sounds good.

Done; here's the rebased patch.

            regards, tom lane

diff --git a/src/backend/main/main.c b/src/backend/main/main.c
index 33c5a0a4e645624515016397da0011423d993c70..968959b85ef53aa2ec0ef8c5c5a9bc544291bfe5 100644
*** a/src/backend/main/main.c
--- b/src/backend/main/main.c
*************** main(int argc, char *argv[])
*** 191,196 ****
--- 191,198 ----
          AuxiliaryProcessMain(argc, argv);        /* does not return */
      else if (argc > 1 && strcmp(argv[1], "--describe-config") == 0)
          GucInfoMain();            /* does not return */
+     else if (argc > 1 && strncmp(argv[1], "--child=", 8) == 0)
+         ChildPostgresMain(argc, argv, get_current_username(progname)); /* does not return */
      else if (argc > 1 && strcmp(argv[1], "--single") == 0)
          PostgresMain(argc, argv, get_current_username(progname)); /* does not return */
      else
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 73520a6ca2f4d3300f3d8939c0e9412064a911c3..5a51fa9cb9abb39b0ac02d21a25e5940159582ea 100644
*** a/src/backend/postmaster/postmaster.c
--- b/src/backend/postmaster/postmaster.c
*************** ExitPostmaster(int status)
*** 4268,4273 ****
--- 4268,4350 ----
      proc_exit(status);
  }

+
+ /*
+  * ChildPostgresMain - start a new-style standalone postgres process
+  *
+  * This may not belong here, but it does share a lot of code with ConnCreate
+  * and BackendInitialize.  Basically what it has to do is set up a
+  * MyProcPort structure and then hand off control to PostgresMain.
+  * Beware that not very much stuff is initialized yet.
+  *
+  * In the future it might be interesting to support a "standalone
+  * multiprocess" mode in which we have a postmaster process that doesn't
+  * listen for connections, but does supervise autovacuum, bgwriter, etc
+  * auxiliary processes.  So that's another reason why postmaster.c might be
+  * the right place for this.
+  */
+ void
+ ChildPostgresMain(int argc, char *argv[], const char *username)
+ {
+     Port       *port;
+
+     /*
+      * Fire up essential subsystems: error and memory management
+      */
+     MemoryContextInit();
+
+     /*
+      * Build a Port structure for the client connection
+      */
+     if (!(port = (Port *) calloc(1, sizeof(Port))))
+         ereport(FATAL,
+                 (errcode(ERRCODE_OUT_OF_MEMORY),
+                  errmsg("out of memory")));
+
+     /*
+      * GSSAPI specific state struct must exist even though we won't use it
+      */
+ #if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
+     port->gss = (pg_gssinfo *) calloc(1, sizeof(pg_gssinfo));
+     if (!port->gss)
+         ereport(FATAL,
+                 (errcode(ERRCODE_OUT_OF_MEMORY),
+                  errmsg("out of memory")));
+ #endif
+
+     /* The file descriptor of the client socket is the argument of --child */
+     if (sscanf(argv[1], "--child=%d", &port->sock) != 1)
+         ereport(FATAL,
+                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                  errmsg("invalid argument for --child: \"%s\"", argv[1])));
+
+     /* Default assumption about protocol to use */
+     FrontendProtocol = port->proto = PG_PROTOCOL_LATEST;
+
+     /* save process start time */
+     port->SessionStartTime = GetCurrentTimestamp();
+     MyStartTime = timestamptz_to_time_t(port->SessionStartTime);
+
+     /* set these to empty in case they are needed */
+     port->remote_host = "";
+     port->remote_port = "";
+
+     MyProcPort = port;
+
+     /*
+      * We can now initialize libpq and then enable reporting of ereport errors
+      * to the client.
+      */
+     pq_init();                    /* initialize libpq to talk to client */
+     whereToSendOutput = DestRemote;    /* now safe to ereport to client */
+
+     /* And pass off control to PostgresMain */
+     PostgresMain(argc, argv, username);
+
+     abort();                    /* not reached */
+ }
+
+
  /*
   * sigusr1_handler - handle signal conditions from child processes
   */
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index f1248a851bf90188da8d3a7e8b61ac99bf78ebbd..c6a7de63c77f79c85be2170d4e5254b790f3fd5e 100644
*** a/src/backend/tcop/postgres.c
--- b/src/backend/tcop/postgres.c
*************** process_postgres_switches(int argc, char
*** 3257,3264 ****
      {
          gucsource = PGC_S_ARGV; /* switches came from command line */

!         /* Ignore the initial --single argument, if present */
!         if (argc > 1 && strcmp(argv[1], "--single") == 0)
          {
              argv++;
              argc--;
--- 3257,3266 ----
      {
          gucsource = PGC_S_ARGV; /* switches came from command line */

!         /* Ignore the initial --single or --child argument, if present */
!         if (argc > 1 &&
!             (strcmp(argv[1], "--single") == 0 ||
!              strncmp(argv[1], "--child=", 8) == 0))
          {
              argv++;
              argc--;
*************** PostgresMain(int argc, char *argv[], con
*** 3522,3539 ****
       * standalone).
       */
      if (!IsUnderPostmaster)
-     {
          MyProcPid = getpid();

          MyStartTime = time(NULL);
-     }

      /*
       * Fire up essential subsystems: error and memory management
       *
!      * If we are running under the postmaster, this is done already.
       */
!     if (!IsUnderPostmaster)
          MemoryContextInit();

      SetProcessingMode(InitProcessing);
--- 3524,3541 ----
       * standalone).
       */
      if (!IsUnderPostmaster)
          MyProcPid = getpid();

+     if (MyProcPort == NULL)
          MyStartTime = time(NULL);

      /*
       * Fire up essential subsystems: error and memory management
       *
!      * If we are running under the postmaster, this is done already; likewise
!      * in child-postgres mode.
       */
!     if (MyProcPort == NULL)
          MemoryContextInit();

      SetProcessingMode(InitProcessing);
diff --git a/src/include/postmaster/postmaster.h b/src/include/postmaster/postmaster.h
index 0fe7ec26db7646472fca248dee6982dc5c595c6c..6d4245cdc05ea5fea4baccf5dbc52dade7c00ff0 100644
*** a/src/include/postmaster/postmaster.h
--- b/src/include/postmaster/postmaster.h
*************** extern int    postmaster_alive_fds[2];
*** 47,52 ****
--- 47,55 ----
  extern const char *progname;

  extern void PostmasterMain(int argc, char *argv[]) __attribute__((noreturn));
+
+ extern void ChildPostgresMain(int argc, char *argv[], const char *username) __attribute__((noreturn));
+
  extern void ClosePostmasterPorts(bool am_syslogger);

  extern int    MaxLivePostmasterChildren(void);
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 9eaf41025beb652c6c242035490d93319a6bc5d0..c578a8ebbe6874825afbd0cb27cdfa2a3d01aec7 100644
*** a/src/interfaces/libpq/fe-connect.c
--- b/src/interfaces/libpq/fe-connect.c
***************
*** 17,22 ****
--- 17,23 ----

  #include <sys/types.h>
  #include <sys/stat.h>
+ #include <sys/wait.h>
  #include <fcntl.h>
  #include <ctype.h>
  #include <time.h>
*************** static const PQconninfoOption PQconninfo
*** 259,264 ****
--- 260,271 ----
      {"replication", NULL, NULL, NULL,
      "Replication", "D", 5},

+     {"standalone_datadir", NULL, NULL, NULL,
+     "Standalone-Data-Directory", "D", 64},
+
+     {"standalone_backend", NULL, NULL, NULL,
+     "Standalone-Backend", "D", 64},
+
      /* Terminating entry --- MUST BE LAST */
      {NULL, NULL, NULL, NULL,
      NULL, NULL, 0}
*************** pqDropConnection(PGconn *conn)
*** 360,365 ****
--- 367,383 ----
      if (conn->sock >= 0)
          closesocket(conn->sock);
      conn->sock = -1;
+     /* If we forked a child postgres process, wait for it to exit */
+     if (conn->postgres_pid > 0)
+     {
+         while (waitpid(conn->postgres_pid, NULL, 0) < 0)
+         {
+             /* If interrupted by signal, keep waiting */
+             if (errno != EINTR)
+                 break;
+         }
+     }
+     conn->postgres_pid = -1;
      /* Discard any unread/unsent data */
      conn->inStart = conn->inCursor = conn->inEnd = 0;
      conn->outCount = 0;
*************** fillPGconn(PGconn *conn, PQconninfoOptio
*** 643,648 ****
--- 661,670 ----
      conn->pghost = tmp ? strdup(tmp) : NULL;
      tmp = conninfo_getval(connOptions, "port");
      conn->pgport = tmp ? strdup(tmp) : NULL;
+     tmp = conninfo_getval(connOptions, "standalone_datadir");
+     conn->standalone_datadir = tmp ? strdup(tmp) : NULL;
+     tmp = conninfo_getval(connOptions, "standalone_backend");
+     conn->standalone_backend = tmp ? strdup(tmp) : NULL;
      tmp = conninfo_getval(connOptions, "tty");
      conn->pgtty = tmp ? strdup(tmp) : NULL;
      tmp = conninfo_getval(connOptions, "options");
*************** setKeepalivesWin32(PGconn *conn)
*** 1311,1316 ****
--- 1333,1384 ----
  #endif   /* SIO_KEEPALIVE_VALS */
  #endif   /* WIN32 */

+ /*
+  * Fork and exec a command, connecting up a pair of anonymous sockets for
+  * communication.  argv[fdarg] is modified by appending the child-side
+  * socket's file descriptor number.  The parent-side socket's FD is returned
+  * in *psock, and the function result is the child's PID.
+  */
+ static pid_t
+ fork_backend_child(char **argv, int fdarg, int *psock)
+ {
+     pid_t        pid;
+     int            socks[2];
+     char        newfdarg[32];
+
+     if (socketpair(AF_UNIX, SOCK_STREAM, 0, socks) < 0)
+         return -1;
+
+     pid = fork();
+
+     if (pid < 0)
+     {
+         /* fork failed */
+         close(socks[0]);
+         close(socks[1]);
+         return pid;
+     }
+     else if (pid == 0)
+     {
+         /* successful, in child process */
+         close(socks[1]);
+         snprintf(newfdarg, sizeof(newfdarg), "%s%d", argv[fdarg], socks[0]);
+         argv[fdarg] = newfdarg;
+         execv(argv[0], argv);
+         perror(argv[0]);
+         exit(1);
+     }
+     else
+     {
+         /* successful, in parent process */
+         close(socks[0]);
+         *psock = socks[1];
+     }
+
+     return pid;
+ }
+
+
  /* ----------
   * connectDBStart -
   *        Begin the process of making a connection to the backend.
*************** connectDBStart(PGconn *conn)
*** 1339,1344 ****
--- 1407,1461 ----
      conn->outCount = 0;

      /*
+      * If the standalone_datadir option was specified, ignore any host or
+      * port specifications and just try to fork a standalone backend.
+      */
+     if (conn->standalone_datadir && conn->standalone_datadir[0])
+     {
+         char   *be_argv[10];
+
+         /*
+          * We set up the backend's command line in execv(3) style, so that
+          * we don't need to cope with shell quoting rules.
+          */
+         if (conn->standalone_backend && conn->standalone_backend[0])
+             be_argv[0] = conn->standalone_backend;
+         else                    /* assume we should use hard-wired path */
+             be_argv[0] = PGBINDIR "/postgres";
+
+         be_argv[1] = "--child=";
+         be_argv[2] = "-D";
+         be_argv[3] = conn->standalone_datadir;
+         be_argv[4] = (conn->dbName && conn->dbName[0]) ? conn->dbName : NULL;
+         be_argv[5] = NULL;
+
+         conn->postgres_pid = fork_backend_child(be_argv, 1, &conn->sock);
+         if (conn->postgres_pid < 0)
+         {
+             char        sebuf[256];
+
+             appendPQExpBuffer(&conn->errorMessage,
+                               libpq_gettext("could not fork standalone backend: %s\n"),
+                               pqStrerror(errno, sebuf, sizeof(sebuf)));
+             goto connect_errReturn;
+         }
+
+         /*
+          * Go directly to CONNECTION_AUTH_OK state, since the standalone
+          * backend is not going to issue any authentication challenge to us.
+          * We're just waiting for startup to conclude.
+          */
+ #ifdef USE_SSL
+         conn->allow_ssl_try = false;
+ #endif
+         conn->pversion = PG_PROTOCOL(3, 0);
+         conn->status = CONNECTION_AUTH_OK;
+         conn->asyncStatus = PGASYNC_BUSY;
+
+         return 1;
+     }
+
+     /*
       * Determine the parameters to pass to pg_getaddrinfo_all.
       */

*************** makeEmptyPGconn(void)
*** 2708,2713 ****
--- 2825,2831 ----
      conn->auth_req_received = false;
      conn->password_needed = false;
      conn->dot_pgpass_used = false;
+     conn->postgres_pid = -1;
  #ifdef USE_SSL
      conn->allow_ssl_try = true;
      conn->wait_ssl_try = false;
*************** freePGconn(PGconn *conn)
*** 2784,2789 ****
--- 2902,2911 ----
          free(conn->pgport);
      if (conn->pgunixsocket)
          free(conn->pgunixsocket);
+     if (conn->standalone_datadir)
+         free(conn->standalone_datadir);
+     if (conn->standalone_backend)
+         free(conn->standalone_backend);
      if (conn->pgtty)
          free(conn->pgtty);
      if (conn->connect_timeout)
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 4a6c8fedf2b6b7f2f72d588c9c504dcc8d26cc0f..a9070ace73cead020b8b42f2720eebc771f39e6e 100644
*** a/src/interfaces/libpq/libpq-int.h
--- b/src/interfaces/libpq/libpq-int.h
*************** struct pg_conn
*** 303,308 ****
--- 303,310 ----
      char       *pgunixsocket;    /* the Unix-domain socket that the server is
                                   * listening on; if NULL, uses a default
                                   * constructed from pgport */
+     char       *standalone_datadir;    /* data directory for standalone backend */
+     char       *standalone_backend;    /* executable for standalone backend */
      char       *pgtty;            /* tty on which the backend messages is
                                   * displayed (OBSOLETE, NOT USED) */
      char       *connect_timeout;    /* connection timeout (numeric string) */
*************** struct pg_conn
*** 373,378 ****
--- 375,383 ----
      bool        sigpipe_so;        /* have we masked SIGPIPE via SO_NOSIGPIPE? */
      bool        sigpipe_flag;    /* can we mask SIGPIPE via MSG_NOSIGNAL? */

+     /* If we forked a child postgres process, its PID is kept here */
+     pid_t        postgres_pid;    /* PID, or -1 if none */
+
      /* Transient state needed while establishing connection */
      struct addrinfo *addrlist;    /* list of possible backend addresses */
      struct addrinfo *addr_cur;    /* the one currently being tried */

pgsql-hackers by date:

Previous
From: Tom Lane
Date:
Subject: Re: Proof of concept: standalone backend with full FE/BE protocol
Next
From: Merlin Moncure
Date:
Subject: Re: Proof of concept: standalone backend with full FE/BE protocol