Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ node_modules
lib
package-lock.json
.idea
*.heapsnapshot
4 changes: 2 additions & 2 deletions simple-git-hooks.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module.exports = {
'prepare-commit-msg': `grep -qE '^[^#]' .git/COMMIT_EDITMSG || (exec < /dev/tty && yarn cz --hook || true)`,
'commit-msg': 'yarn commitlint --edit $1',
'prepare-commit-msg': `grep -qE '^[^#]' .git/COMMIT_EDITMSG || (exec < /dev/tty && pnpm cz --hook || true)`,
'commit-msg': 'pnpm commitlint --edit $1',
}
8 changes: 7 additions & 1 deletion src/interceptors/ClientRequest/MockHttpSocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,13 @@ export class MockHttpSocket extends MockSocket {

// Once the socket is finished, nothing can write to it
// anymore. It has also flushed any buffered chunks.
this.once('finish', () => this.requestParser.free())
this.once('finish', () => {
this.requestParser.free()
// @ts-ignore
this.parser = null
// @ts-ignore
this._httpMessage.parser = null
})

if (this.baseUrl.protocol === 'https:') {
Reflect.set(this, 'encrypted', true)
Expand Down
33 changes: 33 additions & 0 deletions test/modules/http/http-memory-worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const v8 = require('node:v8')
const path = require('node:path')
const http = require('node:http')
const { workerData, parentPort } = require('node:worker_threads')
const {
ClientRequestInterceptor,
} = require('../../../lib/interceptors/ClientRequest')

const interceptor = new ClientRequestInterceptor()
interceptor.apply()

const pendingRequests = []

for (let i = 0; i < workerData.requestCount; i++) {
pendingRequests.push(
new Promise((resolve, reject) => {
http
.get(workerData.serverUrl, (response) => {
response.on('data', () => {})
response.on('error', (error) => reject(error))
response.on('end', () => resolve())
})
.on('error', (error) => reject(error))
})
)
}

globalThis.gc?.()
v8.writeHeapSnapshot(workerData.snapshotPath)

Promise.allSettled(pendingRequests).then(() => {
parentPort.postMessage('complete')
})
50 changes: 50 additions & 0 deletions test/modules/http/http-memory.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// @vitest-environment node
import fs from 'node:fs'
import path from 'node:path'
import { Worker } from 'node:worker_threads'
import { HttpServer } from '@open-draft/test-server/http'
import { it, expect, beforeAll, afterAll } from 'vitest'
import { DeferredPromise } from '@open-draft/deferred-promise'

const server = new HttpServer((app) => {
app.get('/', (_req, res) => res.status(200).end())
})

beforeAll(async () => {
await server.listen()
})

afterAll(async () => {
await server.close()
})

it(
'does not retain the MockHttpSocket instance',
async () => {
const snapshotPath = path.resolve(__dirname, 'http-memory.heapsnapshot')

// Spawn the usage scenario in a worker so the test doesn't affect the heap.
const worker = new Worker(
path.resolve(__dirname, './http-memory-worker.js'),
{
workerData: {
requestCount: 10_000,
serverUrl: server.http.url('/'),
snapshotPath,
},
stderr: true,
stdout: true,
}
)

const completePromise = new DeferredPromise<void>()
worker.once('message', () => completePromise.resolve())
worker.on('error', (error) => completePromise.reject(error))

await completePromise

const snapshotStats = await fs.promises.stat(snapshotPath)
expect(snapshotStats.size / 1_000_000).toBeLessThanOrEqual(100)
},
{ timeout: 10_000 }
)