Delete SocketInterface code

Reviewed By: davidaurelio

Differential Revision: D3152105

fb-gh-sync-id: a6c13bb54c2164ebc063a1b14f00114738546c8c
fbshipit-source-id: a6c13bb54c2164ebc063a1b14f00114738546c8c
This commit is contained in:
Sam Swarr 2016-04-11 14:36:57 -07:00 committed by Facebook Github Bot 3
parent 1d9df442a4
commit 43c5ed2be0
7 changed files with 0 additions and 864 deletions

View File

@ -73,17 +73,6 @@ exports.getOrderedDependencyPaths = function(options, bundleOptions) {
});
};
exports.createClientFor = function(options) {
if (options.verbose) {
enableDebug();
}
startSocketInterface();
return (
require('./src/SocketInterface')
.getOrCreateSocketFor(omit(options, ['verbose']))
);
};
function useGracefulFs() {
var fs = require('fs');
var gracefulFs = require('graceful-fs');
@ -111,7 +100,6 @@ function createServer(options) {
enableDebug();
}
startSocketInterface();
var Server = require('./src/Server');
return new Server(omit(options, ['verbose']));
}
@ -135,19 +123,3 @@ function omit(obj, blacklistedKeys) {
return clone;
}, {});
}
// we need to listen on a socket as soon as a server is created, but only once.
// This file also serves as entry point when spawning a socket server; in that
// case we need to start the server immediately.
var didStartSocketInterface = false;
function startSocketInterface() {
if (didStartSocketInterface) {
return;
}
didStartSocketInterface = true;
require('./src/SocketInterface').listenOnServerMessages();
}
if (require.main === module) { // used as entry point
startSocketInterface();
}

View File

@ -1,170 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
const Bundle = require('../Bundler/Bundle');
const PrepackBundle = require('../Bundler/PrepackBundle');
const Promise = require('promise');
const bser = require('bser');
const debug = require('debug')('ReactNativePackager:SocketClient');
const fs = require('fs');
const net = require('net');
const path = require('path');
const tmpdir = require('os').tmpdir();
const LOG_PATH = path.join(tmpdir, 'react-packager.log');
class SocketClient {
static create(sockPath) {
return new SocketClient(sockPath).onReady();
}
constructor(sockPath) {
debug('connecting to', sockPath);
this._sock = net.connect(sockPath);
this._ready = new Promise((resolve, reject) => {
this._sock.on('connect', () => {
this._sock.removeAllListeners('error');
process.on('uncaughtException', (error) => {
console.error('uncaught error', error.stack);
setImmediate(() => process.exit(1));
});
resolve(this);
});
this._sock.on('error', (e) => {
e.message = `Error connecting to server on ${sockPath} ` +
`with error: ${e.message}`;
e.message += getServerLogs();
reject(e);
});
});
this._resolvers = Object.create(null);
const bunser = new bser.BunserBuf();
this._sock.on('data', (buf) => bunser.append(buf));
bunser.on('value', (message) => this._handleMessage(message));
this._sock.on('close', () => {
if (!this._closing) {
const terminate = (result) => {
const sockPathExists = fs.existsSync(sockPath);
throw new Error(
'Server closed unexpectedly.\n' +
'Server ping connection attempt result: ' + result + '\n' +
'Socket path: `' + sockPath + '` ' +
(sockPathExists ? ' exists.' : 'doesn\'t exist') + '\n' +
getServerLogs()
);
};
// before throwing ping the server to see if it's still alive
const socket = net.connect(sockPath);
socket.on('connect', () => {
socket.end();
terminate('OK');
});
socket.on('error', error => terminate(error));
}
});
}
onReady() {
return this._ready;
}
getDependencies(main) {
return this._send({
type: 'getDependencies',
data: main,
});
}
getOrderedDependencyPaths(main) {
return this._send({
type: 'getOrderedDependencyPaths',
data: main,
});
}
buildBundle(options) {
return this._send({
type: 'buildBundle',
data: options,
}).then(json => Bundle.fromJSON(json));
}
buildPrepackBundle(options) {
return this._send({
type: 'buildPrepackBundle',
data: options,
}).then(json => PrepackBundle.fromJSON(json));
}
_send(message) {
message.id = uid();
this._sock.write(bser.dumpToBuffer(message));
return new Promise((resolve, reject) => {
this._resolvers[message.id] = {resolve, reject};
});
}
_handleMessage(message) {
if (!(message && message.id && message.type)) {
throw new Error(
'Malformed message from server ' + JSON.stringify(message)
);
}
debug('got message with type', message.type);
const resolver = this._resolvers[message.id];
if (!resolver) {
throw new Error(
'Unrecognized message id (' + message.id + ') ' +
'message already resolved or never existed.'
);
}
delete this._resolvers[message.id];
if (message.type === 'error') {
const errorLog =
message.data && message.data.indexOf('TimeoutError') === -1
? 'See logs ' + LOG_PATH
: getServerLogs();
resolver.reject(new Error(message.data + '\n' + errorLog));
} else {
resolver.resolve(message.data);
}
}
close() {
debug('closing connection');
this._closing = true;
this._sock.end();
}
}
module.exports = SocketClient;
function uid(len) {
len = len || 7;
return Math.random().toString(35).substr(2, len);
}
function getServerLogs() {
if (fs.existsSync(LOG_PATH)) {
return '\nServer logs:\n' + fs.readFileSync(LOG_PATH, 'utf8');
}
return '';
}

View File

@ -1,224 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
const Promise = require('promise');
const Server = require('../Server');
const bser = require('bser');
const debug = require('debug')('ReactNativePackager:SocketServer');
const fs = require('fs');
const net = require('net');
const MAX_IDLE_TIME = 30 * 1000;
const MAX_STARTUP_TIME = 5 * 60 * 1000;
class SocketServer {
constructor(sockPath, options) {
this._server = net.createServer();
this._server.listen(sockPath);
this._ready = new Promise((resolve, reject) => {
this._server.once('error', (e) => reject(e));
this._server.once('listening', () => {
// Remove error listener so we make sure errors propagate.
this._server.removeAllListeners('error');
this._server.on(
'close',
() => debug('server closed')
);
debug(
'Process %d listening on socket path %s ' +
'for server with options %j',
process.pid,
sockPath,
options
);
resolve(this);
process.on('exit', code => {
debug('exit code:', code);
fs.unlinkSync(sockPath);
});
});
});
process.on('uncaughtException', (error) => {
debug('uncaught error', error.stack);
setImmediate(() => process.exit(1));
});
this._server.on('connection', (sock) => this._handleConnection(sock));
// Disable the file watcher.
options.nonPersistent = true;
this._packagerServer = new Server(options);
this._dieEventually(MAX_STARTUP_TIME);
}
onReady() {
return this._ready;
}
_handleConnection(sock) {
debug('connection to server', process.pid);
const bunser = new bser.BunserBuf();
sock.on('data', (buf) => bunser.append(buf));
bunser.on('value', (m) => this._handleMessage(sock, m));
bunser.on('error', (e) => {
e.message = 'Unhandled error from the bser buffer. ' +
'Either error on encoding or message handling: \n' +
e.message;
throw e;
});
}
_handleMessage(sock, m) {
if (!m || !m.id || !m.data) {
console.error('SocketServer recieved a malformed message: %j', m);
return;
}
debug('got request', m);
// Debounce the kill timer.
this._dieEventually();
const handleError = (error) => {
debug('request error', error);
this._reply(sock, m.id, 'error', error.stack);
// Fatal error from JSTransformer transform workers.
if (error.type === 'ProcessTerminatedError') {
setImmediate(() => process.exit(1));
}
};
switch (m.type) {
case 'getDependencies':
this._packagerServer.getDependencies(m.data).then(
({ dependencies }) => this._reply(sock, m.id, 'result', dependencies),
handleError,
);
break;
case 'buildBundle':
this._packagerServer.buildBundle(m.data).then(
(result) => this._reply(sock, m.id, 'result', result),
handleError,
);
break;
case 'buildPrepackBundle':
this._packagerServer.buildPrepackBundle(m.data).then(
(result) => this._reply(sock, m.id, 'result', result),
handleError,
);
break;
case 'getOrderedDependencyPaths':
this._packagerServer.getOrderedDependencyPaths(m.data).then(
(dependencies) => this._reply(sock, m.id, 'result', dependencies),
handleError,
);
break;
default:
this._reply(sock, m.id, 'error', 'Unknown message type: ' + m.type);
}
}
_reply(sock, id, type, data) {
debug('request finished', type);
try {
data = toJSON(data);
} catch (e) {
console.error('SocketServer exception:', e);
}
sock.write(bser.dumpToBuffer({
id,
type,
data,
}));
// Debounce the kill timer to make sure all the bytes are sent through
// the socket and the client has time to fully finish and disconnect.
this._dieEventually();
}
_dieEventually(delay = MAX_IDLE_TIME) {
clearTimeout(this._deathTimer);
this._deathTimer = setTimeout(() => {
this._server.getConnections((error, numConnections) => {
// error is passed when connection count is below 0
if (error || numConnections <= 0) {
debug('server dying', process.pid);
process.exit();
}
this._dieEventually();
});
}, delay);
}
static listenOnServerIPCMessages() {
process.on('message', (message) => {
if (!(message && message.type && message.type === 'createSocketServer')) {
return;
}
debug('server got ipc message', message);
const {options, sockPath} = message.data;
// regexp doesn't naturally serialize to json.
options.blacklistRE = new RegExp(options.blacklistRE.source);
new SocketServer(sockPath, options).onReady().then(
() => {
debug('succesfully created server');
process.send({ type: 'createdServer' });
},
error => {
if (error.code === 'EADDRINUSE' || error.code === 'EEXIST') {
// Server already listening, this may happen if multiple
// clients where started in quick succussion (buck).
process.send({ type: 'createdServer' });
// Kill this server because some other server with the same
// config and socket already started.
debug('server already started');
setImmediate(() => process.exit());
} else {
debug('error creating server', error.code);
throw error;
}
}
).done();
});
}
}
// TODO move this to bser code.
function toJSON(object) {
if (!(object && typeof object === 'object')) {
return object;
}
if (object.toJSON) {
return object.toJSON();
}
for (var p in object) {
object[p] = toJSON(object[p]);
}
return object;
}
module.exports = SocketServer;

View File

@ -1,113 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
jest.setMock('worker-farm', function() { return () => {}; })
.setMock('uglify-js')
.mock('net')
.dontMock('../SocketClient');
var Bundle = require('../../Bundler/Bundle');
var SocketClient = require('../SocketClient');
var bser = require('bser');
var net = require('net');
describe('SocketClient', () => {
let sock;
let bunser;
beforeEach(() => {
const {EventEmitter} = require.requireActual('events');
sock = new EventEmitter();
sock.write = jest.genMockFn();
net.connect.mockImpl(() => sock);
bunser = new EventEmitter();
bser.BunserBuf.mockImpl(() => bunser);
bser.dumpToBuffer.mockImpl((a) => a);
Bundle.fromJSON.mockImpl((a) => a);
});
pit('create a connection', () => {
const client = new SocketClient('/sock');
sock.emit('connect');
return client.onReady().then(c => {
expect(c).toBe(client);
expect(net.connect).toBeCalledWith('/sock');
});
});
pit('buildBundle', () => {
const client = new SocketClient('/sock');
sock.emit('connect');
const options = { entryFile: '/main' };
const promise = client.buildBundle(options);
expect(sock.write).toBeCalled();
const message = sock.write.mock.calls[0][0];
expect(message.type).toBe('buildBundle');
expect(message.data).toEqual(options);
expect(typeof message.id).toBe('string');
bunser.emit('value', {
id: message.id,
type: 'result',
data: { bundle: 'foo' },
});
return promise.then(bundle => expect(bundle).toEqual({ bundle: 'foo' }));
});
pit('getDependencies', () => {
const client = new SocketClient('/sock');
sock.emit('connect');
const main = '/main';
const promise = client.getDependencies(main);
expect(sock.write).toBeCalled();
const message = sock.write.mock.calls[0][0];
expect(message.type).toBe('getDependencies');
expect(message.data).toEqual(main);
expect(typeof message.id).toBe('string');
bunser.emit('value', {
id: message.id,
type: 'result',
data: ['a', 'b', 'c'],
});
return promise.then(result => expect(result).toEqual(['a', 'b', 'c']));
});
pit('handle errors', () => {
const client = new SocketClient('/sock');
sock.emit('connect');
const main = '/main';
const promise = client.getDependencies(main);
expect(sock.write).toBeCalled();
const message = sock.write.mock.calls[0][0];
expect(message.type).toBe('getDependencies');
expect(message.data).toEqual(main);
expect(typeof message.id).toBe('string');
bunser.emit('value', {
id: message.id,
type: 'error',
data: 'some error'
});
return promise.catch(m => expect(m.message).toContain('some error'));
});
});

View File

@ -1,81 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
jest.setMock('uglify-js')
.mock('child_process')
.dontMock('../');
var SocketInterface = require('../');
var SocketClient = require('../SocketClient');
var childProcess = require('child_process');
var fs = require('fs');
describe('SocketInterface', () => {
describe('getOrCreateSocketFor', () => {
pit('creates socket path by hashing options', () => {
fs.existsSync = jest.genMockFn().mockImpl(() => true);
fs.unlinkSync = jest.genMockFn();
let callback;
childProcess.spawn.mockImpl(() => ({
on: (event, cb) => callback = cb,
send: (message) => {
setImmediate(() => callback({ type: 'createdServer' }));
},
unref: () => undefined,
disconnect: () => undefined,
}));
// Check that given two equivelant server options, we end up with the same
// socket path.
const options1 = { projectRoots: ['/root'], transformModulePath: '/root/foo' };
const options2 = { transformModulePath: '/root/foo', projectRoots: ['/root'] };
const options3 = { projectRoots: ['/root', '/root2'] };
return SocketInterface.getOrCreateSocketFor(options1).then(() => {
expect(SocketClient.create).toBeCalled();
return SocketInterface.getOrCreateSocketFor(options2).then(() => {
expect(SocketClient.create.mock.calls.length).toBe(2);
expect(SocketClient.create.mock.calls[0]).toEqual(SocketClient.create.mock.calls[1]);
return SocketInterface.getOrCreateSocketFor(options3).then(() => {
expect(SocketClient.create.mock.calls.length).toBe(3);
expect(SocketClient.create.mock.calls[1]).not.toEqual(SocketClient.create.mock.calls[2]);
});
});
});
});
pit('should fork a server', () => {
fs.existsSync = jest.genMockFn().mockImpl(() => false);
fs.unlinkSync = jest.genMockFn();
let sockPath;
let callback;
childProcess.spawn.mockImpl(() => ({
on: (event, cb) => callback = cb,
send: (message) => {
expect(message.type).toBe('createSocketServer');
expect(message.data.options).toEqual({ projectRoots: ['/root'] });
expect(message.data.sockPath).toContain('react-packager');
sockPath = message.data.sockPath;
setImmediate(() => callback({ type: 'createdServer' }));
},
unref: () => undefined,
disconnect: () => undefined,
}));
return SocketInterface.getOrCreateSocketFor({ projectRoots: ['/root'] })
.then(() => {
expect(SocketClient.create).toBeCalledWith(sockPath);
});
});
});
});

View File

@ -1,102 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
jest.autoMockOff();
jest.setMock('uglify-js')
.mock('net')
.mock('fs')
.mock('bser')
.mock('../../Server');
var PackagerServer = require('../../Server');
var SocketServer = require('../SocketServer');
var bser = require('bser');
var net = require('net');
describe('SocketServer', () => {
let netServer;
let bunser;
let processOn;
beforeEach(() => {
const {EventEmitter} = require.requireActual('events');
netServer = new EventEmitter();
netServer.listen = jest.genMockFn();
net.createServer.mockImpl(() => netServer);
bunser = new EventEmitter();
bser.BunserBuf.mockImpl(() => bunser);
bser.dumpToBuffer.mockImpl((a) => a);
// Don't attach `process.on('exit')` handlers directly from SocketServer
processOn = process.on;
process.on = jest.genMockFn();
});
afterEach(() => {
process.on = processOn;
});
pit('create a server', () => {
const server = new SocketServer('/sock', { projectRoots: ['/root'] });
netServer.emit('listening');
return server.onReady().then(s => {
expect(s).toBe(server);
expect(netServer.listen).toBeCalledWith('/sock');
});
});
pit('handles getDependencies message', () => {
const server = new SocketServer('/sock', { projectRoots: ['/root'] });
netServer.emit('listening');
return server.onReady().then(() => {
const sock = { on: jest.genMockFn(), write: jest.genMockFn() };
netServer.emit('connection', sock);
PackagerServer.prototype.getDependencies.mockImpl(
() => Promise.resolve({ dependencies: ['a', 'b', 'c'] })
);
bunser.emit('value', { type: 'getDependencies', id: 1, data: '/main' });
expect(PackagerServer.prototype.getDependencies).toBeCalledWith('/main');
// Run pending promises.
return Promise.resolve().then(() => {
expect(sock.write).toBeCalledWith(
{ id: 1, type: 'result', data: ['a', 'b', 'c']}
);
});
});
});
pit('handles buildBundle message', () => {
const server = new SocketServer('/sock', { projectRoots: ['/root'] });
netServer.emit('listening');
return server.onReady().then(() => {
const sock = { on: jest.genMockFn(), write: jest.genMockFn() };
netServer.emit('connection', sock);
PackagerServer.prototype.buildBundle.mockImpl(
() => Promise.resolve({ bundle: 'foo' })
);
bunser.emit(
'value',
{ type: 'buildBundle', id: 1, data: { options: 'bar' } }
);
expect(PackagerServer.prototype.buildBundle).toBeCalledWith(
{ options: 'bar' }
);
// Run pending promises.
return Promise.resolve().then(() => {
expect(sock.write).toBeCalledWith(
{ id: 1, type: 'result', data: { bundle: 'foo' }}
);
});
});
});
});

View File

@ -1,146 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
const Promise = require('promise');
const SocketClient = require('./SocketClient');
const SocketServer = require('./SocketServer');
const crypto = require('crypto');
const debug = require('debug')('ReactNativePackager:SocketInterface');
const fs = require('fs');
const net = require('net');
const path = require('path');
const tmpdir = require('os').tmpdir();
const {spawn} = require('child_process');
const CREATE_SERVER_TIMEOUT = 5 * 60 * 1000;
const SocketInterface = {
getOrCreateSocketFor(options) {
return new Promise((resolve, reject) => {
const hash = crypto.createHash('md5');
Object.keys(options).sort().forEach(key => {
const value = options[key];
if (value) {
hash.update(
options[key] != null && typeof value === 'string'
? value
: JSON.stringify(value)
);
}
});
let sockPath = path.join(
tmpdir,
'react-packager-' + hash.digest('hex')
);
if (process.platform === 'win32'){
// on Windows, use a named pipe, convert sockPath into a valid pipe name
// based on https://gist.github.com/domenic/2790533
sockPath = sockPath.replace(/^\//, '')
sockPath = sockPath.replace(/\//g, '-')
sockPath = '\\\\.\\pipe\\' + sockPath
}
if (existsSync(sockPath)) {
var sock = net.connect(sockPath);
sock.on('connect', () => {
SocketClient.create(sockPath).then(
client => {
sock.end();
resolve(client);
},
error => {
sock.end();
reject(error);
}
);
});
sock.on('error', (e) => {
try {
debug('deleting socket for not responding', sockPath);
fs.unlinkSync(sockPath);
} catch (err) {
// Another client might have deleted it first.
}
createServer(resolve, reject, options, sockPath);
});
} else {
createServer(resolve, reject, options, sockPath);
}
});
},
listenOnServerMessages() {
return SocketServer.listenOnServerIPCMessages();
}
};
function createServer(resolve, reject, options, sockPath) {
const logPath = path.join(tmpdir, 'react-packager.log');
const timeout = setTimeout(
() => reject(
new Error(
'Took too long to start server. Server logs: \n' +
fs.readFileSync(logPath, 'utf8')
)
),
CREATE_SERVER_TIMEOUT,
);
const log = fs.openSync(logPath, 'a');
// Enable server debugging by default since it's going to a log file.
const env = Object.assign({}, process.env);
env.DEBUG = 'ReactNativePackager:SocketServer';
// We have to go through the main entry point to make sure
// we go through the babel require hook.
const child = spawn(
process.execPath,
[path.join(__dirname, '..', '..', 'index.js')],
{
detached: true,
env: env,
stdio: ['ipc', log, log]
}
);
child.unref();
child.on('message', m => {
if (m && m.type && m.type === 'createdServer') {
clearTimeout(timeout);
child.disconnect();
resolve(SocketClient.create(sockPath));
}
});
if (options.blacklistRE) {
options.blacklistRE = { source: options.blacklistRE.source };
}
child.send({
type: 'createSocketServer',
data: { sockPath, options }
});
}
function existsSync(filename) {
try {
fs.accessSync(filename);
return true;
} catch(ex) {
return false;
}
}
module.exports = SocketInterface;