Author: Noah Misch Commit: Noah Misch Test datfrozenxid/relfrozenxid update race condition Not for commit, mostly because the injection point won't make sense after the fix; it would be obvious that the test is unlikely to detect similar future bugs. (A commit-ready test would also need to remove temporary WARNING messages added to clarify what's happening, and it would need to hide outputs sensitive to exact XID consumption.) diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index b589279..73f0245 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -53,9 +53,11 @@ #include "storage/proc.h" #include "storage/procarray.h" #include "utils/acl.h" +#include "utils/builtins.h" #include "utils/fmgroids.h" #include "utils/guc.h" #include "utils/guc_hooks.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/syscache.h" @@ -1620,6 +1622,8 @@ vac_update_datfrozenxid(void) while ((classTup = systable_getnext(scan)) != NULL) { Form_pg_class classForm = (Form_pg_class) GETSTRUCT(classTup); + TransactionId before, after; + before = classForm->relfrozenxid; /* * Only consider relations able to hold unfrozen XIDs (anything else @@ -1662,7 +1666,11 @@ vac_update_datfrozenxid(void) /* determine new horizon */ if (TransactionIdPrecedes(classForm->relfrozenxid, newFrozenXid)) + { + if (namestrcmp(&classForm->relname, "pg_test_last_table") == 0) + INJECTION_POINT("between-relfrozenxid-reads"); newFrozenXid = classForm->relfrozenxid; + } } if (MultiXactIdIsValid(classForm->relminmxid)) @@ -1678,6 +1686,12 @@ vac_update_datfrozenxid(void) if (MultiXactIdPrecedes(classForm->relminmxid, newMinMulti)) newMinMulti = classForm->relminmxid; } + + after = classForm->relfrozenxid; + elog(WARNING, "done reading %s before=%u after=%u myxid=%u myxmin=%u", + NameStr(classForm->relname), + before, after, + MyProc->xid, MyProc->xmin); } /* we're done with pg_class */ diff --git a/src/backend/utils/adt/lockfuncs.c b/src/backend/utils/adt/lockfuncs.c index 13009cc..bb2db58 100644 --- a/src/backend/utils/adt/lockfuncs.c +++ b/src/backend/utils/adt/lockfuncs.c @@ -17,8 +17,11 @@ #include "funcapi.h" #include "miscadmin.h" #include "storage/predicate_internals.h" +#include "storage/proc.h" +#include "storage/procarray.h" #include "utils/array.h" #include "utils/builtins.h" +#include "utils/wait_event.h" /* @@ -606,8 +609,9 @@ pg_safe_snapshot_blocking_pids(PG_FUNCTION_ARGS) * * Check if specified PID is blocked by any of the PIDs listed in the second * argument. Currently, this looks for blocking caused by waiting for - * heavyweight locks or safe snapshots. We ignore blockage caused by PIDs - * not directly under the isolationtester's control, eg autovacuum. + * injection points, heavyweight locks, or safe snapshots. We ignore blockage + * caused by PIDs not directly under the isolationtester's control, eg + * autovacuum. * * This is an undocumented function intended for use by the isolation tester, * and may change in future releases as required for testing purposes. @@ -615,8 +619,11 @@ pg_safe_snapshot_blocking_pids(PG_FUNCTION_ARGS) Datum pg_isolation_test_session_is_blocked(PG_FUNCTION_ARGS) { +#define UINT32_ACCESS_ONCE(var) ((uint32)(*((volatile uint32 *)&(var)))) int blocked_pid = PG_GETARG_INT32(0); ArrayType *interesting_pids_a = PG_GETARG_ARRAYTYPE_P(1); + PGPROC *proc; + const char *wait_event; ArrayType *blocking_pids_a; int32 *interesting_pids; int32 *blocking_pids; @@ -626,6 +633,17 @@ pg_isolation_test_session_is_blocked(PG_FUNCTION_ARGS) int i, j; + /* Check if blocked_pid is in injection_wait(). */ + proc = BackendPidGetProc(blocked_pid); + if (proc == NULL) + PG_RETURN_BOOL(false); /* session gone: definitely unblocked */ + wait_event = + pgstat_get_wait_event(UINT32_ACCESS_ONCE(proc->wait_event_info)); + if (wait_event && strncmp("INJECTION_POINT(", + wait_event, + strlen("INJECTION_POINT(")) == 0) + PG_RETURN_BOOL(true); + /* Validate the passed-in array */ Assert(ARR_ELEMTYPE(interesting_pids_a) == INT4OID); if (array_contains_nulls(interesting_pids_a)) diff --git a/src/test/modules/injection_points/Makefile b/src/test/modules/injection_points/Makefile index 31bd787..7ee6135d 100644 --- a/src/test/modules/injection_points/Makefile +++ b/src/test/modules/injection_points/Makefile @@ -9,6 +9,8 @@ PGFILEDESC = "injection_points - facility for injection points" REGRESS = injection_points REGRESS_OPTS = --dlpath=$(top_builddir)/src/test/regress +ISOLATION = inplace_update_reader + # The injection points are cluster-wide, so disable installcheck NO_INSTALLCHECK = 1 diff --git a/src/test/modules/injection_points/expected/inplace_update_reader.out b/src/test/modules/injection_points/expected/inplace_update_reader.out new file mode 100644 index 0000000..799100e --- /dev/null +++ b/src/test/modules/injection_points/expected/inplace_update_reader.out @@ -0,0 +1,292 @@ +Parsed test spec with 5 sessions + +starting permutation: assign1 lock2 vac4 assign1 lock3 attach4 vac4 r2 vac5 detach1 read1 r3 +step assign1: SELECT txid_current(); +txid_current +------------ + 743 +(1 row) + +step lock2: BEGIN; LOCK pg_test_last_table IN SHARE UPDATE EXCLUSIVE MODE; +s4: WARNING: done reading pg_statistic before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_type before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_1255 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_1247 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_2604 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_2606 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_2612 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_2600 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_2619 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_3381 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_3429 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_2618 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_2620 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_3466 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_2609 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_foreign_table before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_2615 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_1262 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_2964 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_1213 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_1260 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_2396 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_3600 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_3079 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_2328 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_1417 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_1418 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_3118 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_3256 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_6000 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_826 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_3394 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_3596 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_3592 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_3456 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_6243 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_3350 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_6106 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_6100 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_authid before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_statistic_ext_data before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_user_mapping before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_subscription before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_attribute before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_proc before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_attrdef before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_constraint before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_inherits before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_index before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_operator before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_opfamily before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_opclass before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_am before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_amop before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_amproc before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_language before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_largeobject_metadata before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_aggregate before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_statistic_ext before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_rewrite before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_trigger before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_event_trigger before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_description before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_cast before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_enum before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_namespace before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_conversion before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_depend before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_database before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_db_role_setting before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_tablespace before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_auth_members before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_shdepend before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_shdescription before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_ts_config before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_ts_config_map before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_ts_dict before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_ts_parser before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_ts_template before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_extension before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_foreign_data_wrapper before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_foreign_server before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_policy before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_replication_origin before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_default_acl before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_init_privs before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_seclabel before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_shseclabel before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_collation before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_parameter_acl before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_partitioned_table before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_range before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_transform before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_sequence before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_publication before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_publication_namespace before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_publication_rel before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_subscription_rel before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_largeobject before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_14108 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading sql_features before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_14113 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading sql_implementation_info before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_14118 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading sql_parts before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_toast_14123 before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading sql_sizing before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_class before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading second_oldest before=744 after=744 myxid=0 myxmin=744 +s4: WARNING: done reading pg_test_last_table before=742 after=742 myxid=0 myxmin=744 +step vac4: VACUUM (FREEZE, SKIP_LOCKED); +step assign1: SELECT txid_current(); +txid_current +------------ + 744 +(1 row) + +step lock3: BEGIN; LOCK second_oldest IN SHARE UPDATE EXCLUSIVE MODE; +step attach4: + SELECT injection_points_set_local(); + SELECT injection_points_attach('between-relfrozenxid-reads', 'wait'); + +injection_points_set_local +-------------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +s4: WARNING: done reading pg_statistic before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_type before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_1255 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_1247 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_2604 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_2606 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_2612 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_2600 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_2619 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_3381 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_3429 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_2618 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_2620 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_3466 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_2609 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_foreign_table before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_2615 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_1262 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_2964 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_1213 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_1260 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_2396 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_3600 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_3079 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_2328 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_1417 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_1418 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_3118 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_3256 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_6000 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_826 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_3394 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_3596 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_3592 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_3456 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_6243 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_3350 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_6106 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_6100 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_authid before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_statistic_ext_data before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_user_mapping before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_subscription before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_attribute before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_proc before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_attrdef before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_constraint before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_inherits before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_index before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_operator before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_opfamily before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_opclass before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_am before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_amop before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_amproc before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_language before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_largeobject_metadata before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_aggregate before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_statistic_ext before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_rewrite before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_trigger before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_event_trigger before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_description before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_cast before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_enum before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_namespace before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_conversion before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_depend before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_database before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_db_role_setting before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_tablespace before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_auth_members before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_shdepend before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_shdescription before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_ts_config before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_ts_config_map before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_ts_dict before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_ts_parser before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_ts_template before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_extension before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_foreign_data_wrapper before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_foreign_server before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_policy before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_replication_origin before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_default_acl before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_init_privs before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_seclabel before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_shseclabel before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_collation before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_parameter_acl before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_partitioned_table before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_range before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_transform before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_sequence before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_publication before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_publication_namespace before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_publication_rel before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_subscription_rel before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_largeobject before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_14108 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading sql_features before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_14113 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading sql_implementation_info before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_14118 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading sql_parts before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_toast_14123 before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading sql_sizing before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading pg_class before=745 after=745 myxid=0 myxmin=745 +s4: WARNING: done reading second_oldest before=744 after=744 myxid=0 myxmin=745 +step vac4: VACUUM (FREEZE, SKIP_LOCKED); +step r2: ROLLBACK; +step vac5: VACUUM (FREEZE, SKIP_DATABASE_STATS) pg_test_last_table; +step detach1: + SELECT injection_points_detach('between-relfrozenxid-reads'); + +injection_points_detach +----------------------- + +(1 row) + +s4: WARNING: done reading pg_test_last_table before=742 after=745 myxid=0 myxmin=745 +step vac4: <... completed> +step read1: + -- should never return rows, but it does in this test case + select c.oid::regclass + from pg_database d, pg_class c + where relkind = 'r' and age(relfrozenxid) > age(datfrozenxid); + + select age(datfrozenxid) from pg_database where datname = current_catalog; + + select min(age(relfrozenxid)), max(age(relfrozenxid)) + from pg_class c + where relkind = 'r'; + +oid +------------- +second_oldest +(1 row) + +age +--- + 0 +(1 row) + +min|max +---+--- + 0| 1 +(1 row) + +step r3: ROLLBACK; diff --git a/src/test/modules/injection_points/injection_points.c b/src/test/modules/injection_points/injection_points.c index ace386d..f4fe678 100644 --- a/src/test/modules/injection_points/injection_points.c +++ b/src/test/modules/injection_points/injection_points.c @@ -205,6 +205,7 @@ injection_notice(const char *name) void injection_wait(const char *name) { + char event_name[NAMEDATALEN]; uint32 old_wait_counts = 0; int index = -1; uint32 injection_wait_event = 0; @@ -216,11 +217,11 @@ injection_wait(const char *name) return; /* - * Use the injection point name for this custom wait event. Note that - * this custom wait event name is not released, but we don't care much for - * testing as this should be short-lived. + * Note that this custom wait event name is not released, but we don't + * care much for testing as this should be short-lived. */ - injection_wait_event = WaitEventExtensionNew(name); + snprintf(event_name, sizeof(event_name), "INJECTION_POINT(%s)", name); + injection_wait_event = WaitEventExtensionNew(event_name); /* * Find a free slot to wait for, and register this injection point's name. @@ -402,14 +403,28 @@ Datum injection_points_detach(PG_FUNCTION_ARGS) { char *name = text_to_cstring(PG_GETARG_TEXT_PP(0)); + int wait_index = -1; InjectionPointDetach(name); if (inj_state == NULL) injection_init_shmem(); - /* Clean up any conditions associated to this injection point */ SpinLockAcquire(&inj_state->lock); + + /* First bump any wait counter for the injection point to wake up */ + for (int i = 0; i < INJ_MAX_WAIT; i++) + { + if (strcmp(name, inj_state->name[i]) == 0) + { + wait_index = i; + break; + } + } + if (wait_index >= 0) + inj_state->wait_counts[wait_index]++; + + /* Clean up any conditions associated to this injection point */ for (int i = 0; i < INJ_MAX_CONDITION; i++) { InjectionPointCondition *condition = &inj_state->conditions[i]; @@ -420,7 +435,12 @@ injection_points_detach(PG_FUNCTION_ARGS) condition->name[0] = '\0'; } } + SpinLockRelease(&inj_state->lock); + /* And broadcast the change to any waiters */ + if (wait_index >= 0) + ConditionVariableBroadcast(&inj_state->wait_point); + PG_RETURN_VOID(); } diff --git a/src/test/modules/injection_points/specs/inplace_update_reader.spec b/src/test/modules/injection_points/specs/inplace_update_reader.spec new file mode 100644 index 0000000..325ba12 --- /dev/null +++ b/src/test/modules/injection_points/specs/inplace_update_reader.spec @@ -0,0 +1,75 @@ +# Test race between vac_update_relstats() and vac_update_datfrozenxid() + +# damage is hard to reach because: +# - When v_u_d() first reads the value, the value must be a new minimum. +# - The same v_u_d() must not read any later tuple that wipes out the damage. + +setup +{ + CREATE EXTENSION injection_points; +} +setup +{ + VACUUM FULL pg_class; -- help CREATE TABLE place at end +} +setup +{ + CREATE TABLE second_oldest (); + CREATE TABLE pg_test_last_table (); +} + +teardown +{ + DROP EXTENSION injection_points; +} + + +# s1 does non-blocking, no-transaction-block activities +session s1 +step assign1 { SELECT txid_current(); } +step read1 +{ + -- should never return rows, but it does in this test case + select c.oid::regclass + from pg_database d, pg_class c + where relkind = 'r' and age(relfrozenxid) > age(datfrozenxid); + + select age(datfrozenxid) from pg_database where datname = current_catalog; + + select min(age(relfrozenxid)), max(age(relfrozenxid)) + from pg_class c + where relkind = 'r'; +} +step detach1 +{ + SELECT injection_points_detach('between-relfrozenxid-reads'); +} + +# s2 holds the lock on pg_test_last_table +session s2 +step lock2 { BEGIN; LOCK pg_test_last_table IN SHARE UPDATE EXCLUSIVE MODE; } +step r2 { ROLLBACK; } + +# s3 holds the lock on second_oldest +session s3 +step lock3 { BEGIN; LOCK second_oldest IN SHARE UPDATE EXCLUSIVE MODE; } +step r3 { ROLLBACK; } + +# s4 does database-wide VACUUM w/ SKIP_LOCKED +session s4 +step attach4 +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('between-relfrozenxid-reads', 'wait'); +} +step vac4 { VACUUM (FREEZE, SKIP_LOCKED); } + +# s5 does single-table VACUUM +session s5 +step vac5 { VACUUM (FREEZE, SKIP_DATABASE_STATS) pg_test_last_table; } + + +# 1st vac4 updates every relfrozenxid except pg_test_last_table +# 2nd vac4 updates every relfrozenxid except pg_test_last_table and second_oldest +# vac5 updates pg_test_last_table relfrozenxid between the two vac_update_datfrozenxid reads +permutation assign1 lock2 vac4 assign1 lock3 attach4 vac4(detach1) r2 vac5 detach1 read1 r3