Skip to content

Commit

Permalink
Support async handlers. More logging and comments in proxy.
Browse files Browse the repository at this point in the history
Signed-off-by: Rob Ribeiro <[email protected]>
  • Loading branch information
azurelogic committed May 23, 2018
1 parent 16044d6 commit adb2589
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 20 deletions.
18 changes: 9 additions & 9 deletions package-lock.json

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

48 changes: 37 additions & 11 deletions proxy/index.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@

const cp = require('child_process');
const WebSocket = require('ws');
const http = require('http');
const types = require('../lib/messageTypes');

// parse handler name
const handlerEnv = process.env._HANDLER && process.env._HANDLER.indexOf('.') > -1 ? process.env._HANDLER.split('.') : [null, 'defaultHandler']; // eslint-disable-line
const HANDLER_NAME = handlerEnv[1];// eslint-disable-line

let child;
let childSocket;
let brokerSocket;

function currentTimeInMillis () {
const hrtime = process.hrtime()
return hrtime[0] * 1000 + Math.floor(hrtime[1] / 1000000)
function currentTimeInMilliseconds() {
const hrtime = process.hrtime();
return (hrtime[0] * 1000) + Math.floor(hrtime[1] / 1000000);
}

function runAsProxy() {
if (!process.env.DEBUGGER_ACTIVE || process.env.DEBUGGER_ACTIVE === 'false') return;
console.log('[AWS Lambda Debugger] Debugger Status: ACTIVE');
let childResolver;
let debuggerUrl;
const childPromise = new Promise((resolve) => { childResolver = resolve; });
Expand All @@ -36,6 +37,7 @@ function runAsProxy() {
const message = JSON.parse(messageString);
switch (message.type) {
case types.CHILD_READY: {
console.log('[AWS Lambda Debugger] Child ready.');
debuggerUrl = message.debuggerUrl; // eslint-disable-line
childResolver();
break;
Expand All @@ -46,6 +48,7 @@ function runAsProxy() {
}
});

// set child stream encodings
child.stdout.setEncoding('utf8');
child.stderr.setEncoding('utf8');
}
Expand All @@ -59,6 +62,7 @@ function runAsProxy() {
// this is a requirement to keep function from running to full timeout
context.callbackWaitsForEmptyEventLoop = false; // eslint-disable-line

// handle shims for callback and callbackWaitsForEmptyEventLoop
function childMessageHandler(messageString) {
const message = JSON.parse(messageString);
switch (message.type) {
Expand Down Expand Up @@ -94,9 +98,14 @@ function runAsProxy() {
brokerSocket.on('message', (messageString) => {
const message = JSON.parse(messageString);
switch (message.type) {
// handle notification from broker that user has connected
case types.USER_CONNECTED: {
console.log('user connected via broker. invoking child.');
console.log('[AWS Lambda Debugger] User connected via broker. Invoking child.');

// open socket to child's debugger endpoint
childSocket = new WebSocket(debuggerUrl);

// proxy V8 debugger messages from child
childSocket.on('message', (rawInspectorMessage) => {
if (brokerSocket.readyState === WebSocket.OPEN) {
brokerSocket.send(JSON.stringify({
Expand All @@ -105,9 +114,11 @@ function runAsProxy() {
}));
}
});

// send invoke message to child
childSocket.on('open', () => {
child.send(JSON.stringify({
timestamp: currentTimeInMillis(),
timestamp: currentTimeInMilliseconds(),
remainingTime: context.getRemainingTimeInMillis(),
type: types.INVOKE_HANDLER,
event,
Expand All @@ -116,6 +127,8 @@ function runAsProxy() {
});
break;
}

// proxy V8 debugger messages to child
case types.V8_INSPECTOR_MESSAGE: {
if (childSocket.readyState === WebSocket.OPEN) {
childSocket.send(message.payload);
Expand All @@ -128,6 +141,7 @@ function runAsProxy() {
}
});

// clean up on broker socket close
brokerSocket.on('close', () => {
if (childSocket && childSocket.readyState === WebSocket.OPEN) {
childSocket.close();
Expand All @@ -138,15 +152,17 @@ function runAsProxy() {
}

function runAsChild() {
// shim callback
const callback = (err, response) =>
process.send(JSON.stringify({ type: types.LAMBDA_CALLBACK, err, response }));

// handle messages from proxy
process.on('message', (messageString) => {
const message = JSON.parse(messageString);
switch (message.type) {
// handle invoke message from proxy
case types.INVOKE_HANDLER: {
// shimming context
// shim context
let _callbackWaitsForEmptyEventLoop = true; // eslint-disable-line
Object.defineProperties(message.context, {
callbackWaitsForEmptyEventLoop: {
Expand All @@ -160,8 +176,11 @@ function runAsChild() {
}
}
});
// emulate getRemainingTimeInMillis by taking the known remaining time and subtracting the
// delta between now and when that known remaining time was captured. Result should be
// very close since both parent and child share the same system clock.
message.context.getRemainingTimeInMillis =
() => message.remainingTime - (currentTimeInMillis() - message.timestamp);
() => message.remainingTime - (currentTimeInMilliseconds() - message.timestamp);
message.context.done = (err, response) =>
process.send(JSON.stringify({
type: types.LAMBDA_CALLBACK,
Expand All @@ -173,12 +192,18 @@ function runAsChild() {
message.context.fail = err => message.context.done(err, null);

// get ready for the user
console.log(`Current AWS request ID: ${message.context.awsRequestId}`);
console.log(`[AWS Lambda Debugger] Current AWS request ID: ${message.context.awsRequestId}`);
setTimeout( // this is a hack to get around the delay before the debugger fully kicks in
() => {
const handler = module.parent.exports[HANDLER_NAME];
debugger;
// *** STEP INTO THE FOLLOWING LINE TO BEGIN STEPPING THROUGH YOUR FUNCTION ***
module.parent.exports[HANDLER_NAME](message.event, message.context, callback);
const response = handler(message.event, message.context, callback);
// handle promise returns - required for async handler support
if (response instanceof Promise) {
response.then(message.context.succeed);
response.catch(message.context.fail);
}
},
1000
);
Expand All @@ -197,7 +222,7 @@ function runAsChild() {
responseString += chunk;
});
responseStream.on('end', () => {
console.log(responseString);
console.log(`[AWS Lambda Debugger] Child process info: ${responseString}`);
const response = JSON.parse(responseString);
process.send(JSON.stringify({
type: types.CHILD_READY,
Expand All @@ -207,6 +232,7 @@ function runAsChild() {
});
}

// process.send only exists on a child process
if (process.send) {
runAsChild();
} else {
Expand Down

0 comments on commit adb2589

Please sign in to comment.