2
votes

I've run into an issue with a firebase function, written in TypeScript for the Node.js environment. I have a function with https-endpoint where the client can send data that needs to be stored in the database. In order to know which objects already has been added to the database, it first reads a path ("lookup") that has a simplified registry of the object (lookup/:objectId/true). Then it makes the values that should be updated at the actual object path and updates these in the database.

The function is as follows:

export const scrapeAssignments = functions.https.onCall((data, context) => {
    const htmlString = data.htmlString
    // const htmlString = fs.readFileSync(testPath.join(__dirname, "./assignmentListExample.html"), { encoding: 'utf8' })
    if (!(typeof htmlString === 'string') || htmlString.length === 0) {
        throw new functions.https.HttpsError('invalid-argument', 'The function must be called with one argument "htmlString"');
    }

    const userId = getUserIdFromCallableContext(context)
    console.log("userId", userId)
    let newAssignments: ScrapedAssignment[] = []
    try {
        newAssignments = parseAssignment(htmlString)
    } catch (e) {
        const error = <Error>e
        throw new functions.https.HttpsError('not-found', 'parsing error: ' + error.message)
    }

    return admin.database().ref("lookup").child(userId).child("assignments")
        .once("value", lookupSnapshot => {
            const oldAssignmentsLookup = lookupSnapshot.val() || {}
            const newAssignmentsLookup = makeLookup(newAssignments)

            // 1. Create update values for scraped assignment data
            let scrapedAssignmentUpdateValues = newAssignments.reduce((prev, current) => {
                const prefixed = prefixObject(current.id + "/", current)
                return { ...prev, ...prefixed }
            }, {})

            // 2. Use the diff from the two lookups to find old assignments to delete
            const removeAssignmentsValues = {}
            Object.keys(oldAssignmentsLookup).forEach(assignmentId => {
                if (isUndefined(newAssignmentsLookup[assignmentId]))
                    removeAssignmentsValues[assignmentId] = null
            })

            // 3. Add other user values to newly found assignments
            Object.keys(newAssignmentsLookup).forEach(assignmentId => {
                if (isUndefined(oldAssignmentsLookup[assignmentId])) {
                    const doneKey = assignmentId + "/done" 
                    scrapedAssignmentUpdateValues[doneKey] = false
                }
            })

            const combinedValues = { ...scrapedAssignmentUpdateValues, ...removeAssignmentsValues }
            return admin.database().ref("userAssignments").child(userId).update(combinedValues)
        }).catch(reason => {
            throw new functions.https.HttpsError('internal', 'Database reason: ' + reason)
        })
})

I see that the data is written to the right place and everything seems to go as expected, except that when I call the function from an iOS app, It returns an "Internal"-error

When I check the function logs in the cloud console, I see the following error:

assignment-scrapeAssignments uolk47opctna Unhandled error RangeError: Maximum call stack size exceeded at Function.mapValues (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13395:23) at encode (/user_code/node_modules/firebase-functions/lib/providers/https.js:204:18) at /user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13400:38 at /user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:4925:15 at baseForOwn (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:3010:24) at Function.mapValues (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13399:7) at encode (/user_code/node_modules/firebase-functions/lib/providers/https.js:204:18) at /user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13400:38 at /user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:4925:15 at baseForOwn (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:3010:24) at Function.mapValues (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13399:7)

All I can read out of this, is that it's an "RangeError: Maximum call stack size exceeded"-error, and something happens at Function.mapValues. As I can read from this SO question it seems to be an issue with a reading and writing to the same location at the same time. But I'm quite sure that I'm not doing that here.. Plus everything seems behave like it should, except for the actual error.

When combinedValues is updated, it is an object with ~300 key/value pairs, is that a problem?

1

1 Answers

0
votes

Looks like your function run out of memory, every cloud function has allocated memory to its execution. You can try to increase the stack size from its default value (256MB) up to 2GB by going to: Functions-> Dashboard, then go to your problematic function and click on the right menu "Detailed usage stats":

enter image description here

then on your function details in google cloud dashboard click edit:

enter image description here then increase the value of "memory allocated" to 2GB (or lower sufficient value):

enter image description here

note: you should keep in mind that when your data growth you may exceed the highest limit so consider this when querying in the cloud functions