When you lock a row with FOR KEY SHARE, and the row's non-key columns
have been updated, heap_lock_tuple() walks the update chain to mark all
the in-progress tuple versions also as locked. But it doesn't pay
attention to the NOWAIT or SKIP LOCKED flags when doing so. The
heap_lock_updated_tuple() function walks the update chain, but the
'wait_policy' argument is not passed to it. As a result, a SELECT in KEY
SHARE NOWAIT query can block waiting for another updating transaction,
despite the NOWAIT modifier.
This can be reproduced with the attached isolation test script.
I'm not sure how to fix this. The logic to walk the update chain and
propagate the tuple lock is already breathtakingly complicated :-(.
- Heikki
# Test NOWAIT with an updated tuple chain.
setup
{
CREATE TABLE foo (
id int PRIMARY KEY,
data text NOT NULL
);
INSERT INTO foo VALUES (1, 'x');
}
teardown
{
DROP TABLE foo;
}
session "s1"
setup { BEGIN; }
step "s1a" { SELECT * FROM foo WHERE pg_advisory_lock(0) IS NOT NULL FOR KEY SHARE NOWAIT; }
step "s1b" { COMMIT; }
session "s2"
step "s2a" { SELECT pg_advisory_lock(0); }
step "s2b" { BEGIN; }
step "s2c" { UPDATE foo SET data = 'y'; }
step "s2d" { UPDATE foo SET data = 'z', id = 2; }
step "s2e" { SELECT pg_advisory_unlock(0); }
step "s2f" { UPDATE foo SET data = 'z', id = 3; }
step "s2g" { COMMIT; }
# s1 takes a snapshot but then waits on an advisory lock. Then s2
# updates the row twice, first without changing the key column, and
# then updating the key column as well. Then s1 is allows to proceed
# to try to lock the row; because it has a snapshot that sees the older
# version, we reach the waiting code in EvalPlanQualFetch which ereports
# when in NOWAIT mode.
#
# XXX: That's what *should* happen. But instead, it blocks.
permutation "s2a" "s1a" "s2b" "s2c" "s2d" "s2e" "s2f" "s2g" "s1b"