On 24 July 2014 00:52, Thomas Munro <mu...@ip9.org> wrote: > On 23 July 2014 13:15, David Rowley <dgrowle...@gmail.com> wrote: >> I'm also wondering about this block of code in general: >> >> if (erm->waitPolicy == RWP_WAIT) >> wait_policy = LockWaitBlock; >> else if (erm->waitPolicy == RWP_SKIP ) >> wait_policy = LockWaitSkip; >> else /* erm->waitPolicy == RWP_NOWAIT */ >> wait_policy = LockWaitError; >> >> Just after this code heap_lock_tuple() is called, and if that returns >> HeapTupleWouldBlock, the code does a goto lnext, which then will repeat that >> whole block again. I'm wondering why there's 2 enums that are for the same >> thing? if you just had 1 then you'd not need this block of code at all, you >> could just pass erm->waitPolicy to heap_lock_tuple(). > > True. Somewhere upthread I mentioned the difficulty I had deciding > how many enumerations were needed, for the various subsystems, ie > which headers and type they were allowed to share. [...]
I tried getting rid of the offending if-then-else enum conversion code and replaced it with a simple assignment -- please see attached. I also added compile time assertions that the enum values line up to make that work, and are correctly ordered for use in that 'Max' expression. Please let me know if you think this is an improvement or an abomination. I couldn't find an existing reasonable place to share a single wait policy enumeration between parser/planner/executor and the heap access module, and I get the feeling that it would be unacceptable to introduce one. I suppose that the LockClauseWaitPolicy and RowWaitPolicy could at least be merged into a single enum defined in nodes.h following the example of CmdType, which is also used by both parsenodes.h and plannnode.h, but do I detect a tiny hint of reluctance in its comment, "so put it here..."? (The attached patch also has a couple of trivial typo fixes in documentation and comments). Best regards, Thomas Munro
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml index 231dc6a..0469705 100644 --- a/doc/src/sgml/ref/select.sgml +++ b/doc/src/sgml/ref/select.sgml @@ -45,7 +45,7 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="parameter">expression</replac [ LIMIT { <replaceable class="parameter">count</replaceable> | ALL } ] [ OFFSET <replaceable class="parameter">start</replaceable> [ ROW | ROWS ] ] [ FETCH { FIRST | NEXT } [ <replaceable class="parameter">count</replaceable> ] { ROW | ROWS } ONLY ] - [ FOR { UPDATE | NO KEY UPDATE | SHARE | KEY SHARE } [ OF <replaceable class="parameter">table_name</replaceable> [, ...] ] [ NOWAIT ] [...] ] + [ FOR { UPDATE | NO KEY UPDATE | SHARE | KEY SHARE } [ OF <replaceable class="parameter">table_name</replaceable> [, ...] ] [ NOWAIT | SKIP LOCKED ] [...] ] <phrase>where <replaceable class="parameter">from_item</replaceable> can be one of:</phrase> @@ -1283,7 +1283,7 @@ FETCH { FIRST | NEXT } [ <replaceable class="parameter">count</replaceable> ] { The locking clause has the general form <synopsis> -FOR <replaceable>lock_strength</> [ OF <replaceable class="parameter">table_name</replaceable> [, ...] ] [ NOWAIT ] +FOR <replaceable>lock_strength</> [ OF <replaceable class="parameter">table_name</replaceable> [, ...] ] [ NOWAIT | SKIP LOCKED ] </synopsis> where <replaceable>lock_strength</> can be one of @@ -1359,11 +1359,17 @@ KEY SHARE <para> To prevent the operation from waiting for other transactions to commit, - use the <literal>NOWAIT</> option. With <literal>NOWAIT</>, the statement - reports an error, rather than waiting, if a selected row - cannot be locked immediately. Note that <literal>NOWAIT</> applies only - to the row-level lock(s) — the required <literal>ROW SHARE</literal> - table-level lock is still taken in the ordinary way (see + use either the <literal>NOWAIT</> or <literal>SKIP LOCKED</literal> + option. With <literal>NOWAIT</>, the statement reports an error, rather + than waiting, if a selected row cannot be locked immediately. + With <literal>SKIP LOCKED</literal>, any selected rows that cannot be + immediately locked are skipped. Skipping locked rows provides an + inconsistent view of the data, so this is not suitable for general purpose + work, but can be used to avoid lock contention with multiple consumers + accessing a queue-like table. Note that <literal>NOWAIT</> + and <literal>SKIP LOCKED</literal> apply only to the row-level lock(s) + — the required <literal>ROW SHARE</literal> table-level lock is + still taken in the ordinary way (see <xref linkend="mvcc">). You can use <xref linkend="sql-lock"> with the <literal>NOWAIT</> option first, @@ -1386,14 +1392,14 @@ KEY SHARE </para> <para> - Multiple locking - clauses can be written if it is necessary to specify different locking - behavior for different tables. If the same table is mentioned (or - implicitly affected) by more than one locking clause, - then it is processed as if it was only specified by the strongest one. - Similarly, a table is processed - as <literal>NOWAIT</> if that is specified in any of the clauses - affecting it. + Multiple locking clauses can be written if it is necessary to specify + different locking behavior for different tables. If the same table is + mentioned (or implicitly affected) by more than one locking clause, then + it is processed as if it was only specified by the strongest one. + Similarly, a table is processed as <literal>NOWAIT</> if that is specified + in any of the clauses affecting it. Otherwise, it is processed + as <literal>SKIP LOCKED</literal> if that is specified in any of the + clauses affecting it. </para> <para> @@ -1930,9 +1936,9 @@ SELECT distributors.* WHERE distributors.name = 'Westward'; <productname>PostgreSQL</productname> allows it in any <command>SELECT</> query as well as in sub-<command>SELECT</>s, but this is an extension. The <literal>FOR NO KEY UPDATE</>, <literal>FOR SHARE</> and - <literal>FOR KEY SHARE</> variants, - as well as the <literal>NOWAIT</> option, - do not appear in the standard. + <literal>FOR KEY SHARE</> variants, as well as the <literal>NOWAIT</> + and <literal>SKIP LOCKED</literal> options, do not appear in the + standard. </para> </refsect2> diff --git a/doc/src/sgml/sql.sgml b/doc/src/sgml/sql.sgml index ba92607..57396d7 100644 --- a/doc/src/sgml/sql.sgml +++ b/doc/src/sgml/sql.sgml @@ -863,7 +863,7 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="PARAMETER">expression</replac [ ORDER BY <replaceable class="parameter">expression</replaceable> [ ASC | DESC | USING <replaceable class="parameter">operator</replaceable> ] [ NULLS { FIRST | LAST } ] [, ...] ] [ LIMIT { <replaceable class="PARAMETER">count</replaceable> | ALL } ] [ OFFSET <replaceable class="PARAMETER">start</replaceable> ] - [ FOR { UPDATE | SHARE } [ OF <replaceable class="parameter">table_name</replaceable> [, ...] ] [ NOWAIT ] [...] ] + [ FOR { UPDATE | SHARE } [ OF <replaceable class="parameter">table_name</replaceable> [, ...] ] [ NOWAIT | SKIP LOCKED ] [...] ] </synopsis> </para> diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 35f9404..4e8729c 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -4091,7 +4091,7 @@ get_mxact_status_for_lock(LockTupleMode mode, bool is_update) * cid: current command ID (used for visibility test, and stored into * tuple's cmax if lock is successful) * mode: indicates if shared or exclusive tuple lock is desired - * nowait: if true, ereport rather than blocking if lock not available + * wait_policy: whether to block, ereport or skip if lock not available * follow_updates: if true, follow the update chain to also lock descendant * tuples. * @@ -4104,6 +4104,7 @@ get_mxact_status_for_lock(LockTupleMode mode, bool is_update) * HeapTupleMayBeUpdated: lock was successfully acquired * HeapTupleSelfUpdated: lock failed because tuple updated by self * HeapTupleUpdated: lock failed because tuple updated by other xact + * HeapTupleWouldBlock: lock couldn't be acquired and wait_policy is skip * * In the failure cases, the routine fills *hufd with the tuple's t_ctid, * t_xmax (resolving a possible MultiXact, if necessary), and t_cmax @@ -4115,7 +4116,7 @@ get_mxact_status_for_lock(LockTupleMode mode, bool is_update) */ HTSU_Result heap_lock_tuple(Relation relation, HeapTuple tuple, - CommandId cid, LockTupleMode mode, bool nowait, + CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy, bool follow_updates, Buffer *buffer, HeapUpdateFailureData *hufd) { @@ -4218,7 +4219,11 @@ l3: */ if (!have_tuple_lock) { - if (nowait) + if (wait_policy == LockWaitBlock) + { + LockTupleTuplock(relation, tid, mode); + } + else if (wait_policy == LockWaitError) { if (!ConditionalLockTupleTuplock(relation, tid, mode)) ereport(ERROR, @@ -4226,8 +4231,11 @@ l3: errmsg("could not obtain lock on row in relation \"%s\"", RelationGetRelationName(relation)))); } - else - LockTupleTuplock(relation, tid, mode); + else /* wait_policy == LockWaitSkip */ + { + if (!ConditionalLockTupleTuplock(relation, tid, mode)) + return HeapTupleWouldBlock; + } have_tuple_lock = true; } @@ -4429,7 +4437,13 @@ l3: elog(ERROR, "invalid lock mode in heap_lock_tuple"); /* wait for multixact to end */ - if (nowait) + if (wait_policy == LockWaitBlock) + { + MultiXactIdWait((MultiXactId) xwait, status, infomask, + relation, &tuple->t_data->t_ctid, + XLTW_Lock, NULL); + } + else if (wait_policy == LockWaitError) { if (!ConditionalMultiXactIdWait((MultiXactId) xwait, status, infomask, relation, @@ -4440,10 +4454,18 @@ l3: errmsg("could not obtain lock on row in relation \"%s\"", RelationGetRelationName(relation)))); } - else - MultiXactIdWait((MultiXactId) xwait, status, infomask, - relation, &tuple->t_data->t_ctid, - XLTW_Lock, NULL); + else /* wait_policy == LockWaitSkip */ + { + if (!ConditionalMultiXactIdWait((MultiXactId) xwait, + status, infomask, relation, + &tuple->t_data->t_ctid, + XLTW_Lock, NULL)) + { + if (have_tuple_lock) + UnlockTupleTuplock(relation, tid, mode); + return HeapTupleWouldBlock; + } + } /* if there are updates, follow the update chain */ if (follow_updates && @@ -4489,7 +4511,12 @@ l3: else { /* wait for regular transaction to end */ - if (nowait) + if (wait_policy == LockWaitBlock) + { + XactLockTableWait(xwait, relation, &tuple->t_data->t_ctid, + XLTW_Lock); + } + else if (wait_policy == LockWaitError) { if (!ConditionalXactLockTableWait(xwait)) ereport(ERROR, @@ -4497,9 +4524,15 @@ l3: errmsg("could not obtain lock on row in relation \"%s\"", RelationGetRelationName(relation)))); } - else - XactLockTableWait(xwait, relation, &tuple->t_data->t_ctid, - XLTW_Lock); + else /* wait_policy == LockWaitSkip */ + { + if (!ConditionalXactLockTableWait(xwait)) + { + if (have_tuple_lock) + UnlockTupleTuplock(relation, tid, mode); + return HeapTupleWouldBlock; + } + } /* if there are updates, follow the update chain */ if (follow_updates && diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 9bf0098..f4c0ffa 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -2706,7 +2706,7 @@ ltrmark:; tuple.t_self = *tid; test = heap_lock_tuple(relation, &tuple, estate->es_output_cid, - lockmode, false /* wait */ , + lockmode, LockWaitBlock, false, &buffer, &hufd); switch (test) { diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 072c7df..5faad3f 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -830,7 +830,13 @@ InitPlan(QueryDesc *queryDesc, int eflags) erm->prti = rc->prti; erm->rowmarkId = rc->rowmarkId; erm->markType = rc->markType; - erm->noWait = rc->noWait; + StaticAssertExpr((int) LCWP_WAIT == (int) RWP_WAIT, + "LockClauseWaitPolicy/RowWaitPolicy mismatch"); + StaticAssertExpr((int) LCWP_SKIP == (int) RWP_SKIP, + "LockClauseWaitPolicy/RowWaitPolicy mismatch"); + StaticAssertExpr((int) LCWP_NOWAIT == (int) RWP_NOWAIT, + "LockClauseWaitPolicy/RowWaitPolicy mismatch"); + erm->waitPolicy = rc->waitPolicy; /* LockClauseWaitPolicy -> RowWaitPolicy */ ItemPointerSetInvalid(&(erm->curCtid)); estate->es_rowMarks = lappend(estate->es_rowMarks, erm); } @@ -2012,7 +2018,7 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode, */ test = heap_lock_tuple(relation, &tuple, estate->es_output_cid, - lockmode, false /* wait */ , + lockmode, LockWaitBlock, false, &buffer, &hufd); /* We now have two pins on the buffer, get rid of one */ ReleaseBuffer(buffer); diff --git a/src/backend/executor/nodeLockRows.c b/src/backend/executor/nodeLockRows.c index 298d4b4..03b023a 100644 --- a/src/backend/executor/nodeLockRows.c +++ b/src/backend/executor/nodeLockRows.c @@ -73,6 +73,7 @@ lnext: Buffer buffer; HeapUpdateFailureData hufd; LockTupleMode lockmode; + LockWaitPolicy wait_policy; HTSU_Result test; HeapTuple copyTuple; @@ -131,13 +132,25 @@ lnext: break; } + StaticAssertExpr((int) LockWaitBlock == (int) RWP_WAIT, + "LockWaitPolicy/RowWaitPolicy mismatch"); + StaticAssertExpr((int) LockWaitSkip == (int) RWP_SKIP, + "LockWaitPolicy/RowWaitPolicy mismatch"); + StaticAssertExpr((int) LockWaitError == (int) RWP_NOWAIT, + "LockWaitPolicy/RowWaitPolicy mismatch"); + wait_policy = erm->waitPolicy; /* RowWaitPolicy -> LockWaitPolicy */ + test = heap_lock_tuple(erm->relation, &tuple, estate->es_output_cid, - lockmode, erm->noWait, true, + lockmode, wait_policy, true, &buffer, &hufd); ReleaseBuffer(buffer); switch (test) { + case HeapTupleWouldBlock: + /* couldn't lock tuple in SKIP LOCKED mode */ + goto lnext; + case HeapTupleSelfUpdated: /* diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 3088578..e37d3bb 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -959,7 +959,7 @@ _copyPlanRowMark(const PlanRowMark *from) COPY_SCALAR_FIELD(prti); COPY_SCALAR_FIELD(rowmarkId); COPY_SCALAR_FIELD(markType); - COPY_SCALAR_FIELD(noWait); + COPY_SCALAR_FIELD(waitPolicy); COPY_SCALAR_FIELD(isParent); return newnode; @@ -2071,7 +2071,7 @@ _copyRowMarkClause(const RowMarkClause *from) COPY_SCALAR_FIELD(rti); COPY_SCALAR_FIELD(strength); - COPY_SCALAR_FIELD(noWait); + COPY_SCALAR_FIELD(waitPolicy); COPY_SCALAR_FIELD(pushedDown); return newnode; @@ -2452,7 +2452,7 @@ _copyLockingClause(const LockingClause *from) COPY_NODE_FIELD(lockedRels); COPY_SCALAR_FIELD(strength); - COPY_SCALAR_FIELD(noWait); + COPY_SCALAR_FIELD(waitPolicy); return newnode; } diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 1b07db6..5e915ba 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -2286,7 +2286,7 @@ _equalLockingClause(const LockingClause *a, const LockingClause *b) { COMPARE_NODE_FIELD(lockedRels); COMPARE_SCALAR_FIELD(strength); - COMPARE_SCALAR_FIELD(noWait); + COMPARE_SCALAR_FIELD(waitPolicy); return true; } @@ -2382,7 +2382,7 @@ _equalRowMarkClause(const RowMarkClause *a, const RowMarkClause *b) { COMPARE_SCALAR_FIELD(rti); COMPARE_SCALAR_FIELD(strength); - COMPARE_SCALAR_FIELD(noWait); + COMPARE_SCALAR_FIELD(waitPolicy); COMPARE_SCALAR_FIELD(pushedDown); return true; diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index e686a6c..4c7a66b 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -836,7 +836,7 @@ _outPlanRowMark(StringInfo str, const PlanRowMark *node) WRITE_UINT_FIELD(prti); WRITE_UINT_FIELD(rowmarkId); WRITE_ENUM_FIELD(markType, RowMarkType); - WRITE_BOOL_FIELD(noWait); + WRITE_BOOL_FIELD(waitPolicy); WRITE_BOOL_FIELD(isParent); } @@ -2136,7 +2136,7 @@ _outLockingClause(StringInfo str, const LockingClause *node) WRITE_NODE_FIELD(lockedRels); WRITE_ENUM_FIELD(strength, LockClauseStrength); - WRITE_BOOL_FIELD(noWait); + WRITE_ENUM_FIELD(waitPolicy, LockClauseWaitPolicy); } static void @@ -2326,7 +2326,7 @@ _outRowMarkClause(StringInfo str, const RowMarkClause *node) WRITE_UINT_FIELD(rti); WRITE_ENUM_FIELD(strength, LockClauseStrength); - WRITE_BOOL_FIELD(noWait); + WRITE_ENUM_FIELD(waitPolicy, LockClauseWaitPolicy); WRITE_BOOL_FIELD(pushedDown); } diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 69d9989..d0c7227 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -320,7 +320,7 @@ _readRowMarkClause(void) READ_UINT_FIELD(rti); READ_ENUM_FIELD(strength, LockClauseStrength); - READ_BOOL_FIELD(noWait); + READ_ENUM_FIELD(waitPolicy, LockClauseWaitPolicy); READ_BOOL_FIELD(pushedDown); READ_DONE(); diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index e1480cd..306fe10 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -2229,7 +2229,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->markType = ROW_MARK_KEYSHARE; break; } - newrc->noWait = rc->noWait; + newrc->waitPolicy = rc->waitPolicy; newrc->isParent = false; prowmarks = lappend(prowmarks, newrc); @@ -2257,7 +2257,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->markType = ROW_MARK_REFERENCE; else newrc->markType = ROW_MARK_COPY; - newrc->noWait = false; /* doesn't matter */ + newrc->waitPolicy = RWP_WAIT; /* doesn't matter */ newrc->isParent = false; prowmarks = lappend(prowmarks, newrc); diff --git a/src/backend/optimizer/prep/prepsecurity.c b/src/backend/optimizer/prep/prepsecurity.c index dd7f900..2c5c85d 100644 --- a/src/backend/optimizer/prep/prepsecurity.c +++ b/src/backend/optimizer/prep/prepsecurity.c @@ -233,19 +233,19 @@ expand_security_qual(PlannerInfo *root, List *tlist, int rt_index, { case ROW_MARK_EXCLUSIVE: applyLockingClause(subquery, 1, LCS_FORUPDATE, - rc->noWait, false); + rc->waitPolicy, false); break; case ROW_MARK_NOKEYEXCLUSIVE: applyLockingClause(subquery, 1, LCS_FORNOKEYUPDATE, - rc->noWait, false); + rc->waitPolicy, false); break; case ROW_MARK_SHARE: applyLockingClause(subquery, 1, LCS_FORSHARE, - rc->noWait, false); + rc->waitPolicy, false); break; case ROW_MARK_KEYSHARE: applyLockingClause(subquery, 1, LCS_FORKEYSHARE, - rc->noWait, false); + rc->waitPolicy, false); break; case ROW_MARK_REFERENCE: case ROW_MARK_COPY: diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 0410fdd..69756e4 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -1389,7 +1389,7 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) newrc->prti = rti; newrc->rowmarkId = oldrc->rowmarkId; newrc->markType = oldrc->markType; - newrc->noWait = oldrc->noWait; + newrc->waitPolicy = oldrc->waitPolicy; newrc->isParent = false; root->rowMarks = lappend(root->rowMarks, newrc); diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index fb6c44c..736a85d 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -2358,7 +2358,7 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc, allrels = makeNode(LockingClause); allrels->lockedRels = NIL; /* indicates all rels */ allrels->strength = lc->strength; - allrels->noWait = lc->noWait; + allrels->waitPolicy = lc->waitPolicy; if (lockedRels == NIL) { @@ -2373,12 +2373,12 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc, { case RTE_RELATION: applyLockingClause(qry, i, - lc->strength, lc->noWait, pushedDown); + lc->strength, lc->waitPolicy, pushedDown); rte->requiredPerms |= ACL_SELECT_FOR_UPDATE; break; case RTE_SUBQUERY: applyLockingClause(qry, i, - lc->strength, lc->noWait, pushedDown); + lc->strength, lc->waitPolicy, pushedDown); /* * FOR UPDATE/SHARE of subquery is propagated to all of @@ -2425,13 +2425,13 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc, { case RTE_RELATION: applyLockingClause(qry, i, - lc->strength, lc->noWait, + lc->strength, lc->waitPolicy, pushedDown); rte->requiredPerms |= ACL_SELECT_FOR_UPDATE; break; case RTE_SUBQUERY: applyLockingClause(qry, i, - lc->strength, lc->noWait, + lc->strength, lc->waitPolicy, pushedDown); /* see comment above */ transformLockingClause(pstate, rte->subquery, @@ -2499,7 +2499,7 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc, */ void applyLockingClause(Query *qry, Index rtindex, - LockClauseStrength strength, bool noWait, bool pushedDown) + LockClauseStrength strength, LockClauseWaitPolicy waitPolicy, bool pushedDown) { RowMarkClause *rc; @@ -2516,15 +2516,18 @@ applyLockingClause(Query *qry, Index rtindex, * a shared and exclusive lock at the same time; it'll end up being * exclusive anyway.) * - * We also consider that NOWAIT wins if it's specified both ways. This - * is a bit more debatable but raising an error doesn't seem helpful. - * (Consider for instance SELECT FOR UPDATE NOWAIT from a view that - * internally contains a plain FOR UPDATE spec.) + * We also consider that NOWAIT wins if it is specified multiple ways, + * otherwise SKIP LOCKED wins. This is a bit more debatable but + * raising an error doesn't seem helpful. (Consider for instance + * SELECT FOR UPDATE NOWAIT from a view that internally contains a + * plain FOR UPDATE spec.) * * And of course pushedDown becomes false if any clause is explicit. */ rc->strength = Max(rc->strength, strength); - rc->noWait |= noWait; + StaticAssertExpr(LCWP_NOWAIT > LCWP_SKIP, "LockClauseWaitPolicy order"); + StaticAssertExpr(LCWP_SKIP > LCWP_WAIT, "LockClauseWaitPolicy order"); + rc->waitPolicy = Max(rc->waitPolicy, waitPolicy); rc->pushedDown &= pushedDown; return; } @@ -2533,7 +2536,7 @@ applyLockingClause(Query *qry, Index rtindex, rc = makeNode(RowMarkClause); rc->rti = rtindex; rc->strength = strength; - rc->noWait = noWait; + rc->waitPolicy = waitPolicy; rc->pushedDown = pushedDown; qry->rowMarks = lappend(qry->rowMarks, rc); } diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index a113809..b54a692 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -284,6 +284,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type <boolean> opt_force opt_or_replace opt_grant_grant_option opt_grant_admin_option opt_nowait opt_if_exists opt_with_data +%type <ival> opt_nowait_or_skip %type <list> OptRoleList AlterOptRoleList %type <defelt> CreateOptRoleElem AlterOptRoleElem @@ -577,7 +578,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); LABEL LANGUAGE LARGE_P LAST_P LATERAL_P LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL - LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P + LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED MAPPING MATCH MATERIALIZED MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE @@ -601,7 +602,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SHARE - SHOW SIMILAR SIMPLE SMALLINT SNAPSHOT SOME STABLE STANDALONE_P START + SHOW SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME STABLE STANDALONE_P START STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSTRING SYMMETRIC SYSID SYSTEM_P @@ -9217,6 +9218,12 @@ opt_nowait: NOWAIT { $$ = TRUE; } | /*EMPTY*/ { $$ = FALSE; } ; +opt_nowait_or_skip: + NOWAIT { $$ = LCWP_NOWAIT; } + | SKIP LOCKED { $$ = LCWP_SKIP; } + | /*EMPTY*/ { $$ = LCWP_WAIT; } + ; + /***************************************************************************** * @@ -9858,12 +9865,12 @@ for_locking_items: ; for_locking_item: - for_locking_strength locked_rels_list opt_nowait + for_locking_strength locked_rels_list opt_nowait_or_skip { LockingClause *n = makeNode(LockingClause); n->lockedRels = $2; n->strength = $1; - n->noWait = $3; + n->waitPolicy = $3; $$ = (Node *) n; } ; @@ -12992,6 +12999,7 @@ unreserved_keyword: | LOCAL | LOCATION | LOCK_P + | LOCKED | MAPPING | MATCH | MATERIALIZED @@ -13074,6 +13082,7 @@ unreserved_keyword: | SHARE | SHOW | SIMPLE + | SKIP | SNAPSHOT | STABLE | STANDALONE_P diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index e6c5530..8d22afd 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -62,7 +62,7 @@ static void rewriteValuesRTE(RangeTblEntry *rte, Relation target_relation, static void rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte, Relation target_relation); static void markQueryForLocking(Query *qry, Node *jtnode, - LockClauseStrength strength, bool noWait, bool pushedDown); + LockClauseStrength strength, LockClauseWaitPolicy waitPolicy, bool pushedDown); static List *matchLocks(CmdType event, RuleLock *rulelocks, int varno, Query *parsetree); static Query *fireRIRrules(Query *parsetree, List *activeRIRs, @@ -1481,7 +1481,7 @@ ApplyRetrieveRule(Query *parsetree, */ if (rc != NULL) markQueryForLocking(rule_action, (Node *) rule_action->jointree, - rc->strength, rc->noWait, true); + rc->strength, rc->waitPolicy, true); return parsetree; } @@ -1499,7 +1499,7 @@ ApplyRetrieveRule(Query *parsetree, */ static void markQueryForLocking(Query *qry, Node *jtnode, - LockClauseStrength strength, bool noWait, bool pushedDown) + LockClauseStrength strength, LockClauseWaitPolicy waitPolicy, bool pushedDown) { if (jtnode == NULL) return; @@ -1510,15 +1510,15 @@ markQueryForLocking(Query *qry, Node *jtnode, if (rte->rtekind == RTE_RELATION) { - applyLockingClause(qry, rti, strength, noWait, pushedDown); + applyLockingClause(qry, rti, strength, waitPolicy, pushedDown); rte->requiredPerms |= ACL_SELECT_FOR_UPDATE; } else if (rte->rtekind == RTE_SUBQUERY) { - applyLockingClause(qry, rti, strength, noWait, pushedDown); + applyLockingClause(qry, rti, strength, waitPolicy, pushedDown); /* FOR UPDATE/SHARE of subquery is propagated to subquery's rels */ markQueryForLocking(rte->subquery, (Node *) rte->subquery->jointree, - strength, noWait, true); + strength, waitPolicy, true); } /* other RTE types are unaffected by FOR UPDATE */ } @@ -1528,14 +1528,14 @@ markQueryForLocking(Query *qry, Node *jtnode, ListCell *l; foreach(l, f->fromlist) - markQueryForLocking(qry, lfirst(l), strength, noWait, pushedDown); + markQueryForLocking(qry, lfirst(l), strength, waitPolicy, pushedDown); } else if (IsA(jtnode, JoinExpr)) { JoinExpr *j = (JoinExpr *) jtnode; - markQueryForLocking(qry, j->larg, strength, noWait, pushedDown); - markQueryForLocking(qry, j->rarg, strength, noWait, pushedDown); + markQueryForLocking(qry, j->larg, strength, waitPolicy, pushedDown); + markQueryForLocking(qry, j->rarg, strength, waitPolicy, pushedDown); } else elog(ERROR, "unrecognized node type: %d", diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 7237e5d..bdd129e 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -4446,8 +4446,10 @@ get_select_query_def(Query *query, deparse_context *context, appendStringInfo(buf, " OF %s", quote_identifier(get_rtable_name(rc->rti, context))); - if (rc->noWait) + if (rc->waitPolicy == LCWP_NOWAIT) appendStringInfoString(buf, " NOWAIT"); + else if (rc->waitPolicy == LCWP_SKIP) + appendStringInfoString(buf, " SKIP LOCKED"); } } diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index 493839f..96cbade 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -22,6 +22,12 @@ #include "utils/relcache.h" #include "utils/snapshot.h" +typedef enum +{ + LockWaitBlock = 0, + LockWaitSkip = 1, + LockWaitError = 2 +} LockWaitPolicy; /* "options" flag bits for heap_insert */ #define HEAP_INSERT_SKIP_WAL 0x0001 @@ -144,7 +150,7 @@ extern HTSU_Result heap_update(Relation relation, ItemPointer otid, CommandId cid, Snapshot crosscheck, bool wait, HeapUpdateFailureData *hufd, LockTupleMode *lockmode); extern HTSU_Result heap_lock_tuple(Relation relation, HeapTuple tuple, - CommandId cid, LockTupleMode mode, bool nowait, + CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy, bool follow_update, Buffer *buffer, HeapUpdateFailureData *hufd); extern void heap_inplace_update(Relation relation, HeapTuple tuple); diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index b271f21..40b7b77 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -429,7 +429,7 @@ typedef struct ExecRowMark Index prti; /* parent range table index, if child */ Index rowmarkId; /* unique identifier for resjunk columns */ RowMarkType markType; /* see enum in nodes/plannodes.h */ - bool noWait; /* NOWAIT option */ + RowWaitPolicy waitPolicy; /* see enum in nodes/plannodes.h */ ItemPointerData curCtid; /* ctid of currently locked tuple, if any */ } ExecRowMark; diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 8364bef..52904c2 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -625,12 +625,25 @@ typedef enum LockClauseStrength LCS_FORUPDATE } LockClauseStrength; +typedef enum LockClauseWaitPolicy +{ + /* + * Note: order is important (see applyLockingClause which takes the + * greatest value when several wait policies have been specified), and + * values must match RowWaitPolicy from plannodes.h and LockWaitPolicy + * from heapam.h. + */ + LCWP_WAIT = 0, + LCWP_SKIP = 1, + LCWP_NOWAIT = 2 +} LockClauseWaitPolicy; + typedef struct LockingClause { NodeTag type; List *lockedRels; /* FOR [KEY] UPDATE/SHARE relations */ LockClauseStrength strength; - bool noWait; /* NOWAIT option */ + LockClauseWaitPolicy waitPolicy; /* NOWAIT and SKIP LOCKED */ } LockingClause; /* @@ -975,7 +988,7 @@ typedef struct RowMarkClause NodeTag type; Index rti; /* range table index of target relation */ LockClauseStrength strength; - bool noWait; /* NOWAIT option */ + LockClauseWaitPolicy waitPolicy; bool pushedDown; /* pushed down from higher query level? */ } RowMarkClause; diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 3b9c683..fff9894 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -790,6 +790,14 @@ typedef enum RowMarkType #define RowMarkRequiresRowShareLock(marktype) ((marktype) <= ROW_MARK_KEYSHARE) +typedef enum RowWaitPolicy +{ + /* see also LockClauseWaitPolicy in parsenodes.h */ + RWP_WAIT = 0, + RWP_SKIP = 1, + RWP_NOWAIT = 2 +} RowWaitPolicy; + /* * PlanRowMark - * plan-time representation of FOR [KEY] UPDATE/SHARE clauses @@ -831,7 +839,7 @@ typedef struct PlanRowMark Index prti; /* range table index of parent relation */ Index rowmarkId; /* unique identifier for resjunk columns */ RowMarkType markType; /* see enum above */ - bool noWait; /* NOWAIT option */ + RowWaitPolicy waitPolicy; /* NOWAIT and SKIP LOCKED options */ bool isParent; /* true if this is a "dummy" parent entry */ } PlanRowMark; diff --git a/src/include/parser/analyze.h b/src/include/parser/analyze.h index 370a445..a6b8ed4 100644 --- a/src/include/parser/analyze.h +++ b/src/include/parser/analyze.h @@ -39,6 +39,6 @@ extern bool analyze_requires_snapshot(Node *parseTree); extern char *LCS_asString(LockClauseStrength strength); extern void CheckSelectLocking(Query *qry, LockClauseStrength strength); extern void applyLockingClause(Query *qry, Index rtindex, - LockClauseStrength strength, bool noWait, bool pushedDown); + LockClauseStrength strength, LockClauseWaitPolicy waitPolicy, bool pushedDown); #endif /* ANALYZE_H */ diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index b52e507..8d0bfb2 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -230,6 +230,7 @@ PG_KEYWORD("localtime", LOCALTIME, RESERVED_KEYWORD) PG_KEYWORD("localtimestamp", LOCALTIMESTAMP, RESERVED_KEYWORD) PG_KEYWORD("location", LOCATION, UNRESERVED_KEYWORD) PG_KEYWORD("lock", LOCK_P, UNRESERVED_KEYWORD) +PG_KEYWORD("locked", LOCKED, UNRESERVED_KEYWORD) PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD) PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD) PG_KEYWORD("materialized", MATERIALIZED, UNRESERVED_KEYWORD) @@ -343,6 +344,7 @@ PG_KEYWORD("share", SHARE, UNRESERVED_KEYWORD) PG_KEYWORD("show", SHOW, UNRESERVED_KEYWORD) PG_KEYWORD("similar", SIMILAR, TYPE_FUNC_NAME_KEYWORD) PG_KEYWORD("simple", SIMPLE, UNRESERVED_KEYWORD) +PG_KEYWORD("skip", SKIP, UNRESERVED_KEYWORD) PG_KEYWORD("smallint", SMALLINT, COL_NAME_KEYWORD) PG_KEYWORD("snapshot", SNAPSHOT, UNRESERVED_KEYWORD) PG_KEYWORD("some", SOME, RESERVED_KEYWORD) diff --git a/src/include/utils/snapshot.h b/src/include/utils/snapshot.h index d8e8b35..53e474f 100644 --- a/src/include/utils/snapshot.h +++ b/src/include/utils/snapshot.h @@ -104,7 +104,8 @@ typedef enum HeapTupleInvisible, HeapTupleSelfUpdated, HeapTupleUpdated, - HeapTupleBeingUpdated + HeapTupleBeingUpdated, + HeapTupleWouldBlock /* can be returned by heap_tuple_lock */ } HTSU_Result; #endif /* SNAPSHOT_H */ diff --git a/src/test/isolation/expected/nowait.out b/src/test/isolation/expected/nowait.out new file mode 100644 index 0000000..8713721 --- /dev/null +++ b/src/test/isolation/expected/nowait.out @@ -0,0 +1,329 @@ +Parsed test spec with 2 sessions + +starting permutation: s1a s1b s1c s2a s2b s2c +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s1c: COMMIT; +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s2c: COMMIT; + +starting permutation: s1a s1b s2a s1c s2b s2c +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +ERROR: could not obtain lock on row in relation "queue" +step s1c: COMMIT; +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +ERROR: current transaction is aborted, commands ignored until end of transaction block +step s2c: COMMIT; + +starting permutation: s1a s1b s2a s2b s1c s2c +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +ERROR: could not obtain lock on row in relation "queue" +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +ERROR: current transaction is aborted, commands ignored until end of transaction block +step s1c: COMMIT; +step s2c: COMMIT; + +starting permutation: s1a s1b s2a s2b s2c s1c +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +ERROR: could not obtain lock on row in relation "queue" +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +ERROR: current transaction is aborted, commands ignored until end of transaction block +step s2c: COMMIT; +step s1c: COMMIT; + +starting permutation: s1a s2a s1b s1c s2b s2c +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +ERROR: could not obtain lock on row in relation "queue" +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s1c: COMMIT; +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +ERROR: current transaction is aborted, commands ignored until end of transaction block +step s2c: COMMIT; + +starting permutation: s1a s2a s1b s2b s1c s2c +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +ERROR: could not obtain lock on row in relation "queue" +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +ERROR: current transaction is aborted, commands ignored until end of transaction block +step s1c: COMMIT; +step s2c: COMMIT; + +starting permutation: s1a s2a s1b s2b s2c s1c +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +ERROR: could not obtain lock on row in relation "queue" +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +ERROR: current transaction is aborted, commands ignored until end of transaction block +step s2c: COMMIT; +step s1c: COMMIT; + +starting permutation: s1a s2a s2b s1b s1c s2c +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +ERROR: could not obtain lock on row in relation "queue" +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +ERROR: current transaction is aborted, commands ignored until end of transaction block +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s1c: COMMIT; +step s2c: COMMIT; + +starting permutation: s1a s2a s2b s1b s2c s1c +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +ERROR: could not obtain lock on row in relation "queue" +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +ERROR: current transaction is aborted, commands ignored until end of transaction block +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s2c: COMMIT; +step s1c: COMMIT; + +starting permutation: s1a s2a s2b s2c s1b s1c +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +ERROR: could not obtain lock on row in relation "queue" +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +ERROR: current transaction is aborted, commands ignored until end of transaction block +step s2c: COMMIT; +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s1c: COMMIT; + +starting permutation: s2a s1a s1b s1c s2b s2c +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +ERROR: could not obtain lock on row in relation "queue" +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +ERROR: current transaction is aborted, commands ignored until end of transaction block +step s1c: COMMIT; +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s2c: COMMIT; + +starting permutation: s2a s1a s1b s2b s1c s2c +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +ERROR: could not obtain lock on row in relation "queue" +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +ERROR: current transaction is aborted, commands ignored until end of transaction block +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s1c: COMMIT; +step s2c: COMMIT; + +starting permutation: s2a s1a s1b s2b s2c s1c +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +ERROR: could not obtain lock on row in relation "queue" +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +ERROR: current transaction is aborted, commands ignored until end of transaction block +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s2c: COMMIT; +step s1c: COMMIT; + +starting permutation: s2a s1a s2b s1b s1c s2c +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +ERROR: could not obtain lock on row in relation "queue" +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +ERROR: current transaction is aborted, commands ignored until end of transaction block +step s1c: COMMIT; +step s2c: COMMIT; + +starting permutation: s2a s1a s2b s1b s2c s1c +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +ERROR: could not obtain lock on row in relation "queue" +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +ERROR: current transaction is aborted, commands ignored until end of transaction block +step s2c: COMMIT; +step s1c: COMMIT; + +starting permutation: s2a s1a s2b s2c s1b s1c +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +ERROR: could not obtain lock on row in relation "queue" +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s2c: COMMIT; +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +ERROR: current transaction is aborted, commands ignored until end of transaction block +step s1c: COMMIT; + +starting permutation: s2a s2b s1a s1b s1c s2c +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +ERROR: could not obtain lock on row in relation "queue" +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +ERROR: current transaction is aborted, commands ignored until end of transaction block +step s1c: COMMIT; +step s2c: COMMIT; + +starting permutation: s2a s2b s1a s1b s2c s1c +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +ERROR: could not obtain lock on row in relation "queue" +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +ERROR: current transaction is aborted, commands ignored until end of transaction block +step s2c: COMMIT; +step s1c: COMMIT; + +starting permutation: s2a s2b s1a s2c s1b s1c +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +ERROR: could not obtain lock on row in relation "queue" +step s2c: COMMIT; +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +ERROR: current transaction is aborted, commands ignored until end of transaction block +step s1c: COMMIT; + +starting permutation: s2a s2b s2c s1a s1b s1c +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s2c: COMMIT; +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; +id data status + +1 foo NEW +step s1c: COMMIT; diff --git a/src/test/isolation/expected/skip-locked-data.out b/src/test/isolation/expected/skip-locked-data.out new file mode 100644 index 0000000..f9b9cf2 --- /dev/null +++ b/src/test/isolation/expected/skip-locked-data.out @@ -0,0 +1,401 @@ +Parsed test spec with 2 sessions + +starting permutation: s1a s1b s1c s2a s2b s2c +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s1c: COMMIT; +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s2c: COMMIT; + +starting permutation: s1a s1b s2a s1c s2b s2c +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +2 bar NEW +step s1c: COMMIT; +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s2c: COMMIT; + +starting permutation: s1a s1b s2a s2b s1c s2c +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +2 bar NEW +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +2 bar NEW +step s1c: COMMIT; +step s2c: COMMIT; + +starting permutation: s1a s1b s2a s2b s2c s1c +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +2 bar NEW +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +2 bar NEW +step s2c: COMMIT; +step s1c: COMMIT; + +starting permutation: s1a s2a s1b s1c s2b s2c +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +2 bar NEW +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s1c: COMMIT; +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s2c: COMMIT; + +starting permutation: s1a s2a s1b s2b s1c s2c +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +2 bar NEW +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +2 bar NEW +step s1c: COMMIT; +step s2c: COMMIT; + +starting permutation: s1a s2a s1b s2b s2c s1c +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +2 bar NEW +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +2 bar NEW +step s2c: COMMIT; +step s1c: COMMIT; + +starting permutation: s1a s2a s2b s1b s1c s2c +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +2 bar NEW +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +2 bar NEW +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s1c: COMMIT; +step s2c: COMMIT; + +starting permutation: s1a s2a s2b s1b s2c s1c +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +2 bar NEW +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +2 bar NEW +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s2c: COMMIT; +step s1c: COMMIT; + +starting permutation: s1a s2a s2b s2c s1b s1c +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +2 bar NEW +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +2 bar NEW +step s2c: COMMIT; +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s1c: COMMIT; + +starting permutation: s2a s1a s1b s1c s2b s2c +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +2 bar NEW +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +2 bar NEW +step s1c: COMMIT; +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s2c: COMMIT; + +starting permutation: s2a s1a s1b s2b s1c s2c +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +2 bar NEW +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +2 bar NEW +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s1c: COMMIT; +step s2c: COMMIT; + +starting permutation: s2a s1a s1b s2b s2c s1c +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +2 bar NEW +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +2 bar NEW +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s2c: COMMIT; +step s1c: COMMIT; + +starting permutation: s2a s1a s2b s1b s1c s2c +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +2 bar NEW +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +2 bar NEW +step s1c: COMMIT; +step s2c: COMMIT; + +starting permutation: s2a s1a s2b s1b s2c s1c +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +2 bar NEW +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +2 bar NEW +step s2c: COMMIT; +step s1c: COMMIT; + +starting permutation: s2a s1a s2b s2c s1b s1c +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +2 bar NEW +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s2c: COMMIT; +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s1c: COMMIT; + +starting permutation: s2a s2b s1a s1b s1c s2c +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +2 bar NEW +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +2 bar NEW +step s1c: COMMIT; +step s2c: COMMIT; + +starting permutation: s2a s2b s1a s1b s2c s1c +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +2 bar NEW +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +2 bar NEW +step s2c: COMMIT; +step s1c: COMMIT; + +starting permutation: s2a s2b s1a s2c s1b s1c +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +2 bar NEW +step s2c: COMMIT; +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s1c: COMMIT; + +starting permutation: s2a s2b s2c s1a s1b s1c +step s2a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s2b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s2c: COMMIT; +step s1a: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s1b: SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; +id data status + +1 foo NEW +step s1c: COMMIT; diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule index 36acec1..7583067 100644 --- a/src/test/isolation/isolation_schedule +++ b/src/test/isolation/isolation_schedule @@ -25,3 +25,5 @@ test: propagate-lock-delete test: drop-index-concurrently-1 test: alter-table-1 test: timeouts +test: skip-locked-data +test: nowait diff --git a/src/test/isolation/specs/nowait.spec b/src/test/isolation/specs/nowait.spec new file mode 100644 index 0000000..83cedae --- /dev/null +++ b/src/test/isolation/specs/nowait.spec @@ -0,0 +1,26 @@ +setup +{ + CREATE TABLE queue ( + id int PRIMARY KEY, + data text NOT NULL, + status text NOT NULL + ); + INSERT INTO queue VALUES (1, 'foo', 'NEW'), (2, 'bar', 'NEW'); +} + +teardown +{ + DROP TABLE queue; +} + +session "s1" +setup { BEGIN; } +step "s1a" { SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; } +step "s1b" { SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; } +step "s1c" { COMMIT; } + +session "s2" +setup { BEGIN; } +step "s2a" { SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; } +step "s2b" { SELECT * FROM queue ORDER BY id FOR UPDATE NOWAIT LIMIT 1; } +step "s2c" { COMMIT; } diff --git a/src/test/isolation/specs/skip-locked-data.spec b/src/test/isolation/specs/skip-locked-data.spec new file mode 100644 index 0000000..2ce4739 --- /dev/null +++ b/src/test/isolation/specs/skip-locked-data.spec @@ -0,0 +1,26 @@ +setup +{ + CREATE TABLE queue ( + id int PRIMARY KEY, + data text NOT NULL, + status text NOT NULL + ); + INSERT INTO queue VALUES (1, 'foo', 'NEW'), (2, 'bar', 'NEW'); +} + +teardown +{ + DROP TABLE queue; +} + +session "s1" +setup { BEGIN; } +step "s1a" { SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; } +step "s1b" { SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; } +step "s1c" { COMMIT; } + +session "s2" +setup { BEGIN; } +step "s2a" { SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; } +step "s2b" { SELECT * FROM queue ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; } +step "s2c" { COMMIT; }
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers