|
| 1 | +import { describe, it, expect, beforeEach } from 'vitest' |
| 2 | +import { testDTC } from './test-utils.js' |
| 3 | +import { PGlite } from '../dist/index.js' |
| 4 | + |
| 5 | +// This test isolates the MessageContext leak reported in |
| 6 | +// https://github.com/electric-sql/pglite/issues/779 |
| 7 | +// It inserts many rows with large JSON literals and then inspects |
| 8 | +// pg_backend_memory_contexts to ensure MessageContext has been reset |
| 9 | +// between queries and does not accumulate unbounded allocations. |
| 10 | + |
| 11 | +const KB = 1024 |
| 12 | + |
| 13 | +function makeJsonBlob(size: number): string { |
| 14 | + // Keep the SQL literal simple (mostly "x" payload) to simulate |
| 15 | + // large messages while avoiding excessive parsing overhead. |
| 16 | + return JSON.stringify({ padding: 'x'.repeat(size) }) |
| 17 | +} |
| 18 | + |
| 19 | +testDTC(async (defaultDataTransferContainer) => { |
| 20 | + describe('MessageContext reset between queries', () => { |
| 21 | + let db: PGlite |
| 22 | + |
| 23 | + beforeEach(async () => { |
| 24 | + db = new PGlite({ defaultDataTransferContainer }) |
| 25 | + await db.exec(` |
| 26 | + CREATE TABLE IF NOT EXISTS leak_test ( |
| 27 | + id SERIAL PRIMARY KEY, |
| 28 | + blob jsonb NOT NULL |
| 29 | + ); |
| 30 | + `) |
| 31 | + }) |
| 32 | + |
| 33 | + it('does not accumulate allocations in MessageContext', async () => { |
| 34 | + // Choose sizes to expose the leak without taking too long. |
| 35 | + const blobSize = 100 * KB // ~100KB per row |
| 36 | + const rows = 300 // ~30MB of total SQL literal payload |
| 37 | + |
| 38 | + const blob = makeJsonBlob(blobSize) |
| 39 | + |
| 40 | + for (let i = 0; i < rows; i++) { |
| 41 | + await db.exec(`INSERT INTO leak_test (blob) VALUES ('${blob}')`) |
| 42 | + } |
| 43 | + |
| 44 | + // After the loop, the next query should see a freshly reset |
| 45 | + // MessageContext (reset happens at the start of command read), |
| 46 | + // so used_bytes should remain small (well below the total data inserted). |
| 47 | + const mem = await db.query<{ used_bytes: number }>(` |
| 48 | + SELECT used_bytes |
| 49 | + FROM pg_backend_memory_contexts |
| 50 | + WHERE name = 'MessageContext' |
| 51 | + ORDER BY level |
| 52 | + LIMIT 1 |
| 53 | + `) |
| 54 | + |
| 55 | + expect(mem.rows).toHaveLength(1) |
| 56 | + const used = Number(mem.rows[0].used_bytes) |
| 57 | + |
| 58 | + // On a correctly resetting build, MessageContext should typically be in the |
| 59 | + // low kilobytes to a few megabytes range. Set an upper bound that will fail |
| 60 | + // if allocations accumulated across the INSERTs (~30MB of literal payload). |
| 61 | + // Using 5MB as a generous ceiling for transient allocations of this SELECT. |
| 62 | + expect(used).toBeLessThan(5 * 1024 * 1024) |
| 63 | + }) |
| 64 | + }) |
| 65 | +}) |
0 commit comments