Skip to content

Commit

Permalink
Sockets: Go rewrite
Browse files Browse the repository at this point in the history
Work in progress. I haven't written documentation for the Go code or
confirmed whether this works as intended on Windows yet.

This turned into a pretty substantial refactor of the Node version of
sockets-related code to not only to make using Go child processes
possible, but to optimize it and make unit testing of it and any code
dependent on it possible to write entirely synchronously. sockets.js
and sockets-workers.js are now written in Typescript, though they won't
be able to be transpiled until after Config, Users, Dnsbl, and Monitor
work with it as well.

Fixes #2943
  • Loading branch information
Morfent committed Sep 19, 2017
1 parent 5b9e1f3 commit 6a95158
Show file tree
Hide file tree
Showing 21 changed files with 2,696 additions and 730 deletions.
10 changes: 9 additions & 1 deletion config/config-example.js
Expand Up @@ -25,14 +25,22 @@ exports.workers = 1;
// TODO: allow SSL to actually be possible to use for third-party servers at
// some point.

// golang - toggle using Go instead of Node for sockets workers
// Node workers are more unstable at handling connections because of bugs in
// sockjs-node, but sending/receiving messages over connections on Go workers
// is slightly slower due to the extra work involved in performing IPC with
// them safely. This should be left set to false unless you know what you are
// doing.
exports.golang = false;

// proxyip - proxy IPs with trusted X-Forwarded-For headers
// This can be either false (meaning not to trust any proxies) or an array
// of strings. Each string should be either an IP address or a subnet given
// in CIDR notation. You should usually leave this as `false` unless you
// know what you are doing.
exports.proxyip = false;

// ofe - write heapdumps if sockets.js workers run out of memory.
// ofe - write heapdumps if Node sockets workers run out of memory
// If you wish to enable this, you will need to install ofe, as it is not a
// installed by default:
// $ npm install --no-save ofe
Expand Down
35 changes: 35 additions & 0 deletions dev-tools/sockets.js
@@ -0,0 +1,35 @@
'use strict';

const {Session, SockJSConnection} = require('sockjs/lib/transport');

const chars = 'abcdefghijklmnopqrstuvwxyz1234567890-';
let sessionidCount = 0;

/**
* @return string
*/
function generateSessionid() {
let ret = '';
let idx = sessionidCount;
for (let i = 0; i < 8; i++) {
ret = chars[idx % chars.length] + ret;
idx = idx / chars.length | 0;
}
sessionidCount++;
return ret;
}

/**
* @param {string} sessionid
* @param {{options: {{}}} config
* @return SockJSConnection
*/
exports.createSocket = function (sessionid = generateSessionid(), config = {options: {}}) {
let session = new Session(sessionid, config);
let socket = new SockJSConnection(session);
socket.remoteAddress = '127.0.0.1';
socket.protocol = 'websocket';
return socket;
};

// TODO: move worker mocks here, use require('../sockets-workers').Multiplexer to stub IPC
65 changes: 51 additions & 14 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions package.json
Expand Up @@ -47,10 +47,14 @@
"private": true,
"license": "MIT",
"devDependencies": {
"@types/cloud-env": "^0.2.0",
"@types/node": "^8.0.28",
"@types/node-static": "^0.7.0",
"@types/nodemailer": "^1.3.33",
"@types/ofe": "^0.5.0",
"@types/sockjs": "^0.3.31",
"eslint": "^4.0.0",
"mocha": "^3.0.0",
"@types/node": "^8.0.1",
"@types/nodemailer": "^1.3.33",
"typescript": "^2.5.0-dev.20170622"
}
}
87 changes: 86 additions & 1 deletion pokemon-showdown
Expand Up @@ -40,6 +40,91 @@ try {
}

if (!process.argv[2] || /^[0-9]+$/.test(process.argv[2])) {
// Check if the server is configured to use Go, and ensure the required
// environment variables and dependencies are available if that is the case

let config;
try {
config = require('./config/config');
} catch (e) {}

if (config && config.golang) {
// GOPATH and GOROOT are optional to a degree, but we need them in order
// to be able to handle Go dependencies. Since Go only cares about the
// first path in the list, so will we.
const GOPATH = child_process.execSync('go env GOPATH', {stdio: null, encoding: 'utf8'})
.trim()
.split(path.delimiter)[0]
.replace(/^"(.*)"$/, '$1');
if (!GOPATH) {
// Should never happen, but it does on Bash on Ubuntu on Windows.
console.error('There is no $GOPATH environment variable set. Run:');
console.error('$ go help GOPATH');
console.error('For more information on how to configure it.');
process.exit(0);
}

const dependencies = ['github.com/gorilla/mux', 'github.com/igm/sockjs-go/sockjs'];
let packages = child_process.execSync('go list all', {stdio: null, encoding: 'utf8'});
for (let dep of dependencies) {
if (!packages.includes(dep)) {
console.log(`Dependency ${dep} is not installed. Fetching...`);
child_process.execSync(`go get ${dep}`, {stdio: 'inherit'});
}
}

let stat;
let needsSrcDir = false;
try {
stat = fs.lstatSync(path.resolve(GOPATH, 'src/github.com/Zarel'));
} catch (e) {
needsSrcDir = true;
} finally {
if (stat && !stat.isDirectory()) {
needsSrcDir = true;
}
}

let srcPath = path.resolve(process.cwd(), 'sockets');
let tarPath = path.resolve(GOPATH, 'src/github.com/Zarel/Pokemon-Showdown/sockets');
if (needsSrcDir) {
try {
fs.mkdirSync(path.resolve(GOPATH, 'src/github.com/Zarel'));
fs.mkdirSync(path.resolve(GOPATH, 'src/github.com/Zarel/Pokemon-Showdown'));
} catch (e) {
console.error(e);
console.error(`Cannot make go source directory for the sockets library files! Symlink them manually from ${srcPath} to ${tarPath}`);
process.exit(0);
}
}

try {
stat = fs.lstatSync(path.resolve(GOPATH, 'src/github.com/Zarel/Pokemon-Showdown/sockets'));
} catch (e) {}

if (!stat || !stat.isSymbolicLink()) {
// Windows requires administrator privileges to make symlinks, so we
// make junctions instead. For our purposes they're compatible enough
// with symlinks on UNIX-like OSes.
let symlinkType = (process.platform === 'win32') ? 'junction' : 'dir';
try {
fs.symlinkSync(srcPath, tarPath, symlinkType);
} catch (e) {
console.error(`Cannot make go source directory for the sockets library files! Symlink them manually from ${srcPath} to ${tarPath}`);
process.exit(0);
}
}

console.log('Building Go source libs...');
try {
child_process.execSync('go install github.com/Zarel/Pokemon-Showdown/sockets', {stdio: 'inherit'});
} catch (e) {
// Go will show the errors that caused compiling Go's files to fail, so
// there's no reason to bother logging anything of our own.
process.exit(0);
}
}

// Start the server. We manually load app.js so it can be configured to run as
// the main module, rather than this file being considered the main module.
// This ensures any dependencies that were just installed can be found when
Expand All @@ -62,4 +147,4 @@ if (!process.argv[2] || /^[0-9]+$/.test(process.argv[2])) {
default:
console.error('Unrecognized command: ' + process.argv[2]);
process.exit(1);
}
}

0 comments on commit 6a95158

Please sign in to comment.