1'use strict'; 2const common = require('../common'); 3const assert = require('assert'); 4const fs = require('fs'); 5const path = require('path'); 6const cp = require('child_process'); 7const { spawnSync } = require('child_process'); 8 9// This is a sibling test to test/addons/uv-handle-leak. 10 11const bindingPath = path.resolve( 12 __dirname, '..', 'addons', 'uv-handle-leak', 'build', 13 `${common.buildType}/binding.node`); 14 15if (!fs.existsSync(bindingPath)) 16 common.skip('binding not built yet'); 17 18if (process.argv[2] === 'child') { 19 20 const { Worker } = require('worker_threads'); 21 22 // The worker thread loads and then unloads `bindingPath`. Because of this the 23 // symbols in `bindingPath` are lost when the worker thread quits, but the 24 // number of open handles in the worker thread's event loop is assessed in the 25 // main thread afterwards, and the names of the callbacks associated with the 26 // open handles is retrieved at that time as well. Thus, we require 27 // `bindingPath` here so that the symbols and their names survive the life 28 // cycle of the worker thread. 29 require(bindingPath); 30 31 new Worker(` 32 const binding = require(${JSON.stringify(bindingPath)}); 33 34 binding.leakHandle(); 35 binding.leakHandle(0); 36 binding.leakHandle(0x42); 37 `, { eval: true }); 38} else { 39 const child = cp.spawnSync(process.execPath, [__filename, 'child']); 40 const stderr = child.stderr.toString(); 41 42 assert.strictEqual(child.stdout.toString(), ''); 43 44 const lines = stderr.split('\n'); 45 46 let state = 'initial'; 47 48 // Parse output that is formatted like this: 49 50 // uv loop at [0x559b65ed5770] has open handles: 51 // [0x7f2de0018430] timer (active) 52 // Close callback: 0x7f2df31de220 CloseCallback(uv_handle_s*) [...] 53 // Data: 0x7f2df33df140 example_instance [...] 54 // (First field): 0x7f2df33dedc0 vtable for ExampleOwnerClass [...] 55 // [0x7f2de000b870] timer 56 // Close callback: 0x7f2df31de220 CloseCallback(uv_handle_s*) [...] 57 // Data: (nil) 58 // [0x7f2de000b910] timer 59 // Close callback: 0x7f2df31de220 CloseCallback(uv_handle_s*) [...] 60 // Data: 0x42 61 // uv loop at [0x559b65ed5770] has 3 open handles in total 62 63 function isGlibc() { 64 try { 65 const lddOut = spawnSync('ldd', [process.execPath]).stdout; 66 const libcInfo = lddOut.toString().split('\n').map( 67 (line) => line.match(/libc\.so.+=>\s*(\S+)\s/)).filter((info) => info); 68 if (libcInfo.length === 0) 69 return false; 70 const nmOut = spawnSync('nm', ['-D', libcInfo[0][1]]).stdout; 71 if (/gnu_get_libc_version/.test(nmOut)) 72 return true; 73 } catch { 74 return false; 75 } 76 } 77 78 79 if (!(common.isFreeBSD || 80 common.isAIX || 81 common.isIBMi || 82 (common.isLinux && !isGlibc()) || 83 common.isWindows)) { 84 assert(stderr.includes('ExampleOwnerClass'), stderr); 85 assert(stderr.includes('CloseCallback'), stderr); 86 assert(stderr.includes('example_instance'), stderr); 87 } 88 89 while (lines.length > 0) { 90 const line = lines.shift().trim(); 91 92 switch (state) { 93 case 'initial': 94 assert.match(line, /^uv loop at \[.+\] has open handles:$/); 95 state = 'handle-start'; 96 break; 97 case 'handle-start': 98 if (/^uv loop at \[.+\] has \d+ open handles in total$/.test(line)) { 99 state = 'assertion-failure'; 100 break; 101 } 102 assert.match(line, /^\[.+\] timer( \(active\))?$/); 103 state = 'close-callback'; 104 break; 105 case 'close-callback': 106 assert.match(line, /^Close callback:/); 107 state = 'data'; 108 break; 109 case 'data': 110 assert.match(line, /^Data: .+$/); 111 state = 'maybe-first-field'; 112 break; 113 case 'maybe-first-field': 114 if (!/^\(First field\)/.test(line)) { 115 lines.unshift(line); 116 } 117 state = 'handle-start'; 118 break; 119 case 'assertion-failure': 120 assert.match(line, /Assertion .+ failed/); 121 state = 'done'; 122 break; 123 case 'done': 124 break; 125 } 126 } 127 128 assert.strictEqual(state, 'done'); 129} 130