Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1m19BS-0001X9-5R for pgsql-hackers@arkaria.postgresql.org; Wed, 07 Jul 2021 15:09:54 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.92) (envelope-from ) id 1m19BR-0001NH-32 for pgsql-hackers@arkaria.postgresql.org; Wed, 07 Jul 2021 15:09:53 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1m19BQ-0001N9-KC for pgsql-hackers@lists.postgresql.org; Wed, 07 Jul 2021 15:09:52 +0000 Received: from codesynthesis.com ([188.40.148.39]) by magus.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1m19BM-0004TJ-Hd for pgsql-hackers@lists.postgresql.org; Wed, 07 Jul 2021 15:09:52 +0000 Received: from brak.codesynthesis.com (unknown [105.187.34.11]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by codesynthesis.com (Postfix) with ESMTPSA id 2CF535F16D; Wed, 7 Jul 2021 15:09:46 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=codesynthesis.com; s=mail1; t=1625670586; bh=HlrjQySo4YJ7OPHF06CnrhYiERdpT5/9CMM31JKq2TA=; h=Date:From:To:Subject:Message-ID:MIME-Version:From; b=Kf9TSsiVESB6V6HcaWmLVFtm1a1wqDt/wWUUHqqrql3UxR2vcX3xI82dB2nHLw1K5 8+WPdnp++6hg05RjvecGwzP0EOTpbL2kRn4WjevXD1RhlpTL8FC3BPhSoCnVyW/Hp3 vB+tgV41W1NxYZ8uZf2T9leYlwwaPmaS/VUPbiyf4f5Kvav56DjGZkB+fnNdhWGGHo 19yRwzjmJjfJFz22lAby0AUTxIkblbgDvPkfRMqBYr7A1SwiOCl9XJ+bucr9Usi6rA iQGOEoOmy/6zFHLDqDIlFWs6zqkniG69NDpel5ir+cw0sxOy6T48ZFLqj0fn5bSARW KiQeiVCXPf+lA== Received: by brak.codesynthesis.com (Postfix, from userid 1000) id 75AB81A800C4; Wed, 7 Jul 2021 17:09:41 +0200 (SAST) Date: Wed, 7 Jul 2021 17:09:41 +0200 From: Boris Kolpackov To: Alvaro Herrera Cc: pgsql-hackers@lists.postgresql.org Subject: Re: Pipeline mode and PQpipelineSync() Message-ID: References: <202107071104.acus7s4x3vv3@alvherre.pgsql> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: <202107071104.acus7s4x3vv3@alvherre.pgsql> Organization: Code Synthesis User-Agent: Mutt/1.5.24 (2015-08-30) List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk Alvaro Herrera writes: > On 2021-Jul-07, Boris Kolpackov wrote: > > > I don't get any difference in behavior with this patch. That is, I > > still get the unexpected NULL result. Does it make a difference for > > your reproducer? > > Yes, the behavior changes for my repro. Is it possible for you to > share a full program I can compile and run, plesse? Here is the test sans the connection setup: ----------------------------------------------------------------------- #include #include #include #include #include #include #include // Note: hack. // #include #define htonll(x) ((((long long)htonl(x)) << 32) + htonl((x) >> 32)) static const size_t columns = 3; struct data { long long id; long long idata; const char* sdata; }; static char* values[columns]; static int lengths[columns]; static int formats[columns] = {1, 1, 1}; static const unsigned int types[columns] = { 20, // int8 20, // int8 25 // text }; static void init (const struct data* d) { values[0] = (char*)&d->id; lengths[0] = sizeof (d->id); values[1] = (char*)&d->idata; lengths[1] = sizeof (d->idata); values[2] = (char*)d->sdata; lengths[2] = strlen (d->sdata); } static void execute (PGconn* conn, const struct data* ds, size_t n) { int sock = PQsocket (conn); assert (sock != -1); if (PQsetnonblocking (conn, 1) == -1 || PQenterPipelineMode (conn) == 0) assert (false); // True if we've written and read everything, respectively. // bool wdone = false; bool rdone = false; size_t wn = 0; size_t rn = 0; while (!rdone) { fd_set wds; if (!wdone) { FD_ZERO (&wds); FD_SET (sock, &wds); } fd_set rds; FD_ZERO (&rds); FD_SET (sock, &rds); if (select (sock + 1, &rds, wdone ? NULL : &wds, NULL, NULL) == -1) { if (errno == EINTR) continue; assert (false); } // Try to minimize the chance of blocking the server by first processing // the result and then sending more queries. // if (FD_ISSET (sock, &rds)) { if (PQconsumeInput (conn) == 0) assert (false); while (PQisBusy (conn) == 0) { //fprintf (stderr, "PQgetResult %zu\n", rn); PGresult* res = PQgetResult (conn); assert (res != NULL); ExecStatusType stat = PQresultStatus (res); if (stat == PGRES_PIPELINE_SYNC) { assert (wdone && rn == n); PQclear (res); rdone = true; break; } if (stat == PGRES_FATAL_ERROR) { const char* s = PQresultErrorField (res, PG_DIAG_SQLSTATE); if (strcmp (s, "23505") == 0) fprintf (stderr, "duplicate id at %zu\n", rn); } PQclear (res); assert (rn != n); ++rn; // We get a NULL result after each query result. // { PGresult* end = PQgetResult (conn); assert (end == NULL); } } } if (!wdone && FD_ISSET (sock, &wds)) { // Send queries until we get blocked (write-biased). This feels like // a better overall strategy to keep the server busy compared to // sending one query at a time and then re-checking if there is // anything to read because the results of INSERT/UPDATE/DELETE are // presumably small and quite a few of them can get buffered before // the server gets blocked. // for (;;) { if (wn != n) { //fprintf (stderr, "PQsendQueryPrepared %zu\n", wn); init (ds + wn); if (PQsendQueryPrepared (conn, "persist_object", (int)(columns), values, lengths, formats, 1) == 0) assert (false); if (++wn == n) { if (PQpipelineSync (conn) == 0) assert (false); } } // PQflush() result: // // 0 -- success (queue is now empty) // 1 -- blocked // -1 -- error // int r = PQflush (conn); assert (r != -1); if (r == 0) { if (wn != n) { // If we continue here, then we are write-biased. And if we // break, then we are read-biased. // #if 1 break; #else continue; #endif } wdone = true; } break; // Blocked or done. } } } if (PQexitPipelineMode (conn) == 0 || PQsetnonblocking (conn, 0) == -1) assert (false); } static void test (PGconn* conn) { const size_t batch = 500; struct data ds[batch]; for (size_t i = 0; i != batch; ++i) { ds[i].id = htonll (i == batch / 2 ? i - 1 : i); // Cause duplicate PK. ds[i].idata = htonll (i); ds[i].sdata = "abc"; } // Prepare the statement. // { PGresult* res = PQprepare ( conn, "persist_object", "INSERT INTO \"pgsql_bulk_object\" " "(\"id\", " "\"idata\", " "\"sdata\") " "VALUES " "($1, $2, $3)", (int)(columns), types); assert (PQresultStatus (res) == PGRES_COMMAND_OK); PQclear (res); } // Begin transaction. // { PGresult* res = PQexec (conn, "begin"); assert (PQresultStatus (res) == PGRES_COMMAND_OK); PQclear (res); } execute (conn, ds, batch); // Commit transaction. // { PGresult* res = PQexec (conn, "commit"); assert (PQresultStatus (res) == PGRES_COMMAND_OK); PQclear (res); } } ----------------------------------------------------------------------- Use the following statements to (re)create the table: DROP TABLE IF EXISTS "pgsql_bulk_object" CASCADE; CREATE TABLE "pgsql_bulk_object" ( "id" BIGINT NOT NULL PRIMARY KEY, "idata" BIGINT NOT NULL, "sdata" TEXT NOT NULL); It fails consistently for me when running against the local PostgreSQL 9.5 server (connecting via the UNIX socket): duplicate id at 250 driver: driver.cxx:105: void execute(PGconn*, const data*, size_t): Assertion `res != NULL' failed.