SQL API Reference
All SQL functions live in the ext_memcheck schema unless noted.
ext_memcheck.flush_violations()
Section titled “ext_memcheck.flush_violations()”Moves all entries from the in-memory shared ring buffer (ViolationLog) into the persistent ext_memcheck.violation_log table and returns the count of rows inserted i.e., the number of violations flushed. It also clears the ring buffer, making it ready to log new violations.
-- flushes all pending violations to the table ext_memcheck.violation_log-- and returns the count of violationsSELECT ext_memcheck.flush_violations();
-- query the violation log table directly-- includes all historical violations, not just the flushed onesSELECT * FROM ext_memcheck.violation_log;Returns: int — number of violations flushed to the table.
Notes:
- The ring buffer is bounded by
MEMCHECK_MAX_VIOLATIONS(constant2048, not configurable). When full, oldest entries are overwritten. Callflush_violations()regularly to avoid data loss. - Returns zero if no violations have been detected since the last flush.
flush_violations()drains the entire ring across all backends;end()drains only the current session's entries. Entries consumed by one function are not visible to the other.
Violation log table schema
Section titled “Violation log table schema”| Column | Type | Description |
|---|---|---|
id | int | Auto-incrementing violation ID |
ts | timestamptz | Timestamp of the violation |
backend_pid | int | PID of the backend that detected the violation |
check_type | text | Type of the check: context_leak, wrong_ctx_alloc, ctx_bloat, shmem_overrun, dsm_leak |
severity | text | Severity: ERROR, WARNING, or INFO |
detail | text | Human-readable description of the violation |
source_lib | text | The library that triggered the violation (e.g., buggy_pg_ext) |
Severity levels
Section titled “Severity levels”| Level | Memory Leak Size |
|---|---|
ERROR | > 1 MiB |
WARNING | > 64 KiB and ≤ 1 MiB |
INFO | ≥ min_leak_bytes and ≤ 64 KiB |
ext_memcheck.begin()
Section titled “ext_memcheck.begin()”Opens a manual test window targeting a specific extension by context-name pattern. The monitoring mode (all / executor / none) is controlled by the pg_ext_memcheck.memcheck_mode GUC. If the mode is none when begin() is called, begin() activates it to all so a window opens without a prior SET; an explicit pre-SET of executor or all is honoured unchanged. The matching end() resets the mode back to none.
-- Scope checks to contexts whose names match the SQL LIKE pattern 'MyExtCtx%'SET pg_ext_memcheck.memcheck_mode = 'executor';SELECT ext_memcheck.begin('MyExtCtx%');
-- Monitor all contexts (empty pattern = no filter)SET pg_ext_memcheck.memcheck_mode = 'all';SELECT ext_memcheck.begin('');
-- With options: allowlist contexts that are permitted to grow, and opt out of DSM checksSELECT ext_memcheck.begin( 'MyExtCtx%', '{"track_shmem": true, "track_dsm": false, "allowed_contexts": ["TopMemoryContext"]}');Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
ext_context_pattern | text | '' | SQL LIKE pattern (% wildcard) for context names to monitor. An empty string monitors all contexts. Example: 'MyExtCtx%' monitors only contexts whose names start with MyExtCtx. |
options | jsonb | NULL | Optional JSON object with the following keys: |
options keys:
| Key | Type | Default | Description |
|---|---|---|---|
track_shmem | bool | true | Whether shmem sentinel checks are active for this session. |
track_dsm | bool | true | Whether DSM leak checks are active for this session. |
allowed_contexts | text[] | [] | Allowlist of context names explicitly permitted to grow without triggering a wrong_ctx_alloc violation. Extensions that intentionally cache data in TopMemoryContext across queries should list it here to suppress false positives. |
Returns: text — confirmation message.
Why scope matters: Without a pattern, every PostgreSQL core context that grows during a query (e.g., CacheMemoryContext, ExprContext) is reported and attributed to whatever hook libraries are loaded. A targeted pattern eliminates these false positives and pinpoints violations to the extension under test.
-- Full targeted session exampleSET pg_ext_memcheck.memcheck_mode = 'executor';SELECT ext_memcheck.begin( 'MyExtCtx%', '{"allowed_contexts": ["TopMemoryContext"]}');
-- Run the function under testSELECT my_extension.do_work();
-- End the window; returns only violations from contexts matching 'MyExtCtx%'SELECT * FROM ext_memcheck.end();ext_memcheck.end()
Section titled “ext_memcheck.end()”Closes the current manual test window, resets pg_ext_memcheck.memcheck_mode to 'none', and returns violations belonging to the current session as a result set.
SELECT * FROM ext_memcheck.end();Returns: TABLE(check_type TEXT, severity TEXT, detail TEXT, ts TIMESTAMPTZ, source_lib TEXT) — violations detected during this session window.
Session-scoped drain semantics:
- Only entries where
backend_pidmatches the calling backend andts >= begin()call time are returned. - Matched slots are zeroed from the ring buffer atomically, so a second call to
end()returns 0 rows (non-repeatable read). - Violations from other concurrent backends are not affected.
- The ring buffer is not flushed to
violation_log; those entries remain available toflush_violations()unless they were already drained byend().
ext_memcheck.run_scenario()
Section titled “ext_memcheck.run_scenario()”Runs a named stress scenario. Violations are written to the shared ring buffer; call ext_memcheck.flush_violations() to read them by flushing the buffer.
SELECT ext_memcheck.run_scenario(scenario_name := 'growth_benchmark', iterations := 200, workload := 'SELECT 1');SELECT ext_memcheck.run_scenario(scenario_name := 'tx_abort_loop', iterations := 50, workload := 'SELECT 1');Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
scenario_name | text | — | Scenario name. Six scenarios are available: growth_benchmark, tx_abort_loop, shmem_sentinel_probe, wrong_context_probe (in-process), plus use_after_reset and oom_simulation (BGWorker crash-isolated). See Stress Scenarios. |
iterations | int | 100 | Number of iterations to execute within the scenario. |
workload | text | 'SELECT 1' | SQL query to execute within each iteration. Supply a custom query to simulate your extension's workload. |
Returns: text — confirmation message.
ext_memcheck.clear_violations()
Section titled “ext_memcheck.clear_violations()”Utility function to clear all entries from the ext_memcheck.violation_log table. Does not affect the in-memory ring buffer.
SELECT ext_memcheck.clear_violations();ext_memcheck.dsm_tracking()
Section titled “ext_memcheck.dsm_tracking()”Returns the current contents of the DSM segment tracking table — every segment that has been registered via ext_memcheck.track_dsm_handle() since the last ext_memcheck.clear_dsm_tracking() call.
SELECT * FROM ext_memcheck.dsm_tracking();Returns: TABLE(segid BIGINT, backend_pid INTEGER, attach_at TIMESTAMPTZ, size_bytes BIGINT, detached BOOLEAN)
| Column | Description |
|---|---|
segid | DSM handle (OS-level segment identifier) |
backend_pid | PID of the backend that recorded the attachment |
attach_at | Timestamp when the segment was tracked |
size_bytes | Segment size in bytes |
detached | true if a matching detach was observed, false if leaked |
ext_memcheck.track_dsm_handle()
Section titled “ext_memcheck.track_dsm_handle()”Registers a DSM handle with the DSM lifecycle tracker so that pg_ext_memcheck can detect whether it is properly detached.
SELECT ext_memcheck.track_dsm_handle(1234);Parameter: handle BIGINT — the DSM handle to track.
Returns: text — confirmation message.
ext_memcheck.clear_dsm_tracking()
Section titled “ext_memcheck.clear_dsm_tracking()”Resets the DSM tracking table by zeroing all recorded segment entries. Call this between test runs to start with a clean slate.
SELECT ext_memcheck.clear_dsm_tracking();Returns: void
ext_memcheck.register_shmem_probe()
Section titled “ext_memcheck.register_shmem_probe()”Registers a shared memory segment for sentinel probing. Use this to probe your own extension's ShmemInitStruct allocation — pg_ext_memcheck will plant a 0xDE sentinel byte immediately after your declared data and verify it is intact after each workload run.
SELECT ext_memcheck.register_shmem_probe('my_ext SomeStruct', 1234);Parameters:
| Parameter | Type | Description |
|---|---|---|
seg_name | text | Name the segment is registered under in ShmemIndex — must match the first argument to ShmemInitStruct exactly. |
allocated_size | bigint | Exact size in bytes used when the segment was allocated. The sentinel is planted at base_ptr[allocated_size], which must fall within the alignment slack reserved by CACHELINEALIGN(allocated_size). |
Returns: text — confirmation message.
Notes:
- The segment must already exist in
ShmemIndexwhen this function is called. If it does not, a WARNING is issued and no probe is registered. - Call this once per test session, before running
ext_memcheck.run_scenario('shmem_sentinel_probe', ...). - pg_ext_memcheck's own segments (
ViolationLog,DsmTrackerState) are registered automatically by the scenario and do not need to be registered manually.
-- Register your extension's shmem segment, then run the probe scenarioSELECT ext_memcheck.register_shmem_probe('my_ext SomeStruct', 1234);SELECT ext_memcheck.run_scenario('shmem_sentinel_probe', 10, 'SELECT my_ext.some_function()');SELECT ext_memcheck.flush_violations();SELECT * FROM ext_memcheck.violation_log WHERE check_type = 'shmem_overrun';
-- Clear the registry between test runsSELECT ext_memcheck.clear_shmem_registry();ext_memcheck.probe_check()
Section titled “ext_memcheck.probe_check()”Checks whether the 0xDE sentinel byte planted by register_shmem_probe() is still intact after the workload ran. Returns true if the sentinel is unmodified, false if it was overwritten (indicating a boundary overrun).
SELECT ext_memcheck.probe_check('my_ext SomeStruct');Parameter: seg_name TEXT — must match exactly the name used in the prior register_shmem_probe() call.
Returns: boolean — true if sentinel intact, false if overwritten.
Note: You do not need to call this manually when using run_scenario('shmem_sentinel_probe', …) — the scenario checks all registered probes automatically and writes shmem_overrun violations to the ring buffer. probe_check() is provided for manual, granular inspection outside of a scenario run.
ext_memcheck.clear_shmem_registry()
Section titled “ext_memcheck.clear_shmem_registry()”Resets the shmem sentinel probe registry by clearing all registered segment records. Existing sentinel bytes in shared memory are left in place; subsequent run_scenario('shmem_sentinel_probe', ...) calls will re-register and rewrite them.
SELECT ext_memcheck.clear_shmem_registry();Returns: void
GUC parameters
Section titled “GUC parameters”Set these in postgresql.conf or with SET at session scope (PGC_USERSET — no restart required).
| Parameter | Type | Default | Description |
|---|---|---|---|
pg_ext_memcheck.memcheck_mode | enum | none | Controls which parts of query execution are instrumented. Set to none until begin() activates a window. See below. |
pg_ext_memcheck.min_leak_bytes | int | 8192 | Minimum context growth in bytes required to log a violation. Smaller growth is silently ignored. |
pg_ext_memcheck.bloat_min_bytes | int | 8192 | Minimum cumulative growth in bytes for a context to be reported as bloating by growth_benchmark. Contexts that grow by less than this amount across checkpoints are silently ignored. |
pg_ext_memcheck.bloat_min_bytes
Section titled “pg_ext_memcheck.bloat_min_bytes”Minimum cumulative memory growth (in bytes) that a MemoryContext must show across growth_benchmark checkpoints before a ctx_bloat violation is emitted. Contexts whose net growth falls below this threshold are silently ignored.
-- Raise the threshold to 64 KiB to reduce noiseSET pg_ext_memcheck.bloat_min_bytes = 65536;
-- Lower to 0 to report every context that grew at allSET pg_ext_memcheck.bloat_min_bytes = 0;Default: 8192 (8 KiB)
Range: 0 – INT_MAX
Scope: PGC_USERSET — effective immediately, no restart required.
pg_ext_memcheck.memcheck_mode
Section titled “pg_ext_memcheck.memcheck_mode”The primary control knob for the extension. Determines which execution phases are hooked and checked.
-- Disable all instrumentation (zero overhead)SET pg_ext_memcheck.memcheck_mode = 'none';
-- Check only executor-phase allocations (default for focused testing)SET pg_ext_memcheck.memcheck_mode = 'executor';
-- Check all phases: planner + executorSET pg_ext_memcheck.memcheck_mode = 'all';| Value | Hooks active | When to use |
|---|---|---|
none | None | Disable the extension without unloading it; zero runtime overhead. |
executor | ExecutorStart, ExecutorEnd | Focused testing of executor-phase allocations. |
all | Planner planner_hook + Executor ExecutorEnd | Full sweep — catches leaks that happen during planning and execution. |
Recommended workflow:
- Start with
executorto isolate your function under test. - Promote to
allbefore marking the extension as clean — planner hooks and utility handlers are common sources of leaks thatexecutor-only misses. - Set to
nonein shared CI environments where another test suite is running, to avoid cross-contamination of violation logs.
Violation types reference
Section titled “Violation types reference”| Type | Phase | Status | Description |
|---|---|---|---|
context_leak | 1 | Available | A MemoryContext created during query execution was not freed by ExecutorEnd. |
wrong_ctx_alloc | 1 | Available | A palloc() landed in a long-lived context (TopMemoryContext, CacheMemoryContext, or a user-configured forbidden context). |
shmem_overrun | 1 | Available | Sentinel bytes after a ShmemAlloc() allocation were overwritten. |
dsm_leak | 1 | Available | A DSM segment was attached during a test window but not detached before the window closed. |
ctx_bloat | 1 | Available | A MemoryContext grew monotonically across two or more log-spaced checkpoints in a growth_benchmark run. Severity reflects total growth; superlinear acceleration bumps severity one level. |
use_after_reset | 2 | Available | BGWorker crash confirmed via non-zero exit code after elog(FATAL) in an isolated worker process. |