Ad

Hangs On Await S3.listObjects

I have an event which triggers lambda code that attempts to list all objects in one bucket, and then copy them over to another bucket.

Code that calls the handler with an event

const cfnHandlerWrapper = async (event, handler) => {
    let resultPromise;
    try {
        handler(event).then(function() {
            console.log(`Successfully copied over all artifacts`);
            resultPromise = sendSuccess(event);
        }).catch(function(error) {
            console.log(`Failed copying over all artifacts: ${error}`);
            throw error;
        });
    } catch (e) {
        console.log('ERROR RUNNING HANDLER:');
        console.log(e);
        resultPromise = sendFailure(e.message || 'Something went wrong', event);
    }
    return resultPromise;
};

Code that handles copying

const copyHandler = async (event) => {
    console.log('Running Agent artifacts copy handler');

    const {RequestType, ResourceProperties} = event;
    const {DestBucket, SrcBucket, AdditionalArtifactsPath} = ResourceProperties;

    if (RequestType === 'Create' || RequestType === 'Update') {
        const srcLocation = [SrcBucket, AdditionalArtifactsPath].join('/');
        const batsAdditionalArtifactsParams = {
            Bucket: SrcBucket,
            Delimiter: '',
            Prefix: `${AdditionalArtifactsPath}/`
        };

        let copyPromises = [];
        console.log("about to await s3.listObjects()...");

        let listObjectsResult;
        try {
            listObjectsResult = await s3.listObjects(batsAdditionalArtifactsParams).promise();
        } catch(err) {
            console.log(`Failed to list objects for ${srcLocation}, err: ${err}`);
            throw err;
        }
        console.log(`finished waiting for listObjects. listObjectsResult: ${listObjectsResult}`);
        console.log(`Successfully listed objects in ${srcLocation}. Attempting to copy all artifacts to ${DestBucket}`);
        listObjectsResult.Contents.forEach((object) => {
            console.log(`parsing object ${object}`);
            let keyParts = object.Key.split('/');
            let fileKey = keyParts.pop();
            let destKey = fileKey.includes(agentBootstrapScript) ? `${agentBootstrapScriptsFolder}/${agentBootstrapScript}` : fileKey; // Adhere to bootstrap folder structure
            let copyParams = {
                Bucket: DestBucket,
                CopySource: `${srcLocation}/${fileKey}`,
                Key: destKey
            };
            console.log("pushing copy promise");
            copyPromises.push(s3.copyObject(copyParams).promise()
                .then(function(data) {
                    console.log(`Successfully copied ${fileKey} from ${srcLocation} to ${DestBucket}! data=${data}`);
                }).catch(function(err) {
                console.log(`Encountered error while copying ${fileKey} from ${srcLocation} to ${DestBucket}, error: ${err}`);
            }));
        });
        console.log("about to await Promise.all()....");
        await Promise.all(copyPromises);
    } else {
        console.log(`Received event type ${RequestType}, not copying anything`);
    }

    console.log("returning Promise.resolve()");
    return Promise.resolve();
};

This code only outputs the log message about to await s3.listObjects()..., so it looks like it's getting stuck on the await:

listObjectsResult = await s3.listObjects(batsAdditionalArtifactsParams).promise();

All I want to do is wait for all objects to be listed, make promises for copying those objects, and then use a Promise.all to wait for all those copies to complete before returning from this handler. I put a try/catch around the await but it just looks like it's getting stuck forever in that await. Any tips on this or even how to debug this kind of situation?

Ad

Answer

Your code is very confusing. You are mixing async/await with Promise.then. Usually you go one way or the other, but never both.

By looking at your code, I guess that sendResult returns a Promise since you assign it's result to an object called resultPromise but you never await on it.

copyHandler also returns a Promise but you don't await on it (you are chaining the then call) but by the time it reaches return resultPromise the promise has not yet been resolved.

To me, the problem is on your cfnHandlerWrapper function. I would change the code you posted to:

const cfnHandlerWrapper = async (event, handler) => {
    try {
        await copyHandler(event)
        console.log(`Successfully copied over all artifacts`);
        return await sendSuccess(event);
    } catch (e) {
        console.log('ERROR RUNNING HANDLER:');
        console.log(e);
        return await sendFailure(e.message || 'Something went wrong', event);
    }
};

Now, on your forEach you have another problem because the code inside it contains promises which will also run asynchronously, so by the time you invoke await Promise.all(copyPromises);, copyPromises has not been populated with all the promises you are expecting (likely, it is going to be populated with none).

I would use a for of instead (only relevant code below):

for (const object of listObjectsResult.Contents) {
    console.log(`parsing object ${object}`);
    let keyParts = object.Key.split('/');
    let fileKey = keyParts.pop();
    let destKey = fileKey.includes(agentBootstrapScript) ? `${agentBootstrapScriptsFolder}/${agentBootstrapScript}` : fileKey; // Adhere to bootstrap folder structure
    let copyParams = {
        Bucket: DestBucket,
        CopySource: `${srcLocation}/${fileKey}`,
        Key: destKey
    };
    console.log("pushing copy promise");
    copyPromises.push(s3.copyObject(copyParams).promise())
}
console.log("about to await Promise.all()....");
await Promise.all(copyPromises)

It's hard to tell whether this code works or not without testing it, but I don't see why it should fail. Anyways, try to break down your component in smaller pieces and see exactly where it's failing. I bet all my coins on the way you are dealing with your promises, though.

Ad
source: stackoverflow.com
Ad