mirror of https://github.com/citusdata/citus.git
Fix Subtransaction memory leak
parent
4c68ed4c33
commit
3651fc64ee
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -593,7 +593,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));
|
||||||
|
@ -612,19 +622,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -49,3 +49,7 @@ CREATE OR REPLACE FUNCTION pg_catalog.partition_task_list_results(resultIdPrefix
|
||||||
targetShardIndex int)
|
targetShardIndex int)
|
||||||
LANGUAGE C STRICT VOLATILE
|
LANGUAGE C STRICT VOLATILE
|
||||||
AS 'citus', $$partition_task_list_results$$;
|
AS 'citus', $$partition_task_list_results$$;
|
||||||
|
-- get size of TopTransactionContext
|
||||||
|
CREATE OR REPLACE FUNCTION top_transaction_context_size() RETURNS BIGINT
|
||||||
|
LANGUAGE C STRICT VOLATILE
|
||||||
|
AS 'citus', $$top_transaction_context_size$$;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -48,3 +48,9 @@ CREATE OR REPLACE FUNCTION pg_catalog.partition_task_list_results(resultIdPrefix
|
||||||
targetShardIndex int)
|
targetShardIndex int)
|
||||||
LANGUAGE C STRICT VOLATILE
|
LANGUAGE C STRICT VOLATILE
|
||||||
AS 'citus', $$partition_task_list_results$$;
|
AS 'citus', $$partition_task_list_results$$;
|
||||||
|
|
||||||
|
|
||||||
|
-- get size of TopTransactionContext
|
||||||
|
CREATE OR REPLACE FUNCTION top_transaction_context_size() RETURNS BIGINT
|
||||||
|
LANGUAGE C STRICT VOLATILE
|
||||||
|
AS 'citus', $$top_transaction_context_size$$;
|
||||||
|
|
Loading…
Reference in New Issue