Skip to content

Commit 123b17e

Browse files
committed
[DDW-1081] Really find child processes on Linux – LC_ALL interference
1 parent d283251 commit 123b17e

File tree

3 files changed

+95
-26
lines changed

3 files changed

+95
-26
lines changed

source/main/cardano/CardanoNode.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ export class CardanoNode {
324324
path: this._config.logFilePath,
325325
maxFiles: 4,
326326
}
327-
)
327+
);
328328
};
329329

330330
const nodeLogFile = mkLogFile('node.log');
@@ -334,7 +334,9 @@ export class CardanoNode {
334334

335335
if (isSelfnode) {
336336
try {
337-
const mockTokenMetadataServerLogFile = mkLogFile('mock-token-metadata-server.log');
337+
const mockTokenMetadataServerLogFile = mkLogFile(
338+
'mock-token-metadata-server.log'
339+
);
338340
this._mockTokenMetadataServerLogFile = mockTokenMetadataServerLogFile;
339341

340342
const { selfnodeBin, mockTokenMetadataServerBin } = launcherConfig;
@@ -859,7 +861,8 @@ export class CardanoNode {
859861
_reset = () => {
860862
if (this._cardanoNodeLogFile) this._cardanoNodeLogFile.end();
861863
if (this._cardanoWalletLogFile) this._cardanoWalletLogFile.end();
862-
if (this._mockTokenMetadataServerLogFile) this._mockTokenMetadataServerLogFile.end();
864+
if (this._mockTokenMetadataServerLogFile)
865+
this._mockTokenMetadataServerLogFile.end();
863866
if (this._node) this._node = null;
864867
this._tlsConfig = null;
865868
};

source/main/cardano/CardanoSelfnodeLauncher.ts

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { spawn } from 'child_process';
22
import find from 'find-process';
33
import path from 'path';
4-
import fs, {WriteStream} from 'fs';
4+
import fs, { WriteStream } from 'fs';
55
import which from 'which';
66
import tcpPortUsed from 'tcp-port-used';
77
import type { ChildProcess } from 'child_process';
@@ -33,10 +33,9 @@ const CARDANO_WALLET_START_CHECK_INTERVAL = 500; // 500 ms | unit: milliseconds
3333

3434
const TOKEN_METADATA_SERVER_PORT = 65432;
3535
const TOKEN_METADATA_SERVER = `http://localhost:${TOKEN_METADATA_SERVER_PORT}/`;
36-
const TOKEN_METADATA_SERVER_PROCESS_NAME =
37-
environment.isWindows
38-
? 'mock-token-metadata-server.exe'
39-
: 'mock-token-metadata-server';
36+
const TOKEN_METADATA_SERVER_PROCESS_NAME = environment.isWindows
37+
? 'mock-token-metadata-server.exe'
38+
: 'mock-token-metadata-server';
4039
export async function CardanoSelfnodeLauncher(
4140
selfnodeOptions: SelfnodeOptions
4241
): Promise<{
@@ -57,13 +56,18 @@ export async function CardanoSelfnodeLauncher(
5756
const binDir = path.dirname(which.sync(selfnodeBin));
5857
return firstExisting('SHELLEY_TEST_DATA', [
5958
path.resolve(path.join(binDir, 'data', 'cardano-node-shelley')), // Windows installer
60-
path.resolve(path.join(binDir, '..', 'Resources', 'data', 'cardano-node-shelley')), // Darwin installer
59+
path.resolve(
60+
path.join(binDir, '..', 'Resources', 'data', 'cardano-node-shelley')
61+
), // Darwin installer
6162
// Linux installer substitutes SHELLEY_TEST_DATA in the local-cluster Bash wrapper
62-
'../../utils/cardano/selfnode' // nix-shell? but nix-shell has the substitute ↑ as well… Some other scenario?
63-
])
63+
'../../utils/cardano/selfnode', // nix-shell? but nix-shell has the substitute ↑ as well… Some other scenario?
64+
]);
6465
})();
6566

66-
setupMockTokenMetadataServer(mockTokenMetadataServerBin, mockTokenMetadataServerLogFile);
67+
setupMockTokenMetadataServer(
68+
mockTokenMetadataServerBin,
69+
mockTokenMetadataServerLogFile
70+
);
6771
// @ts-ignore ts-migrate(2322) FIXME: Type '{ pid: number; ppid?: number; uid?: number; ... Remove this comment to see the full error message
6872
const processList: Array<Process> = await find('port', CARDANO_WALLET_PORT);
6973
const isSelfnodeRunning =
@@ -113,11 +117,16 @@ export async function CardanoSelfnodeLauncher(
113117
if (environment.isDev) {
114118
nodeProcess.unref(); // allows Daedalus to exit independently of selfnode (3/3)
115119
}
116-
if (!environment.isDev && nodeProcess.stdout && nodeProcess.stderr && walletLogFile) {
117-
nodeProcess.stdout.on('data', data => {
120+
if (
121+
!environment.isDev &&
122+
nodeProcess.stdout &&
123+
nodeProcess.stderr &&
124+
walletLogFile
125+
) {
126+
nodeProcess.stdout.on('data', (data) => {
118127
walletLogFile.write(data);
119128
});
120-
nodeProcess.stderr.on('data', data => {
129+
nodeProcess.stderr.on('data', (data) => {
121130
walletLogFile.write(data);
122131
});
123132
}
@@ -151,11 +160,18 @@ export async function CardanoSelfnodeLauncher(
151160
/**
152161
* Return the first existing file path among candidates. If none exist, return the last one, and log a warning an identifier.
153162
*/
154-
const firstExisting = (identifier: string, candidates: Array<string>): string => {
163+
const firstExisting = (
164+
identifier: string,
165+
candidates: Array<string>
166+
): string => {
155167
const existing = candidates.filter(fs.existsSync);
156168
if (existing.length > 0) return existing[0];
157169
const fallback = candidates[candidates.length - 1];
158-
logger.warn(`${identifier} candidates don’t exist, will use fallback`, { identifier, candidates, fallback });
170+
logger.warn(`${identifier} candidates don’t exist, will use fallback`, {
171+
identifier,
172+
candidates,
173+
fallback,
174+
});
159175
return fallback;
160176
};
161177

@@ -195,7 +211,7 @@ const setupMockTokenMetadataServer = async (
195211
return firstExisting('TOKEN_METADATA_REGISTRY', [
196212
path.resolve(path.join(binDir, 'token-metadata.json')), // Windows and Linux installers
197213
path.resolve(path.join(binDir, '..', 'Resources', 'token-metadata.json')), // Darwin installer
198-
'./utils/cardano/selfnode/token-metadata.json' // nix-shell
214+
'./utils/cardano/selfnode/token-metadata.json', // nix-shell
199215
]);
200216
})();
201217

@@ -239,12 +255,17 @@ const setupMockTokenMetadataServer = async (
239255
if (environment.isDev) {
240256
mockTokenMetadataServerProcess.unref();
241257
}
242-
if (!environment.isDev && mockTokenMetadataServerProcess.stdout && mockTokenMetadataServerProcess.stderr && mockTokenMetadataServerLogFile) {
243-
mockTokenMetadataServerProcess.stdout.on('data', data => {
258+
if (
259+
!environment.isDev &&
260+
mockTokenMetadataServerProcess.stdout &&
261+
mockTokenMetadataServerProcess.stderr &&
262+
mockTokenMetadataServerLogFile
263+
) {
264+
mockTokenMetadataServerProcess.stdout.on('data', (data) => {
244265
mockTokenMetadataServerLogFile.write(data);
245266
});
246-
mockTokenMetadataServerProcess.stderr.on('data', data => {
247-
mockTokenMetadataServerLogFile.write(data)
267+
mockTokenMetadataServerProcess.stderr.on('data', (data) => {
268+
mockTokenMetadataServerLogFile.write(data);
248269
});
249270
}
250271
mockTokenMetadataServer = mockTokenMetadataServerProcess;

source/main/utils/processes.ts

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export type Process = {
1414
export const getProcessById = async (processId: number): Promise<Process> => {
1515
// finds running processes matching PID
1616
// @ts-ignore ts-migrate(2322) FIXME: Type '{ pid: number; ppid?: number; uid?: number; ... Remove this comment to see the full error message
17-
const matchingProcesses: Array<Process> = await find('pid', processId);
17+
const matchingProcesses: Array<Process> = await wrappedFind('pid', processId);
1818
return matchingProcesses.length > 0 ? matchingProcesses[0] : Promise.reject();
1919
};
2020
export const getProcessName = async (processId: number) =>
@@ -24,7 +24,10 @@ export const getProcessesByName = async (
2424
): Promise<Array<Process>> => {
2525
// finds running processes matching name
2626
// @ts-ignore ts-migrate(2322) FIXME: Type '{ pid: number; ppid?: number; uid?: number; ... Remove this comment to see the full error message
27-
const matchingProcesses: Array<Process> = await find('name', processName);
27+
const matchingProcesses: Array<Process> = await wrappedFind(
28+
'name',
29+
processName
30+
);
2831
return matchingProcesses;
2932
};
3033
export const getProcess = async (
@@ -34,15 +37,21 @@ export const getProcess = async (
3437
try {
3538
// finds running processes matching PID
3639
// @ts-ignore ts-migrate(2322) FIXME: Type '{ pid: number; ppid?: number; uid?: number; ... Remove this comment to see the full error message
37-
const matchingProcesses: Array<Process> = await find('pid', processId);
40+
const matchingProcesses: Array<Process> = await wrappedFind(
41+
'pid',
42+
processId
43+
);
3844
// no processes exist with a matching PID
3945
if (!matchingProcesses.length) return null;
4046
// Return first matching process if names match
4147
const previousProcess: Process = matchingProcesses[0];
4248

4349
if (isObject(previousProcess)) {
4450
const binPathSegments = previousProcess.bin.split(path.sep);
45-
const exeName = binPathSegments.length > 0 ? binPathSegments[binPathSegments.length - 1] : previousProcess.bin;
51+
const exeName =
52+
binPathSegments.length > 0
53+
? binPathSegments[binPathSegments.length - 1]
54+
: previousProcess.bin;
4655
if (exeName === processName || previousProcess.name === processName) {
4756
return previousProcess;
4857
}
@@ -54,3 +63,39 @@ export const getProcess = async (
5463
return null;
5564
}
5665
};
66+
67+
/**
68+
* Like `find from 'find-process'`, but wraps the execution into setting locale to C (available anywhere).
69+
*
70+
* Inside Nix chroot for Linux installation, it is possible there will not be locale definitions for user’s LC_ALL,
71+
* which variable is inherited by the chroot from user’s regular system.
72+
*
73+
* Unfortunately, `/bin/sh` inside the Nix chroot outputs warnings to `stderr` about missing locales then.
74+
*
75+
* Then, `find-process` interprets non-empty stderr as some error, and throws.
76+
*
77+
* Another solution would be to patch `find-process` to construct the `ps` invocation itself, not using `/bin/sh`
78+
* for that (i.e. `exec('ps', 'aux')` instead of `exec('ps aux')` which is really `exec('/bin/sh', '-c', 'ps aux')`.
79+
*/
80+
const wrappedFind = async (
81+
type: 'name' | 'pid' | 'port',
82+
value: string | number | RegExp,
83+
strict?: boolean
84+
): Promise<
85+
{
86+
pid: number;
87+
ppid?: number;
88+
uid?: number;
89+
gid?: number;
90+
name: string;
91+
cmd: string;
92+
}[]
93+
> => {
94+
const previousLocale = process.env.LC_ALL;
95+
process.env.LC_ALL = 'C';
96+
const rv = await find(type, value, strict);
97+
if (typeof previousLocale !== 'undefined')
98+
process.env.LC_ALL = previousLocale;
99+
else delete process.env.LC_ALL;
100+
return rv;
101+
};

0 commit comments

Comments
 (0)