Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TypeError: Cannot read property 'findOne' of null with capped collection #8566

Closed
abuzhynsky opened this issue Feb 6, 2020 · 6 comments
Closed
Labels
confirmed-bug We've confirmed this is a bug in Mongoose and will fix it. docs This issue is due to a mistake or omission in the mongoosejs.com documentation
Milestone

Comments

@abuzhynsky
Copy link

abuzhynsky commented Feb 6, 2020

Do you want to request a feature or report a bug?
I want to report a bug.

What is the current behavior?
When I trying to use thefindOne method immediately after the model creation I get the exception:

TypeError: Cannot read property 'findOne' of null
    at NativeCollection.(anonymous function) [as findOne] (/home/ales/Projects/own/mongoose-test/node_modules/mongoose/lib/drivers/node-mongodb-native/collection.js:166:24)
    at NodeCollection.findOne (/home/ales/Projects/own/mongoose-test/node_modules/mquery/lib/collection/node.js:42:19)
    at model.Query.Query.findOne (/home/ales/Projects/own/mongoose-test/node_modules/mquery/lib/mquery.js:2024:20)
    at model.Query.<anonymous> (/home/ales/Projects/own/mongoose-test/node_modules/mongoose/lib/query.js:2129:22)
    at model.Query._wrappedThunk [as _findOne] (/home/ales/Projects/own/mongoose-test/node_modules/mongoose/lib/helpers/query/wrapThunk.js:16:8)
    at process.nextTick (/home/ales/Projects/own/mongoose-test/node_modules/kareem/index.js:369:33)
    at process._tickCallback (internal/process/next_tick.js:61:11)

Please note that I face this exception only if I use these options simultaneously in the schema:

bufferCommands: false,
capped: { size: 1000000, max: 100 }

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

There is the example code to reproduce the bug:

  await mongoose.connect("mongodb://localhost:27017/test-db", {
    useUnifiedTopology: true,
    useNewUrlParser: true
  });

  const Schema = mongoose.Schema;
  const ObjectId = Schema.ObjectId;

  const Test = new Schema(
    {
      author: ObjectId,
      title: String
    },
    {
      bufferCommands: false,
      capped: { size: 1000000, max: 100 }
    }
  );

  const testsModel = mongoose.model("test", Test);

  try {
    const tests = await testsModel.findOne();
    console.log(tests);
  } catch (e) {
    console.log(e)
  }

If I add the delay (e.g. 100 milliseconds) between the mongoose.model and findOne calls the problem disappears. But this doesn't look like a production-ready solution. Can you advice me more stable workaround, please?
Also seems that the problem does not depend on collection existence in the database.

What is the expected behavior?
I should be able to call findOne immediately after the model call or I should have a possibility to check if the compiled model is ready to use.

What are the versions of Node.js, Mongoose and MongoDB you are using? Note that "latest" is not a version.
Node.js: v10.15.3
Mongoose: 5.8.11
MongoDB: 3.4

@AbdelrahmanHafez
Copy link
Collaborator

I suspect this is because of the bufferCommands: false.

Starting on mongoose 5.0, mongoose buffers all commands until a connection is established.

I think that causes the underlying mongodb driver to attempt to execute a query on a collection that it doesn't know about yet.

Will add a reproduction test ASAP. Meanwhile, try removing the bufferCommands: false bit and see how it affects your program.

@abuzhynsky
Copy link
Author

abuzhynsky commented Feb 9, 2020

Thanks for the answer.

Without the bufferCommands: false everything is fine - I've already tried it.
And everything works fine if I use the bufferCommands: false with regular collection.

Unfortunately, I use the bufferCommands: false intentionally - we need to see if the MongoDB server is down.

@vkarpov15 vkarpov15 added this to the 5.8.13 milestone Feb 13, 2020
@vkarpov15 vkarpov15 added the needs repro script Maybe a bug, but no repro script. The issue reporter should create a script that demos the issue label Feb 13, 2020
@vkarpov15 vkarpov15 modified the milestones: 5.9.1, 5.9.2 Feb 14, 2020
@AbdelrahmanHafez
Copy link
Collaborator

we need to see if the MongoDB server is down.

Could you elaborate on that? I think this behavior is by design, if you choose to not use bufferCommands, then you'll need to carry the burden of waiting for a connection to be established before running code the tries to use the database.

However, here's a reproduction script:

const mongoose = require('mongoose');
const { Schema } = mongoose;
const assert = require('assert');

mongoose.connect('mongodb://127.0.0.1:27017/test', { useNewUrlParser: true });

const userSchema = new Schema({ name: String }, { bufferCommands: false });
const User = mongoose.model('User', userSchema);

async function run () {
  const user = await User.create({ name: 'Hafez' });
  assert.equal(user.name, 'Hafez');
}

run().catch(console.error);

which outputs:

TypeError: Cannot read property 'insertOne' of null
    at NativeCollection.(anonymous function) [as insertOne] (app-root\node_modules\mongoose\lib\drivers\node-mongodb-native\collection.js:158:24)
    at model.Model.$__handleSave (app-root\node_modules\mongoose\lib\model.js:266:33)
    at model.Model.$__save (app-root\node_modules\mongoose\lib\model.js:329:8)
    at app-root\node_modules\kareem\index.js:278:20
    at _next (app-root\node_modules\kareem\index.js:102:16)
    at process.nextTick (app-root\node_modules\kareem\index.js:499:38)
    at process._tickCallback (internal/process/next_tick.js:61:11)
    at Function.Module.runMain (internal/modules/cjs/loader.js:834:11)
    at startup (internal/bootstrap/node.js:283:19)
    at bootstrapNodeJSCore (internal/bootstrap/node.js:623:3)

Tested with:
Mongoose v5.6.3
Node v10.17.0
MongoDB v4.0.11

@abuzhynsky
Copy link
Author

Could you elaborate on that?

Sure. Sorry if I wasn't specific in my issue.

It's not a problem for me to wait for the connection establishment - as you can see, I was awaiting the mongoose.connect call in my test code. So seems that connection is established successfully.

The problem is about using testsModel.findOne() with the capped collection and disabled bufferCommands setting. And this code doesn't throw an exception if I use the regular collection instead of the capped.

Regarding the disabled bufferCommands setting - I'm using the mongoose with the mongoDB replicaset in the k8s environment with the dynamic IP addresses. And after mongoDB redeployment (which causes the change of IP addresses of all mongoDB nodes) mongoose is unable to reconnect and the only option for me is the disabling the bufferCommands setting, addition of liveness probe based on the mongoDB connection and automatic container restart based on the liveness probe. Anyway I'm still using the quite old mongoose version (4.13) and haven't tried the useUnifiedTopology in the k8s environment. Maybe it will solve my problems.

To eliminate the versions inconsistencies - I've faced with the Cannot read property 'findOne' of null issue in the 4.13 version but the issue is still actual in the 5.8.11 (I've tried in in the test project).

Sorry if I'm wrong somewhere.

Thanks in advance.

@vkarpov15
Copy link
Collaborator

I took a closer look and I'm going to have to say that this is expected behavior. With bufferCommands set to false, Mongoose won't wait until the capped collection is done building before using it. For capped collections, you have 2 options:

  1. Explicitly build the collection yourself using Model.createCollection:
  const Test = new Schema(
    {
      author: ObjectId,
      title: String
    },
    { 
      bufferCommands: false, autoCreate: false
    }
  );

  const testsModel = mongoose.model("test", Test);
  await testsModel.createCollection({ capped: true, size: 1000000, max: 100 });
  1. Set autoCreate to true to tell Mongoose to automatically build collections, and wait for Model.init().
  await mongoose.connect("mongodb://localhost:27017/test-db", {
    useUnifiedTopology: true,
    useNewUrlParser: true,
    autoCreate: true
  });
  await mongoose.connection.dropDatabase();

  const Schema = mongoose.Schema;
  const ObjectId = Schema.ObjectId;

  const Test = new Schema(
    {
      author: ObjectId,
      title: String
    },
    { 
      bufferCommands: false, capped: { size: 1000000, max: 100 }
    }
  );

  const testsModel = mongoose.model("test", Test);
  await testsModel.init();

We'll make some changes to the docs to make this more clear.

@vkarpov15 vkarpov15 added docs This issue is due to a mistake or omission in the mongoosejs.com documentation confirmed-bug We've confirmed this is a bug in Mongoose and will fix it. and removed needs repro script Maybe a bug, but no repro script. The issue reporter should create a script that demos the issue labels Feb 15, 2020
vkarpov15 added a commit that referenced this issue Feb 15, 2020
vkarpov15 added a commit that referenced this issue Feb 15, 2020
@abuzhynsky
Copy link
Author

Thanks for the help.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
confirmed-bug We've confirmed this is a bug in Mongoose and will fix it. docs This issue is due to a mistake or omission in the mongoosejs.com documentation
Projects
None yet
Development

No branches or pull requests

3 participants