Fix Subtransaction memory leak

(cherry picked from commit 3651fc64ee)

 Conflicts:
	src/test/regress/expected/multi_test_helpers_superuser.out
	src/test/regress/sql/multi_test_helpers_superuser.sql

 This helper sql file was not in release-9.2. So create helper file
 only with top_transaction_context_size and add this to multi_schedule
release-9.2
Hadi Moshayedi 2020-07-08 19:42:39 -07:00 committed by Onur Tirtir
parent 0419d340b6
commit a2e9a59007
8 changed files with 157 additions and 19 deletions

View File

@ -127,7 +127,8 @@ PostprocessVariableSetStmt(VariableSetStmt *setStmt, const char *setStmtString)
/* haven't seen any SET stmts so far in this (sub-)xact: initialize StringInfo */ /* haven't seen any SET stmts so far in this (sub-)xact: initialize StringInfo */
if (activeSetStmts == NULL) if (activeSetStmts == NULL)
{ {
MemoryContext old_context = MemoryContextSwitchTo(CurTransactionContext); /* see comments in PushSubXact on why we allocate this in TopTransactionContext */
MemoryContext old_context = MemoryContextSwitchTo(TopTransactionContext);
activeSetStmts = makeStringInfo(); activeSetStmts = makeStringInfo();
MemoryContextSwitchTo(old_context); MemoryContextSwitchTo(old_context);
} }

View File

@ -0,0 +1,56 @@
/*-------------------------------------------------------------------------
*
* xact_stats.c
*
* This file contains functions to provide helper UDFs for testing transaction
* statistics.
*
* Copyright (c) Citus Data, Inc.
*
*-------------------------------------------------------------------------
*/
#include <sys/stat.h>
#include <unistd.h>
#include "postgres.h"
#include "funcapi.h"
#include "libpq-fe.h"
#include "miscadmin.h"
#include "pgstat.h"
static Size MemoryContextTotalSpace(MemoryContext context);
PG_FUNCTION_INFO_V1(top_transaction_context_size);
/*
* top_transaction_context_size returns current size of TopTransactionContext.
*/
Datum
top_transaction_context_size(PG_FUNCTION_ARGS)
{
Size totalSpace = MemoryContextTotalSpace(TopTransactionContext);
PG_RETURN_INT64(totalSpace);
}
/*
* MemoryContextTotalSpace returns total space allocated in context and its children.
*/
static Size
MemoryContextTotalSpace(MemoryContext context)
{
Size totalSpace = 0;
MemoryContextCounters totals = { 0 };
TopTransactionContext->methods->stats(TopTransactionContext, NULL, NULL, &totals);
totalSpace += totals.totalspace;
for (MemoryContext child = context->firstchild;
child != NULL;
child = child->nextchild)
{
totalSpace += MemoryContextTotalSpace(child);
}
return totalSpace;
}

View File

@ -556,7 +556,17 @@ AdjustMaxPreparedTransactions(void)
static void static void
PushSubXact(SubTransactionId subId) PushSubXact(SubTransactionId subId)
{ {
MemoryContext old_context = MemoryContextSwitchTo(CurTransactionContext); /*
* We need to allocate these in TopTransactionContext instead of current
* subxact's memory context. This is because AtSubCommit_Memory won't
* delete the subxact's memory context unless it is empty, and this
* can cause in memory leaks. For emptiness it just checks if the memory
* has been reset, and we cannot reset the subxact context since other
* data can be in the context that are needed by upper commits.
*
* See https://github.com/citusdata/citus/issues/3999
*/
MemoryContext old_context = MemoryContextSwitchTo(TopTransactionContext);
/* save provided subId as well as propagated SET LOCAL stmts */ /* save provided subId as well as propagated SET LOCAL stmts */
SubXactContext *state = palloc(sizeof(SubXactContext)); SubXactContext *state = palloc(sizeof(SubXactContext));
@ -575,19 +585,34 @@ PushSubXact(SubTransactionId subId)
static void static void
PopSubXact(SubTransactionId subId) PopSubXact(SubTransactionId subId)
{ {
MemoryContext old_context = MemoryContextSwitchTo(CurTransactionContext);
SubXactContext *state = linitial(activeSubXactContexts); SubXactContext *state = linitial(activeSubXactContexts);
/*
* the previous activeSetStmts is already invalid because it's in the now-
* aborted subxact (what we're popping), so no need to free before assign-
* ing with the setLocalCmds of the popped context
*/
Assert(state->subId == subId); Assert(state->subId == subId);
activeSetStmts = state->setLocalCmds;
activeSubXactContexts = list_delete_first(activeSubXactContexts);
MemoryContextSwitchTo(old_context); /*
* Free activeSetStmts to avoid memory leaks when we create subxacts
* for each row, e.g. in exception handling of UDFs.
*/
if (activeSetStmts != NULL)
{
pfree(activeSetStmts->data);
pfree(activeSetStmts);
}
/*
* SET LOCAL commands are local to subxact blocks. When a subxact commits
* or rolls back, we should roll back our set of SET LOCAL commands to the
* ones we had in the upper commit.
*/
activeSetStmts = state->setLocalCmds;
/*
* Free state to avoid memory leaks when we create subxacts for each row,
* e.g. in exception handling of UDFs.
*/
pfree(state);
activeSubXactContexts = list_delete_first(activeSubXactContexts);
} }

View File

@ -1,3 +1,5 @@
CREATE SCHEMA multi_subtransactions;
SET search_path TO 'multi_subtransactions';
CREATE TABLE artists ( CREATE TABLE artists (
id bigint NOT NULL, id bigint NOT NULL,
name text NOT NULL name text NOT NULL
@ -159,7 +161,7 @@ BEGIN;
DELETE FROM artists; DELETE FROM artists;
SAVEPOINT s1; SAVEPOINT s1;
INSERT INTO artists SELECT NULL, NULL FROM generate_series(1, 5) i; INSERT INTO artists SELECT NULL, NULL FROM generate_series(1, 5) i;
ERROR: the partition column of table public.artists cannot be NULL ERROR: the partition column of table multi_subtransactions.artists cannot be NULL
ROLLBACK TO s1; ROLLBACK TO s1;
INSERT INTO artists VALUES (11, 'Egon Schiele'); INSERT INTO artists VALUES (11, 'Egon Schiele');
COMMIT; COMMIT;
@ -175,7 +177,7 @@ BEGIN;
DELETE FROM artists; DELETE FROM artists;
SAVEPOINT s1; SAVEPOINT s1;
INSERT INTO artists(name) SELECT 'a' FROM generate_series(1, 5) i; INSERT INTO artists(name) SELECT 'a' FROM generate_series(1, 5) i;
ERROR: the partition column of table public.artists should have a value ERROR: the partition column of table multi_subtransactions.artists should have a value
ROLLBACK TO s1; ROLLBACK TO s1;
INSERT INTO artists VALUES (12, 'Marc Chagall'); INSERT INTO artists VALUES (12, 'Marc Chagall');
COMMIT; COMMIT;
@ -401,6 +403,29 @@ SELECT * FROM researchers WHERE lab_id=10;
32 | 10 | Raymond Smullyan 32 | 10 | Raymond Smullyan
(2 rows) (2 rows)
-- Verify that we don't have a memory leak in subtransactions
-- See https://github.com/citusdata/citus/pull/4000
CREATE FUNCTION text2number(v_value text) RETURNS numeric
LANGUAGE plpgsql VOLATILE
AS $$
BEGIN
RETURN v_value::numeric;
exception
when others then
return null;
END;
$$;
-- if we leak at least an integer in each subxact, then size of TopTransactionSize
-- will be way beyond the 50k limit. If issue #3999 happens, then this will also take
-- a long time, since for each row we will create a memory context that is not destroyed
-- until the end of command.
SELECT max(text2number('1234')), max(public.top_transaction_context_size()) > 50000 AS leaked
FROM generate_series(1, 20000);
max | leaked
---------------------------------------------------------------------
1234 | f
(1 row)
-- Clean-up -- Clean-up
DROP TABLE artists; SET client_min_messages TO ERROR;
DROP TABLE researchers; DROP SCHEMA multi_subtransactions CASCADE;

View File

@ -0,0 +1,4 @@
-- get size of TopTransactionContext
CREATE OR REPLACE FUNCTION top_transaction_context_size() RETURNS BIGINT
LANGUAGE C STRICT VOLATILE
AS 'citus', $$top_transaction_context_size$$;

View File

@ -25,7 +25,7 @@ test: multi_cluster_management
test: alter_role_propagation test: alter_role_propagation
test: propagate_extension_commands test: propagate_extension_commands
test: escape_extension_name test: escape_extension_name
test: multi_test_helpers test: multi_test_helpers multi_test_helpers_superuser
test: multi_test_catalog_views test: multi_test_catalog_views
test: multi_table_ddl test: multi_table_ddl
test: multi_name_lengths test: multi_name_lengths

View File

@ -1,3 +1,6 @@
CREATE SCHEMA multi_subtransactions;
SET search_path TO 'multi_subtransactions';
CREATE TABLE artists ( CREATE TABLE artists (
id bigint NOT NULL, id bigint NOT NULL,
name text NOT NULL name text NOT NULL
@ -305,7 +308,27 @@ COMMIT;
SELECT * FROM researchers WHERE lab_id=10; SELECT * FROM researchers WHERE lab_id=10;
-- Clean-up -- Verify that we don't have a memory leak in subtransactions
DROP TABLE artists; -- See https://github.com/citusdata/citus/pull/4000
DROP TABLE researchers;
CREATE FUNCTION text2number(v_value text) RETURNS numeric
LANGUAGE plpgsql VOLATILE
AS $$
BEGIN
RETURN v_value::numeric;
exception
when others then
return null;
END;
$$;
-- if we leak at least an integer in each subxact, then size of TopTransactionSize
-- will be way beyond the 50k limit. If issue #3999 happens, then this will also take
-- a long time, since for each row we will create a memory context that is not destroyed
-- until the end of command.
SELECT max(text2number('1234')), max(public.top_transaction_context_size()) > 50000 AS leaked
FROM generate_series(1, 20000);
-- Clean-up
SET client_min_messages TO ERROR;
DROP SCHEMA multi_subtransactions CASCADE;

View File

@ -0,0 +1,4 @@
-- get size of TopTransactionContext
CREATE OR REPLACE FUNCTION top_transaction_context_size() RETURNS BIGINT
LANGUAGE C STRICT VOLATILE
AS 'citus', $$top_transaction_context_size$$;