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
Add 'mongo_client' argument to AgnosticClient #44
Conversation
Adding a 'mongo_client' argument to AgnosticClient enables tests to re-use a single Mongo client object and avoid the overhead of repeatedly recreating clients, along with their periodic threads.
Neat. Thanks for explaining the problem, that definitely sounds plausible. But I don't think that the "motor_client" parameter is the right approach. It's a powerful new feature that anyone could use, not just Motor's test suite, and if people use it then they bypass some logic that Motor needs to do: see the AgnosticClient constructor for Motor's current logic for creating a MongoClient. We want to preserve that logic and preserve Motor's freedom to change it, without worrying about users who have bypassed it with the "motor_client" parameter. I have another idea: MotorClient.close() calls MongoClient.close(), which signals the background thread(s) to terminate and then waits for them. Is it possible to call MotorClient.close() on every MotorClient the test suite creates, or at least on most of them, to prevent the accumulation of background threads? Most of the MotorClients and AsyncIOMotorClients created in the suite come from MotorTest.motor_client(), MotorTest.motor_rsc(), and the equivalent AsyncIOTestCase methods. There, try something like:
That should handle most of the test suite's clients. I believe addCleanup is available on all Python versions Motor supports. Will a technique like that solve the problem? |
@ajdavis Unfortunately, calling I created this example project to demonstrate the issue: https://github.com/tjensen/motor-test-example The example project requires Python 3.6 and includes about 2000 tests that use Motor to talk to MongoDB. Per your suggestion, the test To run the tests, run:
If you don't set The tests hang pretty reliably on my MacBook. I can break in to the Python process with
As you can see, in this case there were 1614 threads running at the time of the hang. Thank you for considering this PR. I understand the change I proposed may not align with the Motor's future direction, so any suggestions you can offer to solve this issue are appreciated. |
Jesse + Tim, I think pymongo could improve this situation by stopping the "pymongo_kill_cursors_thread" when a MongoClient is closed. Then the thread would be destroyed without the need for garbage collection. This also brings the kill cursors thread's lifetime more in line with the Topology's lifetime. I tested this theory using https://github.com/tjensen/motor-test-example and this pymongo branch and threads peeked around ~30 and remained stable for the entire nosetests run. Before the change I also saw the number of threads climb to over 1300+ however I could not reproduce a test hang. |
Shane thank you! Yeah, this sounds like a PyMongo bug: I'd expected MongoClient.close to stop and join all the background threads, not just the monitor threads. |
(By the way, I notice that I had misunderstood the original bug report. I see this is about Tim's application test suite, not Motor's self-tests.) |
This change adds a new
mongo_client
argument when creating a newMotorClient
orAsyncIOMotorClient
instance, setting the delegate object to a pre-existingMongoClient
instead of creating a new instance.Background
I help maintain a project that uses Motor and has a very large number of tests, the majority of which involve MongoDB accesses. These tests use Tornado's
AsyncTestCase
and create newMotorClient
instances in theirsetUp
methods. As the tests run, they become progressively slower and, especially on weaker computers, eventually hang. Attaching a debugger to the hung Python process shows that there are a large number of active threads, the number roughly corresponding to the number ofMotorClient
instances that were created, thus far. These threads appear to be running the PyMongo_process_periodic_tasks
code run through the periodic executor.To avoid the hanging issue and improve overall test performance, we would like to be able to re-use the same
MotorClient
instance in all tests that need database access but, sinceMotorClient
needs an IO loop and Tornado'sAsyncTestCase
creates a new IO loop for each test, that does not appear possible. A decent alternative is to re-use a singleMongoClient
instance for all tests and pass thatMongoClient
when creating a newMotorClient
in thesetUp
method.