skip to Main Content

I am trying to create new documents from existing ones in one of my database’s collections. I’m flattening one of the fields from the original document. The process worked perfectly when using mongosh, but now I have to run this script on a gcp function. So I need to have everything inside the script.

I managed to connect to the db, add the flattened document to the new collection, but when I go to close the connection it gives me the error:

MongoExpiredSessionError: Cannot use a session that has ended

And it never adds the document to the new collection.

I have figured out that when I remove the DB.close() from the code, it adds the documents but the program never finished/exits.

I have placed each await document where it interacts with the DB, but it has not been of much help. Here is my full code. The main function calls the connectMongo which connects to the mongo db and runs the execute function. This execute functions takes the documents from the original collection and passes them one by one to the flatten function. The flatten function does the flattening plus the adding of the new document to the new collection.

Here is the code:
The print statements at all cases (w/ & w/out DB.close()) work and print out each one.

const {MongoClient} = require('mongodb');
const uri = "mongodb://<myURI>"


const DB = new MongoClient(uri);
var collections;

//---------------------------------------------------------------------------------------------
execute = async function(db){
    
    docs = await db.collection('observations6').find()
    
    await docs.forEach(flatten)
    
}
//---------------------------------------------------------------------------------------------

flatten = async function(myDoc){

    forms = myDoc.properties.forms
    for(var i = 0; i < forms.length; i++){

        FormObj = {
            parent_id : myDoc._id+'_form_'+i,
            eventId: myDoc.eventId,
            userId: myDoc.userId,
            deviceId: myDoc.deviceId,
            createdAt: myDoc.createdAt,
            lastModified : myDoc.lastModified,
            type: myDoc.type,
            geometry: myDoc.geometry,
            favoriteUserIds: myDoc.favoriteUserIds,
            states: myDoc.states,
            attachments: myDoc.attachments,
            formId: forms[i]['formId'],
        }
        console.log('Created form object')
        field_count = 0
        retry = 0

        while (retry <= 10){
            if (!forms[i].hasOwnProperty('field'+field_count)){
                field_count += 1
            }
            retry += 1
        }
        console.log(`Did ${field_count} retry(s)`)
        while(forms[i].hasOwnProperty('field'+field_count)){
            FormObj['field'+field_count] = forms[i]['field'+field_count]
            field_count+=1
        }
        console.log('Added field properties')
        
        parent_id = myDoc._id + '_form_' + i
        await collections.collection('observations6_flatten').updateOne({parent_id},{$set: {FormObj}}, {upsert:true})

        console.log('Added object to collection')
       
    }
    
}
//---------------------------------------------------------------------------------------------
async function connectMongo() {
     
    try {
        // Connect to the MongoDB cluster
        await DB.connect();
 
        // Make the appropriate DB calls
        collections = await DB.db('magedb')
        //console.log(collections)
        await execute(collections)
    
       
    } catch (e) {
        console.error(e);
    } 
    
}

//---------------------------------------------------------------------------------------------
//Main call
main = async function(){
    await connectMongo()
    .then(()=>{
        DB.close()
    })
}

main()

This is the error it returns

aortiz@WW-593 MINGW64 ~/Documents/GitHub/engineering_etl (main)
$ node flattening/flattening.js
Created form object
Did 11 retry(s)
Added field properties
Created form object
Did 0 retry(s)
Added field properties
Created form object
Did 1 retry(s)
Added field properties
Created form object
Did 0 retry(s)
Added field properties
Created form object
Did 0 retry(s)
Added field properties
Created form object
Did 0 retry(s)
Added field properties
Created form object
Did 0 retry(s)
Added field properties
C:UsersaortizDocumentsGitHubengineering_etlnode_modulesmongodblibsessions.js:655
        return new error_1.MongoExpiredSessionError();
               ^

MongoExpiredSessionError: Cannot use a session that has ended
    at applySession (C:UsersaortizDocumentsGitHubengineering_etlnode_modulesmongodblibsessions.js:655:16)        
    at Connection.command (C:UsersaortizDocumentsGitHubengineering_etlnode_modulesmongodblibcmapconnection.js:281:53)
    at C:UsersaortizDocumentsGitHubengineering_etlnode_modulesmongodblibsdamserver.js:210:18
    at Object.callback (C:UsersaortizDocumentsGitHubengineering_etlnode_modulesmongodblibcmapconnection_pool.js:327:13)
    at ConnectionPool.processWaitQueue (C:UsersaortizDocumentsGitHubengineering_etlnode_modulesmongodblibcmapconnection_pool.js:506:33)
    at C:UsersaortizDocumentsGitHubengineering_etlnode_modulesmongodblibcmapconnection_pool.js:183:37
    at processTicksAndRejections (node:internal/process/task_queues:78:11) {
  [Symbol(errorLabels)]: Set(0) {}
}

When I remove the DB.close() it returns

aortiz@WW-593 MINGW64 ~/Documents/GitHub/engineering_etl (main)
$ node flattening/flattening.js
Created form object
Did 11 retry(s)
Added field properties
Created form object
Did 0 retry(s)
Added field properties
Created form object
Did 1 retry(s)
Added field properties
Created form object
Did 0 retry(s)
Added field properties
Created form object
Did 0 retry(s)
Added field properties
Created form object
Did 0 retry(s)
Added field properties
Created form object
Did 0 retry(s)
Added field properties
Added object to collection
Added object to collection
Added object to collection
Added object to collection
Added object to collection
Added object to collection
Added object to collection


and never finished/exits the script

EDIT

Now that I am looking better at the output when I remove DB.close(), I see that the Added Object to collection print statement gets printer and when I have the DB.close() it stops just before then. So this must mean that the error is ocurring on the line:

await collections.collection('observations6_flatten').updateOne({parent_id},{$set: {FormObj}}, {upsert:true})

But what exactly is going on that the connection ends before this point?

2

Answers


  1. As mentioned in comments, you really need to crack on promises and async/await syntax https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises. I’d recommend to learn it in context of event loop: https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop, https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/ etc.

    This

    await docs.forEach(flatten)
    

    Doesn’t do what you think it does.

    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach is a synchronous call. It works identical with and without await. It iterates the docs array adding flatten function to the queue for all items at once and resolves the "promise", i.e. returns control back to the calling await execute(collections) without waiting for any promises inside.

    await execute(collections) is the last statement in the function, so once this promise is resolved, it returns control back to the caller

    await connectMongo()
    .then(()=>{
        DB.close()
    })
    

    Which closes the connection before any await collections.collection('observations6_flatten').updateOne has a chance to take control.

    You need to either use https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all:

    execute = async function(db){
        
        docs = await db.collection('observations6').find().toArray();
        
        return await Promise.all(docs.map(flatten))    
    } 
    

    or loop through the docs explicitly:

    execute = async function(db){
        
        docs = await db.collection('observations6').find().toArray();
        
        for(const doc of docs) { 
            await flatten(doc)
        }    
    } 
    

    The Promise.all start all updates at once, and resolves the promise when flatten for all items are resolved. The for-await loop is much slower us it updates one doc at a time.

    Full working example with Promise.all (I simplified irrelevant flattening logic): https://replit.com/@blex18/74891663mongoexpiredsessionerror-cannot-use-a-session?v=1

    Console output:

    { _id: new ObjectId("63ada717dc7041cce1c67a86") }
    Created form object
    Did 0 retry(s)
    Added field properties
    { _id: new ObjectId("63ada723dc7041cce1c67a87") }
    Created form object
    Did 0 retry(s)
    Added field properties
    { _id: new ObjectId("63ada725dc7041cce1c67a88") }
    Created form object
    Did 0 retry(s)
    Added field properties
    Added object to collection
    Created form object
    Did 0 retry(s)
    Added field properties
    Added object to collection
    Created form object
    Did 0 retry(s)
    Added field properties
    Added object to collection
    Added object to collection
    Created form object
    Did 0 retry(s)
    Added field properties
    Added object to collection
    Created form object
    Did 0 retry(s)
    Added field properties
    Added object to collection
    Created form object
    Did 0 retry(s)
    Added field properties
    Added object to collection
    Created form object
    Did 0 retry(s)
    Added field properties
    Added object to collection
    Added object to collection
    
    Login or Signup to reply.
  2. await is different that .then() read here if you want to learn more about Promise Chaining

    const {MongoClient} = require('mongodb');
    const uri = "mongodb://<myURI>"
    const client = new MongoClient(uri);
    
    async function connectMongo() {
        try {
            await client.connect();
            console.log('Connected successfully to server');
            // Make the appropriate DB calls
            const collections = await client.db('magedb');
            console.log('Collections', collections);
            await execute(collections);
        } catch (e) {
            console.error(e);
        } 
    }
    
    async function execute(myDoc) {
        forms = myDoc.properties.forms
        for(var i = 0; i < forms.length; i++){
    
            FormObj = {
                parent_id : myDoc._id+'_form_'+i,
                eventId: myDoc.eventId,
                userId: myDoc.userId,
                deviceId: myDoc.deviceId,
                createdAt: myDoc.createdAt,
                lastModified : myDoc.lastModified,
                type: myDoc.type,
                geometry: myDoc.geometry,
                favoriteUserIds: myDoc.favoriteUserIds,
                states: myDoc.states,
                attachments: myDoc.attachments,
                formId: forms[i]['formId'],
            }
            console.log('Created form object')
            field_count = 0
            retry = 0
    
            while (retry <= 10){
                if (!forms[i].hasOwnProperty('field'+field_count)){
                    field_count += 1
                }
                retry += 1
            }
            console.log(`Did ${field_count} retry(s)`)
            while(forms[i].hasOwnProperty('field'+field_count)){
                FormObj['field'+field_count] = forms[i]['field'+field_count]
                field_count+=1
            }
            console.log('Added field properties')
            
            parent_id = myDoc._id + '_form_' + i;
    
            try {
                await client.db().collection('observations6_flatten').updateOne({parent_id},{$set: {FormObj}}, {upsert:true});
                console.log('Added object to collection');
            } catch (error) {
                throw error; // still want to crash
            }
    
        }
        
    }
    
    connectMongo()
      .then(console.log)
      .catch(console.error)
      .finally(() => client.close());
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search