forked from Sobesednik/node-exiftool
-
Notifications
You must be signed in to change notification settings - Fork 0
/
detached-true.js
217 lines (195 loc) · 7.23 KB
/
detached-true.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
'use strict'
const makepromise = require('makepromise')
const ps = require('ps-node')
const assert = require('assert')
const killPid = require('../lib/kill-pid')
const exiftool = require('../../src/')
const context = require('../context/detached')
context.globalExiftoolConstructor = exiftool.ExiftoolProcess
const isWindows = process.platform === 'win32'
const checkPid = pid => makepromise(ps.lookup, { pid })
const checkPpid = ppid => makepromise(ps.lookup, { ppid })
/**
* @typedef {Object} PSRes
* @property {string} pid
* @property {string} command
* @property {string[]} arguments
* @property {string} ppid
*/
/**
* @typedef {Object} CheckPidsRes
* @property {PSRes} [fork]
* @property {PSRes} [ep]
* @property {PSRes} [epChild]
* @property {PSRes} [conhost]
*/
/**
* @typedef {function(): Promise.<CheckPidsRes>} CheckPidsFn
*/
/**
* Create checkPids function.
* @param {string|number} forkPid pid of Node fork
* @param {string|number} epPid exiftool pid
* @param {string|number} [epChildPid] on windows, child ep pid
* @param {string|number} [conhostPid] on windows, child conhost pid
* @returns {CheckPidsFn} Function to check pids with ps-node
*/
function createCheckPids(forkPid, epPid, epChildPid, conhostPid) {
assert(forkPid)
assert(epPid)
const arr = [forkPid, epPid]
if (epChildPid) arr.push(epChildPid)
if (conhostPid) arr.push(conhostPid)
const checkPids = () => checkPid(arr)
.then((res) => {
const fork = res.find(r => r.pid === `${forkPid}`)
const ep = res.find(r => r.pid === `${epPid}`)
const epChild = epChildPid ? res.find(r => r.pid === `${epChildPid}`) : undefined
const conhost = conhostPid ? res.find(r => r.pid === `${conhostPid}`) : undefined
const o = {
fork,
ep,
epChild,
conhost,
}
// filter out undefined
const fo = Object.keys(o).reduce((acc, key) => {
const value = o[key]
if (value === undefined) {
return acc
}
return Object.assign({}, acc, {
[key]: value,
})
}, {})
return fo
})
return checkPids
}
const findExiftoolChildAndConhost = (epPid, exiftoolDetached) => {
if (!isWindows) {
return Promise.reject(new Error('This function is only available on Windows'))
}
return checkPpid(epPid)
.then((res) => {
// if not detached, conhost is child of parent exiftool,
// which is already found above
if (!exiftoolDetached) return res
// if detached, conhost is child of child exiftool,
// which we are now finding
const childExiftool = res[0]
assert(childExiftool)
assert(/exiftool\.exe/.test(childExiftool.command))
return checkPpid(childExiftool.pid).then((conhostRes) => {
const conhost = conhostRes[0]
assert(conhost)
assert(/conhost.exe/.test(conhost.command))
const all = [].concat(conhostRes, res)
return all
})
})
.then((res) => {
assert.equal(res.length, 2)
const conhost = res.find(p => /conhost\.exe/.test(p.command))
assert(conhost, 'conhost.exe should have been started as child of exiftool')
const conhostPid = conhost.pid
const epChild = res.find(p => /exiftool\.exe/.test(p.command))
assert(epChild, 'exiftool.exe should have been started as child of exiftool')
const epChildPid = epChild.pid
return { conhostPid, epChildPid }
})
}
/**
* Fork a node process with a module which will spawn exiftool. Because of the
* way exiftool works on Windows, it will spawn an extra process itself.
* @param {boolean} exiftoolDetached Whether to start exiftool in detached mode
* @returns {CheckPidsFn} A scoped function to check pids
*/
const setup = (exiftoolDetached, ctx) => {
let checkPids
return ctx.forkNode(exiftoolDetached)
.then((meta) => {
const forkPid = meta.forkPid
const epPid = meta.epPid
const res = { forkPid, epPid }
if (isWindows) {
return findExiftoolChildAndConhost(epPid, exiftoolDetached)
.then(r => Object.assign(res, r))
}
return res
})
.then((res) => {
checkPids = createCheckPids(res.forkPid, res.epPid, res.epChildPid, res.conhostPid)
return checkPids()
})
.then((res) => {
assert(res.fork)
assert(res.ep)
if (isWindows) {
assert(res.epChild)
assert(res.conhost)
}
return checkPids
})
}
const createTestWin = (detached) => {
const test = (ctx) => {
let checkPids
return setup(detached, ctx)
.then((res) => {
checkPids = res
return ctx.killFork().then(checkPids)
})
.then((res) => {
assert(!res.fork, 'Node fork should have quit')
assert(res.epChild, 'Exiftool child should stay open')
assert(res.conhost, 'conhost should stay open')
if (detached) {
assert(res.ep, 'Exiftool parent should stay open')
} else {
assert(!res.ep, 'Exiftool parent should have quit')
}
// cleanup by killing child exiftool, this should kill the whole tree
return killPid(res.epChild.pid)
})
}
return test
}
const createTest = (detached) => {
const test = (ctx) => {
let checkPids
return setup(detached, ctx)
.then((res) => {
checkPids = res
return ctx.killFork(true).then(checkPids)
})
.then((res) => {
assert(!res.fork, 'Node fork should have quit')
if (detached) {
assert(res.ep, 'Exiftool parent should stay open')
return killPid(res.ep.pid)
} else {
assert(!res.ep, 'Exiftool parent should have quit')
}
})
}
return test
}
const DetachedTrueTestSuite = {}
if (isWindows) {
Object.assign(DetachedTrueTestSuite, {
context,
'should quit child process when fork exits without detached option (win)': createTestWin(),
'should not quit child process when fork exits with detached option (win)': createTestWin(true),
})
} else {
Object.assign(DetachedTrueTestSuite, {
context,
// Also note: on Linux, child processes of child processes will not be terminated when attempting to kill their parent.
// kill detached fork by passing -pid (i.e., pgid)
// https://nodejs.org/api/child_process.html#child_process_subprocess_kill_signal
'should quit child process when fork exits without detached option': createTest(),
'should not quit child process when fork exits with detached option': createTest(true),
})
}
module.exports = DetachedTrueTestSuite