Hi, hackers. I've been playing with "autoprepared" patch, and have got isolation "freeze-the-dead" test stuck on first VACUUM FREEZE statement. After some research I found issue is reproduced with unmodified master branch if extended protocol used. I've prepared ruby script for demonstration (cause ruby-pg has simple interface to PQsendQueryParams).
Further investigation showed it happens due to portal is not dropped inside of exec_execute_message, and it is kept in third session till COMMIT is called. Therefore heap page remains pinned, and VACUUM FREEZE became locked inside LockBufferForCleanup. It seems that it is usually invisible to common users since either: - command is called as standalone and then transaction is closed immediately, - next PQsendQueryParams will initiate another unnamed portal using CreatePortal("", true, true) and this action will drop previous one. But "freeze-the-dead" remains locked since third session could not send COMMIT until VACUUM FULL finished. I propose to add PortalDrop at the 'if (completed)' branch of exec_execute_message. --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c@@ -2209,6 +2209,8 @@ exec_execute_message(const char *portal_name, long max_rows)
if (completed) { + PortalDrop(portal, false); + if (is_xact_command) { With this change 'make check-world' runs without flaws (at least on empty configure with enable-cassert and enable-tap-tests). There is small chance applications exist which abuses seekable portals with 'execute' protocol message so not every completed portal can be safely dropped. But I believe there is some sane conditions that cover common case. For example, isn't empty name check is enough? Can client reset or seek portal with empty name? regards, Sokolov Yura aka funny_falcon
require 'pg' c1 = PG.connect(host: "localhost", dbname: "postgres") c2 = PG.connect(host: "localhost", dbname: "postgres") c3 = PG.connect(host: "localhost", dbname: "postgres") class PG::Connection def simple(sql) puts sql exec(sql) end def extended(sql) puts "#{sql}" exec_params(sql, []) end end c1.simple "DROP TABLE IF EXISTS tab_freeze" c1.simple " CREATE TABLE tab_freeze ( id int PRIMARY KEY, name char(3), x int); INSERT INTO tab_freeze VALUES (1, '111', 0); INSERT INTO tab_freeze VALUES (3, '333', 0); " c1.simple "BEGIN" c2.simple "BEGIN" c3.simple "BEGIN" c1.simple "UPDATE tab_freeze SET x = x + 1 WHERE id = 3" c2.extended "SELECT id FROM tab_freeze WHERE id = 3 FOR KEY SHARE" c3.extended "SELECT id FROM tab_freeze WHERE id = 3 FOR KEY SHARE" c1.simple "COMMIT" c2.simple "COMMIT" c2.simple "VACUUM FREEZE tab_freeze" c1.simple " BEGIN; SET LOCAL enable_seqscan = false; SET LOCAL enable_bitmapscan = false; SELECT * FROM tab_freeze WHERE id = 3; COMMIT; " c3.simple "COMMIT" c2.simple "VACUUM FREEZE tab_freeze" c1.simple "SELECT * FROM tab_freeze ORDER BY name, id"