Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/pglite/.changeset/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Changesets

Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)

We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
5 changes: 5 additions & 0 deletions packages/pglite/.changeset/big-lobsters-bathe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@electric-sql/pglite': minor
---

Ensure MessageContext and its children are actually cleared between queries
11 changes: 11 additions & 0 deletions packages/pglite/.changeset/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"$schema": "https://unpkg.com/@changesets/config@3.0.3/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "restricted",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}
66 changes: 66 additions & 0 deletions packages/pglite/tests/message-context-leak.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { describe, it, expect, beforeEach } from 'vitest'
import { testDTC } from './test-utils.js'
import { PGlite } from '../dist/index.js'

// This test isolates the MessageContext leak reported in
// https://github.com/electric-sql/pglite/issues/779
// It inserts many rows with large JSON literals and then inspects
// pg_backend_memory_contexts to ensure MessageContext has been reset
// between queries and does not accumulate unbounded allocations.

const KB = 1024

function makeJsonBlob(size: number): string {
// Keep the SQL literal simple (mostly "x" payload) to simulate
// large messages while avoiding excessive parsing overhead.
return JSON.stringify({ padding: 'x'.repeat(size) })
}

testDTC(async (defaultDataTransferContainer) => {
describe('MessageContext reset between queries', () => {
let db: PGlite

beforeEach(async () => {
db = new PGlite({ defaultDataTransferContainer })
await db.exec(`
CREATE TABLE IF NOT EXISTS leak_test (
id SERIAL PRIMARY KEY,
blob jsonb NOT NULL
);
`)
})

it('does not accumulate allocations in MessageContext', async () => {
// Choose sizes to expose the leak without taking too long.
const blobSize = 100 * KB // ~100KB per row
const rows = 300 // ~30MB of total SQL literal payload

const blob = makeJsonBlob(blobSize)

for (let i = 0; i < rows; i++) {
await db.exec(`INSERT INTO leak_test (blob) VALUES ('${blob}')`)
}

// After the loop, the next query should see a freshly reset
// MessageContext (reset happens at the start of command read),
// so used_bytes should remain small (well below the total data inserted).
const mem = await db.query<{ used_bytes: number }>(`
SELECT used_bytes
FROM pg_backend_memory_contexts
WHERE name = 'MessageContext'
ORDER BY level
LIMIT 1
`)

expect(mem.rows).toHaveLength(1)
const used = Number(mem.rows[0].used_bytes)

// On a correctly resetting build, MessageContext should typically be in the
// low kilobytes to a few megabytes range. Set an upper bound that will fail
// if allocations accumulated across the INSERTs (~30MB of literal payload).
// Using 5MB as a generous ceiling for transient allocations of this SELECT.
expect(used).toBeLessThan(5 * 1024 * 1024)
})
})
})