0

I've been stuck on this issue for days with little to no progress, please help if you can!

I have a Node.js (v12) AWS Lambda that needs to pull data from my Firebase realtime database and process each record into a Redis cache if it doesn't exist already. The function starts but never finishes and instead I receive Task timed out after 180.10 seconds from AWS.

Things I've tried:

  • Using exports.handler = async function(event) versus exports.handler = function(event, context, callback);
  • For the synchronous attempt above I've tried using context.callbackWaitsForEmptyEventLoop = false versus not;
  • Using promises versus cascading functions versus stitching a bunch of .then()'s together;
  • Using the firebase-admin versus the https module in-conjunction with the Firebase REST API;
  • Using settimeout to fire the callback later versus not;
  • Setting the GOOGLE_APPLICATION_CREDENTIALS environment variable to my service account credentials versus referencing the file directly in the code;
  • I've even beefed-up the memory and timeout of the Lambda itself to the maximum it can go, as well as cut down the data I want to pull from Firebase to just 1 record.

Responses I've had as-per the attempts above:

  • AWS (the most frequent): Task timed out after 180.10 seconds;
  • AWS (.then stitching approach): Function completed successfully (but no data was actually processed);
  • Node HTTPS (REST API approach): ETIMEDOUT or ECONNREFUSED;

Below is where I'm up to and still no luck. I have cut-out the caching code since I know that works fine. The settimeout's you see were my last resorts before reaching out here.

const admin = require("firebase-admin");
admin.initializeApp({
    credential: admin.credential.applicationDefault(),
    databaseURL: "https://{projectName}.firebaseio.com"
});
var result = [];
exports.handler = (event, context, callback) => {
    context.callbackWaitsForEmptyEventLoop = false;
    try {
        admin.database().ref("data").orderByChild("timestamp").limitToLast(1).once("value", snapshot => {
            if (snapshot.exists()) {
                console.log('snapshot exists...');
                let posts = snapshot.val();
                result = Object.keys(posts);
            }
            setTimeout(() => {
                admin.database().goOffline();
                admin.app().delete();
                callback(null, `Success! ${JSON.stringify(result)}`); // <-- NEVER RETURNS
            }, 2000);
        }, error => { 
            setTimeout(() => {
                admin.database().goOffline();
                admin.app().delete();
                callback(error); // <-- NEVER RETURNS
            }, 2000);
        });
    } catch (error) {
        setTimeout(() => {
            admin.database().goOffline();
            admin.app().delete();
            callback(error); // <-- NEVER RETURNS
        }, 2000);
    }
};
cook
  • 45
  • 6

1 Answers1

0

It doesn't appear you are storing or using the setTimeout on the root level of your function. You should store it so the call back function can keep running since it only exists while it's in scope. Doing this also requires you to bind the object so you have a self-reference if you decide to push it into an array for multiple callbacks

var result = [];
var timeOut;
//...
timeOut = setTimeout(() => {
            admin.database().goOffline();
            admin.app().delete();
            callback(error);
        }.bind(this), 2000);

Source: MSDN Function.prototype.bind()

If Binding is not a solution and you want a blocking method, you might be interested in a delay function, it behaves the same as a setTimeout but works with promises

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

// Async function solution
await sleep(2000);
  console.log('Two seconds later, showing sleep in a loop...');

// non-Async solution
sleep(2000)
.then(()=> {
            admin.database().goOffline();
            admin.app().delete();
            callback(error);
        })
.catch(e => console.log(e));
DIGI Byte
  • 4,225
  • 1
  • 12
  • 20
  • Thanks for your response DIGI Byte, the bind function doesn't seem to be available to be set where you put it above - I'm getting an error: `',' expected.`. Is there a better way to implement the binding you mention? – cook May 04 '21 at 11:48
  • for additional and comprehensive answers, I suggest https://stackoverflow.com/a/2130411/2301161 and the other answer https://stackoverflow.com/a/8800171/2301161 - please let me know if any of those work – DIGI Byte May 05 '21 at 03:01
  • an alternative is to make the function async with a delayed function instead - Added code for a promise delay – DIGI Byte May 05 '21 at 03:04
  • Unfortunately I'm still getting timeout errors DIGI Byte - do you think there is a better way altogether to accomplishing the same goal? – cook May 06 '21 at 20:05
  • Is it possible that it's being blocked by a cors policy or misconfigured? – DIGI Byte May 06 '21 at 21:39