|
| 1 | +'use strict' |
| 2 | + |
| 3 | +const execFile = require('child_process').execFile; |
| 4 | +const crypto = require('crypto'); |
| 5 | +const fs = require('fs'); |
| 6 | +const path = require('path'); |
| 7 | +const os = require('os'); |
| 8 | + |
| 9 | +let child = null; |
| 10 | + |
| 11 | +// Retrieve the singleton Haskell process |
| 12 | +function getHaskellProcess() { |
| 13 | + |
| 14 | + if (child == null) { |
| 15 | + |
| 16 | + child = execFile(path.join(process.env['LAMBDA_TASK_ROOT'],'main_hs')); |
| 17 | + |
| 18 | + console.log('Exec-ed child'); |
| 19 | + |
| 20 | + // Set proper encoding |
| 21 | + child.stdin.setEncoding('utf-8'); |
| 22 | + child.stdout.setEncoding('utf-8'); |
| 23 | + |
| 24 | + // Forward Haskell logs to CloudWatch |
| 25 | + child.stdout.on('data', function(data) { |
| 26 | + console.log('child: ' + data); |
| 27 | + }); |
| 28 | + |
| 29 | + child.stderr.on('data', function(data) { |
| 30 | + console.log('child err: ' + data); |
| 31 | + }); |
| 32 | + |
| 33 | + // Crash if Haskell process returned |
| 34 | + child.on('exit', function() { |
| 35 | + console.log("child returned!"); |
| 36 | + process.exit(1); |
| 37 | + }); |
| 38 | + |
| 39 | + console.log('Child is ready'); |
| 40 | + } |
| 41 | + |
| 42 | + return child; |
| 43 | +} |
| 44 | + |
| 45 | +exports.handler = function(event, context, callback) { |
| 46 | + |
| 47 | + // keeping the child (Haskell) process around means that the node event |
| 48 | + // loop is never really empty. Setting this means that Lambda won't wait |
| 49 | + // around for something that'll never happen. |
| 50 | + context.callbackWaitsForEmptyEventLoop = false; |
| 51 | + |
| 52 | + // Get a new response ID and directory |
| 53 | + const respId = crypto.randomBytes(20).toString('hex'); |
| 54 | + const responseDirTemplate = path.join(os.tmpdir(), 'resp-'); |
| 55 | + |
| 56 | + fs.mkdtemp(responseDirTemplate, (err, responseDir) => { |
| 57 | + |
| 58 | + if (err) throw err; |
| 59 | + |
| 60 | + const responseFile = path.join(responseDir, 'response.json'); |
| 61 | + |
| 62 | + console.log("Expecting answer in " + responseDir); |
| 63 | + |
| 64 | + let fswatcher = null; |
| 65 | + let fileWasHandled = false; |
| 66 | + |
| 67 | + fswatcher = fs.watch(responseDir, (eventType, filename) => { |
| 68 | + |
| 69 | + console.log("Got event: " + eventType + " at " + filename); |
| 70 | + |
| 71 | + // my gut feeling is that there could be a race condition if a |
| 72 | + // thread yields _after_ the eval of "!fileWasHandled" and _before_ |
| 73 | + // setting "fileWasHandled = true", though not 100% sure since node |
| 74 | + // is single threaded and I have no idea if any thread can just |
| 75 | + // "yield" |
| 76 | + if (eventType === 'rename' && filename === 'response.json' && !fileWasHandled) { |
| 77 | + |
| 78 | + // prevent other events from trying to handle the file |
| 79 | + fileWasHandled = true; |
| 80 | + |
| 81 | + // no need to watch this file anymore |
| 82 | + fswatcher.close(); |
| 83 | + |
| 84 | + console.log("Event is response"); |
| 85 | + |
| 86 | + // Parse the response from the Haskell process |
| 87 | + const response = JSON.parse(fs.readFileSync(responseFile, 'utf8')); |
| 88 | + console.log("Parsed response: " + JSON.stringify(response)); |
| 89 | + |
| 90 | + // Clean up the directory and finally reply |
| 91 | + console.log("cleaning up"); |
| 92 | + fs.unlink(responseFile, (err) => { |
| 93 | + if (err) throw err; |
| 94 | + fs.rmdir(responseDir); |
| 95 | + }); |
| 96 | + callback(null, response); |
| 97 | + } |
| 98 | + }); |
| 99 | + |
| 100 | + // Send the request to the child |
| 101 | + const req = { responseFile: responseFile, request: event } |
| 102 | + const reqStr = JSON.stringify(req); |
| 103 | + console.log("Writing request: " + reqStr); |
| 104 | + |
| 105 | + getHaskellProcess().stdin.write(reqStr + "\n"); |
| 106 | + console.log("Request was sent to Haskell process."); |
| 107 | + }); |
| 108 | +} |
0 commit comments