Skip to content

Commit 1a336f4

Browse files
I now feel 100% okay about this.
1 parent d2e97d8 commit 1a336f4

File tree

3 files changed

+280
-0
lines changed

3 files changed

+280
-0
lines changed
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
'use strict'
2+
3+
const spawn = require('child_process').spawn
4+
const Promise = require('bluebird')
5+
const domain = require('domain')
6+
const test = require('tap').test
7+
const pg = require('pg')
8+
9+
const db = require('../db-session.js')
10+
11+
const TEST_DB_NAME = process.env.TEST_DB_NAME || 'pg_db_session_test'
12+
13+
function setup () {
14+
return teardown().then(() => new Promise(resolve => {
15+
spawn('createdb', [TEST_DB_NAME]).on('exit', resolve)
16+
}))
17+
}
18+
19+
function teardown () {
20+
return new Promise(resolve => {
21+
spawn('dropdb', [TEST_DB_NAME]).on('exit', resolve)
22+
})
23+
}
24+
25+
test('setup', assert => setup().then(assert.end))
26+
27+
test('pg pooling does not adversely affect operation', assert => {
28+
const domain1 = domain.create()
29+
const domain2 = domain.create()
30+
31+
db.install(domain1, getConnection, {maxConcurrency: 0})
32+
db.install(domain2, getConnection, {maxConcurrency: 0})
33+
34+
const runOne = domain1.run(() => runOperation(domain1))
35+
.then(() => {
36+
domain1.exit()
37+
assert.ok(!process.domain)
38+
})
39+
40+
const runTwo = runOne.then(() => {
41+
return domain2.run(() => runOperation(domain2))
42+
}).then(() => {
43+
domain2.exit()
44+
assert.ok(!process.domain)
45+
})
46+
47+
return runTwo
48+
.catch(assert.fail)
49+
.finally(() => pg.end())
50+
.finally(assert.end)
51+
52+
function getConnection () {
53+
return new Promise((resolve, reject) => {
54+
pg.connect(`postgres://localhost/${TEST_DB_NAME}`, onconn)
55+
56+
function onconn (err, connection, release) {
57+
err ? reject(err) : resolve({connection, release})
58+
}
59+
})
60+
}
61+
62+
function runOperation (expectDomain) {
63+
assert.equal(process.domain, expectDomain)
64+
const getConnPair = db.getConnection()
65+
66+
const runSQL = getConnPair.get('connection').then(conn => {
67+
assert.equal(process.domain, expectDomain)
68+
return new Promise((resolve, reject) => {
69+
assert.equal(process.domain, expectDomain)
70+
conn.query('SELECT 1', (err, data) => {
71+
assert.equal(process.domain, expectDomain)
72+
err ? reject(err) : resolve(data)
73+
})
74+
})
75+
})
76+
77+
const runRelease = runSQL.return(getConnPair).then(
78+
pair => pair.release()
79+
)
80+
81+
return runRelease.return(runSQL)
82+
}
83+
})
84+
85+
test('teardown', assert => teardown().then(assert.end))

test/integrate-pummel-leak-test.js

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
'use strict'
2+
3+
const childProcess = require('child_process')
4+
const Promise = require('bluebird')
5+
const domain = require('domain')
6+
const spawn = childProcess.spawn
7+
const pg = require('pg')
8+
9+
const db = require('../db-session.js')
10+
11+
const TEST_DB_NAME = process.env.TEST_DB_NAME || 'pg_db_session_test'
12+
const IS_MAIN = !Boolean(process.env.TAP)
13+
14+
if (process.env.IS_CHILD) {
15+
runChild()
16+
} else {
17+
const test = require('tap').test
18+
test('setup', assert => setup().then(assert.end))
19+
test('pummel: make sure we are not leaking memory', runParent)
20+
test('teardown', assert => teardown().then(assert.end))
21+
}
22+
23+
function setup () {
24+
return teardown().then(() => new Promise(resolve => {
25+
spawn('createdb', [TEST_DB_NAME]).on('exit', resolve)
26+
}))
27+
}
28+
29+
function teardown () {
30+
return new Promise(resolve => {
31+
spawn('dropdb', [TEST_DB_NAME]).on('exit', resolve)
32+
})
33+
}
34+
35+
function runParent (assert) {
36+
const child = spawn(process.execPath, [
37+
'--expose_gc',
38+
'--max_old_space_size=32',
39+
__filename
40+
], {
41+
env: Object.assign({}, process.env, {
42+
IS_CHILD: 1,
43+
TEST_DB_NAME,
44+
BLUEBIRD_DEBUG: 0
45+
})
46+
})
47+
48+
if (IS_MAIN) {
49+
child.stderr.pipe(process.stderr)
50+
}
51+
const gotSignal = new Promise(resolve => {
52+
child.once('close', code => {
53+
resolve(code)
54+
})
55+
})
56+
57+
const checkCode = gotSignal.then(code => {
58+
assert.equal(code, 0)
59+
})
60+
61+
return checkCode
62+
.catch(err => assert.fail(err))
63+
.finally(assert.end)
64+
}
65+
66+
function runChild () {
67+
// if we leak domains, given a 32mb old space size we should crash in advance
68+
// of this number
69+
const ITERATIONS = 70000
70+
var count = 0
71+
var pending = 20
72+
73+
var resolve = null
74+
const doRun = new Promise(_resolve => resolve = _resolve)
75+
76+
function iter () {
77+
if (count % 1000 === 0) {
78+
process._rawDebug(count, process.memoryUsage())
79+
}
80+
if (++count < ITERATIONS) {
81+
return run().then(iter)
82+
}
83+
return !--pending && resolve()
84+
}
85+
86+
for (var i = 0; i < 20; ++i) {
87+
iter()
88+
}
89+
90+
return doRun
91+
.finally(() => pg.end())
92+
93+
function run () {
94+
const domain1 = domain.create()
95+
96+
db.install(domain1, getConnection, {maxConcurrency: 0})
97+
98+
return domain1.run(() => runOperation()).then(() => {
99+
domain1.exit()
100+
})
101+
}
102+
103+
function runOperation () {
104+
const getConnPair = db.getConnection()
105+
106+
const runSQL = getConnPair.get('connection').then(conn => {
107+
return new Promise((resolve, reject) => {
108+
conn.query('SELECT 1', (err, data) => {
109+
err ? reject(err) : resolve(data)
110+
})
111+
})
112+
})
113+
114+
const runRelease = runSQL.return(getConnPair).then(
115+
pair => pair.release()
116+
)
117+
118+
return runRelease.return(runSQL)
119+
}
120+
121+
function getConnection () {
122+
return new Promise((resolve, reject) => {
123+
pg.connect(`postgres://localhost/${TEST_DB_NAME}`, onconn)
124+
125+
function onconn (err, connection, release) {
126+
err ? reject(err) : resolve({connection, release})
127+
}
128+
})
129+
}
130+
}

test/integrate-server-test.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
'use strict'
2+
3+
const domain = require('domain')
4+
const test = require('tap').test
5+
const http = require('http')
6+
7+
const db = require('../db-session.js')
8+
9+
function runOperation () {
10+
return db.getConnection().then(pair => {
11+
pair.release()
12+
return 'ok!'
13+
})
14+
}
15+
16+
test('test requests do not leak domains into requester', assert => {
17+
const server = http.createServer((req, res) => {
18+
const domain1 = domain.create()
19+
db.install(domain1, getConnection, {maxConcurrency: 0})
20+
21+
domain1.add(req)
22+
domain1.add(res)
23+
24+
const result = domain1.run(() => {
25+
return runOperation()
26+
})
27+
28+
const removed = result.then(data => {
29+
domain1.remove(req)
30+
domain1.remove(res)
31+
})
32+
33+
return removed.return(result).then(data => {
34+
res.end(data)
35+
})
36+
})
37+
38+
server.listen(60808, () => {
39+
http.get('http://localhost:60808', res => {
40+
assert.ok(!process.domain)
41+
var acc = []
42+
res.on('data', data => {
43+
assert.ok(!process.domain)
44+
acc.push(data)
45+
})
46+
res.on('end', () => {
47+
assert.ok(!process.domain)
48+
server.close(() => {
49+
assert.end()
50+
})
51+
})
52+
})
53+
})
54+
55+
function getConnection () {
56+
return {
57+
connection: {query (sql, ready) {
58+
return ready()
59+
}},
60+
release () {
61+
}
62+
}
63+
}
64+
})
65+

0 commit comments

Comments
 (0)