mirror of
https://github.com/unraid/api.git
synced 2026-01-12 19:50:07 -06:00
fix(module:app/server): fix EADDRINUSE
This commit is contained in:
222
app/server.js
222
app/server.js
@@ -9,8 +9,6 @@
|
||||
module.exports = function ($injector, path, fs, net, express, config, log, getEndpoints, stoppable, http) {
|
||||
const app = express();
|
||||
const port = config.get('graphql-api-port');
|
||||
let server;
|
||||
|
||||
const {ApolloServer} = $injector.resolve('apollo-server-express');
|
||||
const graphql = $injector.resolvePath(path.join(__dirname, '/graphql'));
|
||||
|
||||
@@ -34,69 +32,126 @@ module.exports = function ($injector, path, fs, net, express, config, log, getEn
|
||||
res.status(error.status || 500).send(error);
|
||||
});
|
||||
|
||||
// Return an object with start and stop methods.
|
||||
// Generate types and schema for core modules
|
||||
// {
|
||||
// const jsdocx = $injector.resolve('jsdoc-x');
|
||||
// const path = $injector.resolve('path');
|
||||
// const paths = $injector.resolve('paths');
|
||||
// const moduleDir = path.join(paths.get('core'), '/modules/');
|
||||
|
||||
// console.info('----------------------------')
|
||||
// console.info('Parsing core modules')
|
||||
// const docs = jsdocx.parse(`${moduleDir}/**/*.js`)
|
||||
// .then(docs => {
|
||||
// console.log('%s', JSON.stringify(docs, null, 0))
|
||||
// // const x = gql`
|
||||
// // type Disk {
|
||||
// // id: String!
|
||||
// // }
|
||||
// // `;
|
||||
// })
|
||||
// .catch(error => console.error(error.stack));
|
||||
// console.info('----------------------------')
|
||||
// }
|
||||
// (() => {
|
||||
// const documentedTypeDefs = docs
|
||||
// .filter(doc => !doc.undocumented)
|
||||
// .filter(doc => doc.kind === 'typedef')
|
||||
// .filter(doc => !doc.type.names.find(name => name.startsWith('Array')));
|
||||
|
||||
// documentedTypeDefs.map(doc => {
|
||||
// const props = doc.properties ? Object.values(doc.properties).map(prop => {
|
||||
// const desc = prop.description ? ('"""' + prop.description + '"""') : '';
|
||||
// const reservedWords = {
|
||||
// boolean: 'Boolean',
|
||||
// number: 'Number',
|
||||
// string: 'String'
|
||||
// };
|
||||
// const propType = prop.type.names[0];
|
||||
// const type = Object.keys(reservedWords).includes(propType) ? reservedWords[propType] : propType;
|
||||
|
||||
// if (doc.name === 'DeviceInfo') {
|
||||
// console.log({ doc });
|
||||
// }
|
||||
|
||||
// return `${desc}\n${prop.name}: ${prop.optional ? '[' : ''}${type || 'JSON'}${!prop.optional ? '!' : ']'}`;
|
||||
// }) : [];
|
||||
// const template = `
|
||||
// type ${doc.name} {
|
||||
// ${props.join('\n')}
|
||||
// }
|
||||
// `;
|
||||
|
||||
// return template;
|
||||
// })
|
||||
// .forEach(doc => console.info('%s', doc));
|
||||
// })()
|
||||
|
||||
const httpServer = http.createServer(app);
|
||||
const server = stoppable(httpServer);
|
||||
|
||||
const handleError = error => {
|
||||
if (error.code !== 'EADDRINUSE') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (!isNaN(parseInt(port, 10))) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
server.close();
|
||||
|
||||
net.connect({
|
||||
path: port
|
||||
}, () => {
|
||||
// Really in use: re-throw
|
||||
throw error;
|
||||
}).on('error', error => {
|
||||
if (error.code !== 'ECONNREFUSED') {
|
||||
log.error(error);
|
||||
|
||||
process.exitCode = 1;
|
||||
}
|
||||
|
||||
// Not in use: delete it and re-listen
|
||||
fs.unlinkSync(port);
|
||||
|
||||
setTimeout(() => {
|
||||
server.listen(port);
|
||||
}, 1000);
|
||||
});
|
||||
};
|
||||
|
||||
// Port is a UNIX socket file
|
||||
if (isNaN(parseInt(port, 10))) {
|
||||
server.on('listening', () => {
|
||||
// In production this will let pm2 know we're ready
|
||||
if (process.send) {
|
||||
process.send('ready');
|
||||
}
|
||||
|
||||
// Set permissions
|
||||
return fs.chmodSync(port, 660);
|
||||
});
|
||||
|
||||
server.on('error', handleError);
|
||||
|
||||
process.on('uncaughtException', error => {
|
||||
// Skip EADDRINUSE as it's already handled above
|
||||
if (error.code !== 'EADDRINUSE') {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Add graphql subscription handlers
|
||||
graphApp.installSubscriptionHandlers(server);
|
||||
|
||||
// Return an object with a server and start/stop async methods.
|
||||
return {
|
||||
server,
|
||||
async start() {
|
||||
// Generate types and schema for core modules
|
||||
// {
|
||||
// const jsdocx = $injector.resolve('jsdoc-x');
|
||||
// const path = $injector.resolve('path');
|
||||
// const paths = $injector.resolve('paths');
|
||||
// const moduleDir = path.join(paths.get('core'), '/modules/');
|
||||
|
||||
// console.info('----------------------------')
|
||||
// console.info('Parsing core modules')
|
||||
// const docs = jsdocx.parse(`${moduleDir}/**/*.js`)
|
||||
// .then(docs => {
|
||||
// console.log('%s', JSON.stringify(docs, null, 0))
|
||||
// // const x = gql`
|
||||
// // type Disk {
|
||||
// // id: String!
|
||||
// // }
|
||||
// // `;
|
||||
// })
|
||||
// .catch(error => console.error(error.stack));
|
||||
// console.info('----------------------------')
|
||||
// }
|
||||
// (() => {
|
||||
// const documentedTypeDefs = docs
|
||||
// .filter(doc => !doc.undocumented)
|
||||
// .filter(doc => doc.kind === 'typedef')
|
||||
// .filter(doc => !doc.type.names.find(name => name.startsWith('Array')));
|
||||
|
||||
// documentedTypeDefs.map(doc => {
|
||||
// const props = doc.properties ? Object.values(doc.properties).map(prop => {
|
||||
// const desc = prop.description ? ('"""' + prop.description + '"""') : '';
|
||||
// const reservedWords = {
|
||||
// boolean: 'Boolean',
|
||||
// number: 'Number',
|
||||
// string: 'String'
|
||||
// };
|
||||
// const propType = prop.type.names[0];
|
||||
// const type = Object.keys(reservedWords).includes(propType) ? reservedWords[propType] : propType;
|
||||
|
||||
// if (doc.name === 'DeviceInfo') {
|
||||
// console.log({ doc });
|
||||
// }
|
||||
|
||||
// return `${desc}\n${prop.name}: ${prop.optional ? '[' : ''}${type || 'JSON'}${!prop.optional ? '!' : ']'}`;
|
||||
// }) : [];
|
||||
// const template = `
|
||||
// type ${doc.name} {
|
||||
// ${props.join('\n')}
|
||||
// }
|
||||
// `;
|
||||
|
||||
// return template;
|
||||
// })
|
||||
// .forEach(doc => console.info('%s', doc));
|
||||
// })()
|
||||
|
||||
const httpServer = http.createServer(app);
|
||||
|
||||
graphApp.installSubscriptionHandlers(httpServer);
|
||||
|
||||
server = stoppable(httpServer).listen(port, () => {
|
||||
return server.listen(port, () => {
|
||||
// Downgrade process user to owner of this file
|
||||
return fs.stat(__filename, (error, stats) => {
|
||||
if (error) {
|
||||
@@ -106,43 +161,6 @@ module.exports = function ($injector, path, fs, net, express, config, log, getEn
|
||||
return process.setuid(stats.uid);
|
||||
});
|
||||
});
|
||||
|
||||
// Port is a UNIX socket file
|
||||
if (isNaN(parseInt(port, 10))) {
|
||||
server.on('listening', () => {
|
||||
// In production this will let pm2 know we're ready
|
||||
if (process.send) {
|
||||
process.send('ready');
|
||||
}
|
||||
|
||||
// Set permissions
|
||||
return fs.chmodSync(port, 660);
|
||||
});
|
||||
|
||||
// Double-check EADDRINUSE
|
||||
server.on('error', error => {
|
||||
if (error.code !== 'EADDRINUSE') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
net.connect({
|
||||
path: port
|
||||
}, () => {
|
||||
// Really in use: re-throw
|
||||
throw error;
|
||||
}).on('error', error => {
|
||||
if (error.code !== 'ECONNREFUSED') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Not in use: delete it and re-listen
|
||||
fs.unlinkSync(port);
|
||||
server.listen(port);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return server;
|
||||
},
|
||||
stop() {
|
||||
// Stop the server from accepting new connections and close existing connections
|
||||
@@ -154,7 +172,9 @@ module.exports = function ($injector, path, fs, net, express, config, log, getEn
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
log.info('Server shutting down..');
|
||||
const name = process.title;
|
||||
const serverName = `@unraid/${name}`;
|
||||
log.info(`Successfully stopped ${serverName}`);
|
||||
|
||||
// Gracefully exit
|
||||
process.exitCode = 0;
|
||||
|
||||
Reference in New Issue
Block a user