0001-Multiplexing-Append-POC.patch
application/octet-stream
Filename: 0001-Multiplexing-Append-POC.patch
Type: application/octet-stream
Part: 0
Patch
Format: format-patch
Series: patch 0001
Subject: Multiplexing Append POC.
| File | + | − |
|---|---|---|
| contrib/postgres_fdw/connection.c | 7 | 1 |
| contrib/postgres_fdw/postgres_fdw.c | 119 | 13 |
| contrib/postgres_fdw/postgres_fdw.h | 11 | 1 |
| doc/src/sgml/monitoring.sgml | 5 | 1 |
| src/backend/executor/execProcnode.c | 25 | 0 |
| src/backend/executor/nodeAppend.c | 214 | 0 |
| src/backend/executor/nodeForeignscan.c | 19 | 0 |
| src/backend/postmaster/pgstat.c | 3 | 0 |
| src/include/executor/execReady.h | 31 | 0 |
| src/include/executor/executor.h | 1 | 0 |
| src/include/executor/nodeForeignscan.h | 1 | 0 |
| src/include/foreign/fdwapi.h | 5 | 0 |
| src/include/nodes/execnodes.h | 5 | 0 |
| src/include/pgstat.h | 1 | 0 |
From 7240714031e06df64da118f25237381e5250fc2a Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@gmail.com>
Date: Wed, 4 Sep 2019 17:49:39 +1200
Subject: [PATCH] Multiplexing Append POC.
---
contrib/postgres_fdw/connection.c | 8 +-
contrib/postgres_fdw/postgres_fdw.c | 132 +++++++++++++--
contrib/postgres_fdw/postgres_fdw.h | 12 +-
doc/src/sgml/monitoring.sgml | 6 +-
src/backend/executor/execProcnode.c | 25 +++
src/backend/executor/nodeAppend.c | 214 +++++++++++++++++++++++++
src/backend/executor/nodeForeignscan.c | 19 +++
src/backend/postmaster/pgstat.c | 3 +
src/include/executor/execReady.h | 31 ++++
src/include/executor/executor.h | 1 +
src/include/executor/nodeForeignscan.h | 1 +
src/include/foreign/fdwapi.h | 5 +
src/include/nodes/execnodes.h | 5 +
src/include/pgstat.h | 1 +
14 files changed, 447 insertions(+), 16 deletions(-)
create mode 100644 src/include/executor/execReady.h
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 57ed5f4b905..494b67126a2 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -58,6 +58,7 @@ typedef struct ConnCacheEntry
bool invalidated; /* true if reconnect is pending */
uint32 server_hashvalue; /* hash value of foreign server OID */
uint32 mapping_hashvalue; /* hash value of user mapping OID */
+ PgFdwConnState state; /* extra per-connection state */
} ConnCacheEntry;
/*
@@ -104,7 +105,7 @@ static bool pgfdw_get_cleanup_result(PGconn *conn, TimestampTz endtime,
* (not even on error), we need this flag to cue manual cleanup.
*/
PGconn *
-GetConnection(UserMapping *user, bool will_prep_stmt)
+GetConnection(UserMapping *user, bool will_prep_stmt, PgFdwConnState **state)
{
bool found;
ConnCacheEntry *entry;
@@ -196,6 +197,7 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
entry->mapping_hashvalue =
GetSysCacheHashValue1(USERMAPPINGOID,
ObjectIdGetDatum(user->umid));
+ memset(&entry->state, 0, sizeof(entry->state));
/* Now try to make the connection */
entry->conn = connect_pg_server(server, user);
@@ -212,6 +214,10 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
/* Remember if caller will prepare statements */
entry->have_prep_stmt |= will_prep_stmt;
+ /* If caller needs access to the per-connection state, return it. */
+ if (state)
+ *state = &entry->state;
+
return entry->conn;
}
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 82d8140ba25..0bc0a1f6960 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -21,6 +21,7 @@
#include "commands/defrem.h"
#include "commands/explain.h"
#include "commands/vacuum.h"
+#include "executor/execReady.h"
#include "foreign/fdwapi.h"
#include "funcapi.h"
#include "miscadmin.h"
@@ -158,6 +159,9 @@ typedef struct PgFdwScanState
MemoryContext temp_cxt; /* context for per-tuple temporary data */
int fetch_size; /* number of tuples per fetch */
+
+ /* per-connection state */
+ PgFdwConnState *conn_state;
} PgFdwScanState;
/*
@@ -391,6 +395,8 @@ static void postgresGetForeignUpperPaths(PlannerInfo *root,
RelOptInfo *output_rel,
void *extra);
+static int postgresReady(ForeignScanState *node);
+
/*
* Helper functions
*/
@@ -418,6 +424,7 @@ static bool ec_member_matches_foreign(PlannerInfo *root, RelOptInfo *rel,
EquivalenceClass *ec, EquivalenceMember *em,
void *arg);
static void create_cursor(ForeignScanState *node);
+static void fetch_more_data_begin(ForeignScanState *node);
static void fetch_more_data(ForeignScanState *node);
static void close_cursor(PGconn *conn, unsigned int cursor_number);
static PgFdwModifyState *create_foreign_modify(EState *estate,
@@ -557,6 +564,9 @@ postgres_fdw_handler(PG_FUNCTION_ARGS)
/* Support functions for upper relation push-down */
routine->GetForeignUpperPaths = postgresGetForeignUpperPaths;
+ /* Support for asynchrony */
+ routine->Ready = postgresReady;
+
PG_RETURN_POINTER(routine);
}
@@ -1446,7 +1456,7 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
* Get connection to the foreign server. Connection manager will
* establish new connection if necessary.
*/
- fsstate->conn = GetConnection(user, false);
+ fsstate->conn = GetConnection(user, false, &fsstate->conn_state);
/* Assign a unique ID for my cursor */
fsstate->cursor_number = GetCursorNumber(fsstate->conn);
@@ -1497,6 +1507,7 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
&fsstate->param_flinfo,
&fsstate->param_exprs,
&fsstate->param_values);
+ fsstate->conn_state->async_query_sent = false;
}
/*
@@ -1609,6 +1620,13 @@ postgresEndForeignScan(ForeignScanState *node)
if (fsstate == NULL)
return;
+ /*
+ * If we're ending before we've collected a response from an asynchronous
+ * query, we have to consume the response.
+ */
+ if (fsstate->conn_state->async_query_sent)
+ fetch_more_data(node);
+
/* Close the cursor if open, to prevent accumulation of cursors */
if (fsstate->cursor_exists)
close_cursor(fsstate->conn, fsstate->cursor_number);
@@ -2384,7 +2402,7 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags)
* Get connection to the foreign server. Connection manager will
* establish new connection if necessary.
*/
- dmstate->conn = GetConnection(user, false);
+ dmstate->conn = GetConnection(user, false, NULL);
/* Update the foreign-join-related fields. */
if (fsplan->scan.scanrelid == 0)
@@ -2684,7 +2702,7 @@ estimate_path_cost_size(PlannerInfo *root,
false, &retrieved_attrs, NULL);
/* Get the remote estimate */
- conn = GetConnection(fpinfo->user, false);
+ conn = GetConnection(fpinfo->user, false, NULL);
get_remote_estimate(sql.data, conn, &rows, &width,
&startup_cost, &total_cost);
ReleaseConnection(conn);
@@ -3351,13 +3369,28 @@ fetch_more_data(ForeignScanState *node)
int numrows;
int i;
- snprintf(sql, sizeof(sql), "FETCH %d FROM c%u",
- fsstate->fetch_size, fsstate->cursor_number);
+ if (!fsstate->conn_state->async_query_sent)
+ {
+ /* This is a regular synchronous fetch. */
+ snprintf(sql, sizeof(sql), "FETCH %d FROM c%u",
+ fsstate->fetch_size, fsstate->cursor_number);
- res = pgfdw_exec_query(conn, sql);
- /* On error, report the original query, not the FETCH. */
- if (PQresultStatus(res) != PGRES_TUPLES_OK)
- pgfdw_report_error(ERROR, res, conn, false, fsstate->query);
+ res = pgfdw_exec_query(conn, sql);
+ /* On error, report the original query, not the FETCH. */
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ pgfdw_report_error(ERROR, res, conn, false, fsstate->query);
+ }
+ else
+ {
+ /*
+ * The query was already sent by an earlier call to
+ * fetch_more_data_begin. So now we just fetch the result.
+ */
+ res = PQgetResult(conn);
+ /* On error, report the original query, not the FETCH. */
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ pgfdw_report_error(ERROR, res, conn, false, fsstate->query);
+ }
/* Convert the data into HeapTuples */
numrows = PQntuples(res);
@@ -3386,6 +3419,15 @@ fetch_more_data(ForeignScanState *node)
fsstate->eof_reached = (numrows < fsstate->fetch_size);
PQclear(res);
+
+ /* If this was the second part of an async request, we must fetch until NULL. */
+ if (fsstate->conn_state->async_query_sent)
+ {
+ /* call once and raise error if not NULL as expected? */
+ while (PQgetResult(conn) != NULL)
+ ;
+ fsstate->conn_state->async_query_sent = false;
+ }
res = NULL;
}
PG_CATCH();
@@ -3399,6 +3441,35 @@ fetch_more_data(ForeignScanState *node)
MemoryContextSwitchTo(oldcontext);
}
+/*
+ * Begin an asynchronous data fetch.
+ * fetch_more_data must be called to fetch the results..
+ */
+static void
+fetch_more_data_begin(ForeignScanState *node)
+{
+ PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state;
+ PGconn *conn = fsstate->conn;
+ char sql[64];
+
+ Assert(!fsstate->conn_state->async_query_sent);
+
+ /*
+ * Create the cursor synchronously. (With more state machine stuff we
+ * could do this asynchronously too).
+ */
+ if (!fsstate->cursor_exists)
+ create_cursor(node);
+
+ /* We will send this query, but not wait for the response. */
+ snprintf(sql, sizeof(sql), "FETCH %d FROM c%u",
+ fsstate->fetch_size, fsstate->cursor_number);
+
+ if (PQsendQuery(conn, sql) < 0)
+ pgfdw_report_error(ERROR, NULL, conn, false, fsstate->query);
+ fsstate->conn_state->async_query_sent = true;
+}
+
/*
* Force assorted GUC parameters to settings that ensure that we'll output
* data values in a form that is unambiguous to the remote server.
@@ -3512,7 +3583,7 @@ create_foreign_modify(EState *estate,
user = GetUserMapping(userid, table->serverid);
/* Open connection; report that we'll create a prepared statement. */
- fmstate->conn = GetConnection(user, true);
+ fmstate->conn = GetConnection(user, true, NULL);
fmstate->p_name = NULL; /* prepared statement not made yet */
/* Set up remote query information. */
@@ -4387,7 +4458,7 @@ postgresAnalyzeForeignTable(Relation relation,
*/
table = GetForeignTable(RelationGetRelid(relation));
user = GetUserMapping(relation->rd_rel->relowner, table->serverid);
- conn = GetConnection(user, false);
+ conn = GetConnection(user, false, NULL);
/*
* Construct command to get page count for relation.
@@ -4477,7 +4548,7 @@ postgresAcquireSampleRowsFunc(Relation relation, int elevel,
table = GetForeignTable(RelationGetRelid(relation));
server = GetForeignServer(table->serverid);
user = GetUserMapping(relation->rd_rel->relowner, table->serverid);
- conn = GetConnection(user, false);
+ conn = GetConnection(user, false, NULL);
/*
* Construct cursor that retrieves whole rows from remote.
@@ -4705,7 +4776,7 @@ postgresImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid)
*/
server = GetForeignServer(serverOid);
mapping = GetUserMapping(GetUserId(), server->serverid);
- conn = GetConnection(mapping, false);
+ conn = GetConnection(mapping, false, NULL);
/* Don't attempt to import collation if remote server hasn't got it */
if (PQserverVersion(conn) < 90100)
@@ -5514,6 +5585,41 @@ postgresGetForeignJoinPaths(PlannerInfo *root,
/* XXX Consider parameterized paths for the join relation */
}
+static int
+postgresReady(ForeignScanState *node)
+{
+ PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state;
+
+ if (fsstate->conn_state->async_query_sent)
+ {
+ /*
+ * We have already started a query, for some other executor node. We
+ * currently can't handle two at the same time (we'd have to create
+ * more connections for that).
+ */
+ return EXEC_READY_BUSY;
+ }
+ else if (fsstate->next_tuple < fsstate->num_tuples)
+ {
+ /* We already have buffered tuples. */
+ return EXEC_READY_MORE;
+ }
+ else if (fsstate->eof_reached)
+ {
+ /* We have already hit the end of the scan. */
+ return EXEC_READY_EOF;
+ }
+ else
+ {
+ /*
+ * We will start a query now, and tell the caller to wait until the
+ * file descriptor says we're ready and then call ExecProcNode.
+ */
+ fetch_more_data_begin(node);
+ return PQsocket(fsstate->conn);
+ }
+}
+
/*
* Assess whether the aggregation, grouping and having operations can be pushed
* down to the foreign server. As a side effect, save information we obtain in
diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h
index 6acb7dcf6cd..b4a25bbe105 100644
--- a/contrib/postgres_fdw/postgres_fdw.h
+++ b/contrib/postgres_fdw/postgres_fdw.h
@@ -20,6 +20,15 @@
#include "libpq-fe.h"
+/*
+ * Extra control information relating to a connection.
+ */
+typedef struct PgFdwConnState
+{
+ /* Has an asynchronous query been sent? */
+ bool async_query_sent;
+} PgFdwConnState;
+
/*
* FDW-specific planner information kept in RelOptInfo.fdw_private for a
* postgres_fdw foreign table. For a baserel, this struct is created by
@@ -127,7 +136,8 @@ extern int set_transmission_modes(void);
extern void reset_transmission_modes(int nestlevel);
/* in connection.c */
-extern PGconn *GetConnection(UserMapping *user, bool will_prep_stmt);
+extern PGconn *GetConnection(UserMapping *user, bool will_prep_stmt,
+ PgFdwConnState **state);
extern void ReleaseConnection(PGconn *conn);
extern unsigned int GetCursorNumber(PGconn *conn);
extern unsigned int GetPrepStmtNumber(PGconn *conn);
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bf72d0c3031..8e54d1957e4 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -1310,7 +1310,11 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser
<entry>Waiting in an extension.</entry>
</row>
<row>
- <entry morerows="36"><literal>IPC</literal></entry>
+ <entry morerows="37"><literal>IPC</literal></entry>
+ <entry><literal>AppendReady</literal></entry>
+ <entry>Waiting for a subplan of Append to be ready.</entry>
+ </row>
+ <row>
<entry><literal>BgWorkerShutdown</literal></entry>
<entry>Waiting for background worker to shut down.</entry>
</row>
diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c
index c227282975a..6f946d859f5 100644
--- a/src/backend/executor/execProcnode.c
+++ b/src/backend/executor/execProcnode.c
@@ -73,6 +73,7 @@
#include "postgres.h"
#include "executor/executor.h"
+#include "executor/execReady.h"
#include "executor/nodeAgg.h"
#include "executor/nodeAppend.h"
#include "executor/nodeBitmapAnd.h"
@@ -732,6 +733,30 @@ ExecEndNode(PlanState *node)
}
}
+/*
+ * ExecReady
+ *
+ * Check whether the node would be able to produce a new tuple without
+ * blocking. EXEC_READY_MORE means a tuple can be returned by ExecProcNode
+ * immediately without waiting. EXEC_READY_EOF means there are no further
+ * tuples to consume. EXEC_READY_UNSUPPORTED means that this node doesn't
+ * support asynchronous interaction. EXEC_READY_BUSY means that this node
+ * currently can't provide asynchronous service. Any other value is a file
+ * descriptor which can be used to wait until the node is ready to produce a
+ * tuple.
+ */
+int
+ExecReady(PlanState *node)
+{
+ switch (nodeTag(node))
+ {
+ case T_ForeignScanState:
+ return ExecForeignScanReady((ForeignScanState *) node);
+ default:
+ return EXEC_READY_UNSUPPORTED;
+ }
+}
+
/*
* ExecShutdownNode
*
diff --git a/src/backend/executor/nodeAppend.c b/src/backend/executor/nodeAppend.c
index 5ff986ac7d3..5b88f8e7e43 100644
--- a/src/backend/executor/nodeAppend.c
+++ b/src/backend/executor/nodeAppend.c
@@ -59,8 +59,11 @@
#include "executor/execdebug.h"
#include "executor/execPartition.h"
+#include "executor/execReady.h"
#include "executor/nodeAppend.h"
#include "miscadmin.h"
+#include "pgstat.h"
+#include "storage/latch.h"
/* Shared state for parallel-aware Append. */
struct ParallelAppendState
@@ -239,9 +242,207 @@ ExecInitAppend(Append *node, EState *estate, int eflags)
/* For parallel query, this will be overridden later. */
appendstate->choose_next_subplan = choose_next_subplan_locally;
+ /*
+ * Initially we consider all subplans to be potentially asynchronous.
+ */
+ appendstate->asyncplans = (PlanState **) palloc(nplans * sizeof(PlanState *));
+ appendstate->asyncfds = (int *) palloc0(nplans * sizeof(int));
+ appendstate->nasyncplans = nplans;
+ memcpy(appendstate->asyncplans, appendstate->appendplans, nplans * sizeof(PlanState *));
+ appendstate->lastreadyplan = 0;
+
return appendstate;
}
+/*
+ * Forget about an asynchronous subplan, given an async subplan index. Return
+ * the index of the next subplan.
+ */
+static int
+forget_async_subplan(AppendState *node, int i)
+{
+ int last = node->nasyncplans - 1;
+
+ if (i == last)
+ {
+ /* This was the last subplan, forget it and move to first. */
+ i = 0;
+ if (node->lastreadyplan == last)
+ node->lastreadyplan = 0;
+ }
+ else
+ {
+ /*
+ * Move the last one here (cheaper than memmov'ing the whole array
+ * down and we don't care about the order).
+ */
+ node->asyncplans[i] = node->asyncplans[last];
+ node->asyncfds[i] = node->asyncfds[last];
+ }
+ --node->nasyncplans;
+
+ return i;
+}
+
+/*
+ * Wait for the first asynchronous subplan's file descriptor to be ready to
+ * read or error, and then ask it for a tuple.
+ *
+ * This is called by append_next_async when every async subplan has provided a
+ * file descriptor to wait on, so we must begin waiting.
+ */
+static TupleTableSlot *
+append_next_async_wait(AppendState *node)
+{
+ while (node->nasyncplans > 0)
+ {
+ WaitEventSet *set;
+ WaitEvent event;
+ int i;
+
+ /*
+ * For now there is no facility to remove fds from WaitEventSets when
+ * they are no longer interesting, so we allocate, populate, free
+ * every time, a la select(). If we had RemoveWaitEventFromSet, we
+ * could use the same WaitEventSet object for the life of the append
+ * node, and add/remove as we go, a la epoll/kqueue.
+ *
+ * Note: We could make a single call to WaitEventSetWait and have a
+ * big enough output event buffer to learn about readiness on all
+ * interesting sockets and loop over those, but one implementation can
+ * only tell us about a single socket at a time, so we need to be
+ * prepared to call WaitEventSetWait repeatedly.
+ */
+ set = CreateWaitEventSet(CurrentMemoryContext, node->nasyncplans + 1);
+ AddWaitEventToSet(set, WL_EXIT_ON_PM_DEATH, PGINVALID_SOCKET, NULL,
+ NULL);
+ for (i = 0; i < node->nasyncplans; ++i)
+ {
+ Assert(node->asyncfds[i] > 0);
+ AddWaitEventToSet(set, WL_SOCKET_READABLE, node->asyncfds[i], NULL,
+ NULL);
+ }
+ i = WaitEventSetWait(set, -1, &event, 1, WAIT_EVENT_APPEND_READY);
+ Assert(i > 0);
+ FreeWaitEventSet(set);
+
+ if (event.events & WL_SOCKET_READABLE)
+ {
+ /* Linear search for the node that told us to wait for this fd. */
+ for (i = 0; i < node->nasyncplans; ++i)
+ {
+ if (event.fd == node->asyncfds[i])
+ {
+ TupleTableSlot *result;
+
+ /*
+ * We assume that because the fd is ready, it can produce
+ * a tuple now, which is not perfect. An improvement
+ * would be if it could say 'not yet, I'm still not
+ * ready', so eg postgres_fdw could PQconsumeInput and
+ * then say 'I need more input'.
+ */
+ result = ExecProcNode(node->asyncplans[i]);
+ if (!TupIsNull(result))
+ {
+ /*
+ * Remember this plan so that append_next_async will
+ * keep trying this subplan first until it stops
+ * feeding us buffered tuples.
+ */
+ node->lastreadyplan = i;
+ /* We can stop waiting for this fd. */
+ node->asyncfds[i] = 0;
+ return result;
+ }
+ else
+ {
+ /*
+ * This subplan has reached EOF. We'll go back and
+ * wait for another one.
+ */
+ forget_async_subplan(node, i);
+ break;
+ }
+ }
+ }
+ }
+ }
+ /*
+ * We visited every ready subplan, tried to pull a tuple, and they all
+ * reported EOF. There is no more async data available.
+ */
+ return NULL;
+}
+
+/*
+ * Fetch the next tuple available from any asynchronous subplan. If none can
+ * provide a tuple immediately, wait for the first one that is ready to
+ * provide a tuple. Return NULL when there are no more tuples available.
+ */
+static TupleTableSlot *
+append_next_async(AppendState *node)
+{
+ int count;
+ int i;
+
+ /*
+ * We'll start our scan of subplans at the last one that was able to give
+ * us a tuple, if there was one. It may be able to give us a new tuple
+ * straight away so we can leave early.
+ */
+ i = node->lastreadyplan;
+
+ /* Loop until we've visited each potentially async subplan. */
+ for (count = node->nasyncplans; count > 0; --count)
+ {
+ /*
+ * If we don't already have a file descriptor to wait on for this
+ * subplan, see if it is ready.
+ */
+ if (node->asyncfds[i] == 0)
+ {
+ int ready = ExecReady(node->asyncplans[i]);
+
+ switch (ready)
+ {
+ case EXEC_READY_MORE:
+ /* The node has a buffered tuple for us. */
+ return ExecProcNode(node->asyncplans[i]);
+
+ case EXEC_READY_UNSUPPORTED:
+ case EXEC_READY_EOF:
+ case EXEC_READY_BUSY:
+ /* This subplan can't give us anything asynchronously. */
+ i = forget_async_subplan(node, i);
+ continue;
+
+ default:
+ /* We have a new file descriptor to wait for. */
+ Assert(ready > 0);
+ node->asyncfds[i] = ready;
+ node->lastreadyplan = 0;
+ break;
+ }
+ }
+
+ /* Move on to the next plan (circular). */
+ i = (i + 1) % node->nasyncplans;
+ }
+
+ /* We might have removed all subplans; if so we can leave now. */
+ if (node->nasyncplans == 0)
+ return NULL;
+
+ /*
+ * If we reached here, then all remaining async subplans have given us a
+ * file descriptor to wait for. So do that, and pull a tuple as soon as
+ * one is ready.
+ */
+ return append_next_async_wait(node);
+}
+
+
/* ----------------------------------------------------------------
* ExecAppend
*
@@ -253,6 +454,16 @@ ExecAppend(PlanState *pstate)
{
AppendState *node = castNode(AppendState, pstate);
+ /* First, drain all asynchronous subplans as they become ready. */
+ if (node->nasyncplans > 0)
+ {
+ TupleTableSlot *result = append_next_async(node);
+
+ if (!TupIsNull(result))
+ return result;
+ }
+ Assert(node->nasyncplans == 0);
+
if (node->as_whichplan < 0)
{
/*
@@ -415,6 +626,9 @@ ExecAppendInitializeDSM(AppendState *node,
node->as_pstate = pstate;
node->choose_next_subplan = choose_next_subplan_for_leader;
+
+ /* TODO: for now disable async when running in parallel */
+ node->nasyncplans = 0;
}
/* ----------------------------------------------------------------
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 52af1dac5c4..95e7f66cb76 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -23,6 +23,7 @@
#include "postgres.h"
#include "executor/executor.h"
+#include "executor/execReady.h"
#include "executor/nodeForeignscan.h"
#include "foreign/fdwapi.h"
#include "utils/memutils.h"
@@ -384,3 +385,21 @@ ExecShutdownForeignScan(ForeignScanState *node)
if (fdwroutine->ShutdownForeignScan)
fdwroutine->ShutdownForeignScan(node);
}
+
+/* ----------------------------------------------------------------
+ * ExecForeignScanReady
+ *
+ * Checks if the foreign scan can emit data asynchronously
+ * using socket readiness as an indicator.
+ * ----------------------------------------------------------------
+ */
+int
+ExecForeignScanReady(ForeignScanState *node)
+{
+ FdwRoutine *fdwroutine = node->fdwroutine;
+
+ if (fdwroutine->Ready)
+ return fdwroutine->Ready(node);
+ else
+ return EXEC_READY_UNSUPPORTED;
+}
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index d362e7f7d7d..cae90b2f7d8 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -3744,6 +3744,9 @@ pgstat_get_wait_ipc(WaitEventIPC w)
switch (w)
{
+ case WAIT_EVENT_APPEND_READY:
+ event_name = "AppendReady";
+ break;
case WAIT_EVENT_BGWORKER_SHUTDOWN:
event_name = "BgWorkerShutdown";
break;
diff --git a/src/include/executor/execReady.h b/src/include/executor/execReady.h
new file mode 100644
index 00000000000..01410ea7bcb
--- /dev/null
+++ b/src/include/executor/execReady.h
@@ -0,0 +1,31 @@
+/*-------------------------------------------------------------------------
+ *
+ * execReady.h
+ * Values used by FDW and the executor for async tuple iteration.
+ *
+ * Portions Copyright (c) 2019, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/executor/execReady.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef EXECREADY_H
+#define EXECREADY_H
+
+/*
+ * Asynchronous processing is not currently available (because an asynchronous
+ * request is already in progress).
+ */
+#define EXEC_READY_BUSY -3
+
+/* There are no more tuples. */
+#define EXEC_READY_EOF -2
+
+/* This FDW or executor node does not support asynchronous processing. */
+#define EXEC_READY_UNSUPPORTED -1
+
+/* More tuples are available immediately without waiting. */
+#define EXEC_READY_MORE 0
+
+#endif
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index affe6ad6982..2d84c6d01b4 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -219,6 +219,7 @@ extern void EvalPlanQualEnd(EPQState *epqstate);
extern PlanState *ExecInitNode(Plan *node, EState *estate, int eflags);
extern void ExecSetExecProcNode(PlanState *node, ExecProcNodeMtd function);
extern Node *MultiExecProcNode(PlanState *node);
+extern int ExecReady(PlanState *node);
extern void ExecEndNode(PlanState *node);
extern bool ExecShutdownNode(PlanState *node);
extern void ExecSetTupleBound(int64 tuples_needed, PlanState *child_node);
diff --git a/src/include/executor/nodeForeignscan.h b/src/include/executor/nodeForeignscan.h
index ca7723c8997..32bc7215478 100644
--- a/src/include/executor/nodeForeignscan.h
+++ b/src/include/executor/nodeForeignscan.h
@@ -30,5 +30,6 @@ extern void ExecForeignScanReInitializeDSM(ForeignScanState *node,
extern void ExecForeignScanInitializeWorker(ForeignScanState *node,
ParallelWorkerContext *pwcxt);
extern void ExecShutdownForeignScan(ForeignScanState *node);
+extern int ExecForeignScanReady(ForeignScanState *node);
#endif /* NODEFOREIGNSCAN_H */
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
index 822686033e4..7a4c07478a9 100644
--- a/src/include/foreign/fdwapi.h
+++ b/src/include/foreign/fdwapi.h
@@ -170,6 +170,8 @@ typedef List *(*ReparameterizeForeignPathByChild_function) (PlannerInfo *root,
List *fdw_private,
RelOptInfo *child_rel);
+typedef int (*Ready_function) (ForeignScanState *node);
+
/*
* FdwRoutine is the struct returned by a foreign-data wrapper's handler
* function. It provides pointers to the callback functions needed by the
@@ -246,6 +248,9 @@ typedef struct FdwRoutine
/* Support functions for path reparameterization. */
ReparameterizeForeignPathByChild_function ReparameterizeForeignPathByChild;
+
+ /* Support functions for asynchronous processing */
+ Ready_function Ready;
} FdwRoutine;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index f42189d2bf6..da4aead8722 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1174,6 +1174,11 @@ struct AppendState
struct PartitionPruneState *as_prune_state;
Bitmapset *as_valid_subplans;
bool (*choose_next_subplan) (AppendState *);
+
+ PlanState **asyncplans;
+ int *asyncfds;
+ int nasyncplans;
+ int lastreadyplan;
};
/* ----------------
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index fe076d823db..298aebe3ddc 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -817,6 +817,7 @@ typedef enum
*/
typedef enum
{
+ WAIT_EVENT_APPEND_READY,
WAIT_EVENT_BGWORKER_SHUTDOWN = PG_WAIT_IPC,
WAIT_EVENT_BGWORKER_STARTUP,
WAIT_EVENT_BTREE_PAGE,
--
2.22.0