Automattic / mongoose

MongoDB object modeling designed to work in an asynchronous environment.
https://mongoosejs.com
MIT License
26.95k stars 3.84k forks source link

Topology was destroyed #6121

Closed JonathanWilbur closed 6 years ago

JonathanWilbur commented 6 years ago

Do you want to request a feature or report a bug? Bug

What is the current behavior? If I close a connection using db.close() at the end of the insertMany() callback, an exception is thrown (most times, but not every time) saying "topology was destroyed."

If the current behavior is a bug, please provide the steps to reproduce.

Here is the code I used, just with the connection URI removed:

let mongoose = require('mongoose');
const languageSchema = require("../mongoose/language.schema.js").Language;
const langs = require("./languages3.json");

mongoose.connect('mongodb://my.host.name/database');
let db = mongoose.connection;
let Language = mongoose.model("Language", languageSchema);
db.on('error', console.error.bind(console, 'connection error:'));
db.once('connected', function() {
    let languages = [];
    langs.forEach((lang) => {
        let language = new Language({
            englishNames: lang.name.trim().split(", "),
            nativeNames: (typeof(lang.nativeName) == "undefined" ? [] : lang.nativeName.trim().split(", ")),
            family: lang.family,
            iso639part1Code: lang["iso639-1"],
            iso639part2TCode: lang["iso639-2T"],
            iso639part2BCode: lang["iso639-2B"],
            iso639part3Code: lang["iso639-3"],
            latitude: lang.latitude,
            longitude: lang.longitude
        });
        languages.push(language);
    });
    Language.insertMany(languages, function (err, saved) {
        if (err) {
            console.log(err);
        } else {
            console.log("Done!");
        }
        if (db.readyState == 1) db.close();
    });
});

What is the expected behavior? The expected behavior is for this connection to close out gracefully.

Note: I am using MongoDB 3.6.2, Mongoose 5.0.4, and NodeJS 9.5.0 on Mac OS X 10.13.3.

lineus commented 6 years ago

I wanted to try this out so I created a test and ran it 100 times. I didn't see any errors on mine though. I'm curious, do you still see errors when running an albeit contrived example like the one below?

here's the code I used:

#!/usr/bin/env node
'use strict'

const mongoose = require('./lib/test_db')
const mongo = mongoose.connections[0]
const uri = `mongodb://${mongo.host}:${mongo.port}/${mongo.name}`
const waiting = require('./lib/versions').get(uri)
waiting.then(versions => console.log(uri, '\n', versions))

const testSchema = mongoose.Schema({ x: Number })
const Test = mongoose.model('test', testSchema)
const tests = []

for (let i = 0; i < 100000; i++) {
  tests.push(new Test({ x: i }))
}

Test.insertMany(tests, function (err, savedTests) {
  if (err) { return console.error(err) }
  console.log('Done!')
  mongoose.connection.close()
})

here's the output from one run:

5: ./6121.js 
mongodb://localhost:27017/6121 
 { mongooseVersion: '5.0.4',
  mongodbNativeVersion: '3.0.2',
  nodeVersion: 'v8.9.4',
  mongodbServerVersion: '3.6.2' }
Done!

mongo shell:

> use 6121
switched to db 6121
> db.tests.count()
10000000

how I ran the test:

5: i=0;while [ $i -lt 100 ];do ./6121.js >>./output.txt 2>&1 && let i=(${i}+1);done
5: wc -l output.txt 
     600 output.txt
5: grep -c Done output.txt
100
5: egrep -cv "(Done|Version|mongodb:)" output.txt 
0
5: 
vkarpov15 commented 6 years ago

@JonathanWilbur can you modify @lineus ' script to reproduce your issue?

@lineus thanks for your help! Are you on the mongoosejsteam.slack.com slack channel?

lineus commented 6 years ago

@vkarpov15 I am now!

sobafuchs commented 6 years ago

My guess is this is due to a race condition if you are trying to run some other db logic and it runs after db.close(), is that possibility @JonathanWilbur ?

Spown commented 5 years ago

I occasionally see this too in similar situations. Reproducability rate is very low though. I never see it on my server but my local machine is understandably less powerful so... It looks like most of the time all other tasks manage to complete before close() is called. I gather close would check if the query queue is empty and run when it's empty, but it probably doesn't mark the connection as not being able to execute new queries fast enough.