-
Notifications
You must be signed in to change notification settings - Fork 192
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
Properly handle multiple requests to threaded REST API #3974
Properly handle multiple requests to threaded REST API #3974
Conversation
6d36526
to
0c4239f
Compare
Codecov Report
@@ Coverage Diff @@
## develop #3974 +/- ##
===========================================
+ Coverage 78.50% 78.57% +0.07%
===========================================
Files 461 461
Lines 34094 34095 +1
===========================================
+ Hits 26763 26787 +24
+ Misses 7331 7308 -23
Continue to review full report at Codecov.
|
d3ca10c
to
23b1a91
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot @CasperWA , looking good in general with a few minor things and a design question/suggestion.
Last thing: @sphuber, should the close_session decorator function be placed somewhere else in the code?
Any code path that will cause the QueryBuilder
to be constructed by a thread should have this. I have the feeling that the get
method would be a good place to catch all of these, but I am not sure. We should check with @waychal and @asle85
Also, would I then have to move the from aiida.manage.manager import get_manager import line into the finally: section of the close_session() function again, where it was originally placed, in order to not unintentionally load the AiiDA profile before it's truly time?
You can import the get_manager
function safely without having to have loaded a profile.
I didn't mean where to use the decorater, I believe this is already the best place where it is now.
Great! Thanks 👍 |
Ah I see, no it shouldn't go to |
I'll try and ask again then: Do you think the |
I have answered this in the PR comments themselves. I suggested defining the decorator on the |
23b1a91
to
6d71d4a
Compare
With the latest commits I have moved to the |
6d71d4a
to
b9f1075
Compare
@sphuber, it seems the fixture |
This is likely related to the discussion here. In essence, I think the issue is rooted in interference between the pytest fixtures and the remaining AiiDATestCase implementation.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost there @CasperWA thanks a lot. Just a few more minor things
Well that's crap. So what now? Should I undo my conversion to |
You could try using the
Please don't 😅 |
f9336c0
to
a86e84f
Compare
Not at all. There is a reason that SqlAlchemy provides a way to tweak these parameters. We just haven't exposed the same interface in AiiDA because so far it had not been necessary yet. It is fine to add this for the purposes of the tests. It would of course also open it up to users changing, but the |
6b279ca
to
5691668
Compare
All right! Also, I am unsure whether it was the switch to check the Finally, @sphuber, please review specifically whether my trailing of |
Update: Now to see if this is also the case with GH Actions ... |
However, it does seem that the whole traceback sometimes still doesn't get registered and captured in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot @CasperWA . Last thing I would ask is to adapt the docstrings as indicated. Once you have done that, please create a first separate commit that introduces the new kwargs
piping and then the second commit with the actual fix and tests on top, then I will merge it.
b711b1b
to
e780e3d
Compare
@sphuber Wait a second with merging - I just realized I should use our "new" |
95efcdd
to
acfe8ee
Compare
@sphuber All right, it seems to be all fixed with docs building etc. now. Also, I have updated the test and it seems to work. |
acfe8ee
to
e502170
Compare
The function `aiida.backends.utils.create_sqlalchemy_engine` now takes keyword arguments that are passed straight to the SQLAlchemy function `create_engine` which creates the engine that connects to the database. This utility function is used by the SQLAlchemy and by both backends for the `QueryBuilder`. By allowing these keyword arguments to be passed and plumbing them all the way in to the `Backend` class, the parameters of the SQLAlchemy engine and the associated queue pool can be customized. This is not meant to be used by end users, but can be important for important use, for testing and performance reasons.
This is needed due to the server running in threaded mode, i.e., creating a new thread for each incoming request. This concept is great for handling many requests, but crashes when used together with AiiDA's global singleton SQLA session used, no matter the backend of the profile by the `QueryBuilder`. Specifically, this leads to issues with the SQLA QueuePool, since the connections are not properly released when a thread is closed. This leads to unintended QueuePool overflow. This fix wraps all HTTP method requests and makes sure to close the current thread's SQLA session after the request as been completely handled. Use Flask-RESTful's integrated `Resource` attribute `method_decorators` to apply `close_session` wrapper to all and any HTTP request that may be requested of AiiDA's `BaseResource` (and its sub-classes). Additionally, remove the `__init__` function overwritten in `Node(BaseResource)`, since it is redundant, and the attributes `tclass` is not relevant with v4 (AiiDA v1.0.0 and above), but was never removed. It should have been removed when moving to v4 in 4ff2829. Concerning the added tests: the timeout needs to be set for Python 3.5 in order to stop the http socket and properly raise (and escape out of an infinite loop). The `capfd` fixture must be used, otherwise the exception cannot be properly captured. The tests were simplified into the pytest scheme with ideas from @sphuber and @greschd.
e502170
to
11130a4
Compare
Thanks a lot @CasperWA ! |
Fixes #3743
This PR implements the same fix as was used for
aiida-optimade
, wherein it closes the SQLAlchemy session used byQueryBuilder
after having run a method/function to handle aGET
request to the REST API.If this is not done, the session should(?) still eventually close, releasing connections made in the SQLA connection pool. However, this may take such a long time that when a new
GET
request comes in (or more precisely, whenever theQueryBuilder
is used), the newly created thread that handles the request cannot use the singleton session that AiiDA has, since the connections have not been released, and so it fails with the following message:At least that is how I remember the explanation @sphuber and I came up with some time ago. Hence @sphuber also cleaned up the whole session handling code in AiiDA and provided a public interface for closing the given session.
The major issue here is threading; the AiiDA SQLA session (that the
QueryBuilder
uses no matter the actual profile's database backend) is a singleton, and only meant to be used within the same (main) thread. Since the REST API runs threaded, as it should to accommodate multiple incoming requests effectively, the singleton AiiDA SQLA sessions design breaks down. However, by closing the session at the end of every request, the connections are immediately released and only in extreme cases will theQueuePool
ever reach is limit. In fact, one may argue that in those cases, it should complain, since something is probably wrong/you're flooding the database with too many requests.This issue was never actually tested with the current tests, since a threaded server is never started.
I have added a new test that starts the REST API server in a new
Thread
, issues 100 requests in serial, and finally shuts the server down.This resulted in a reproduction of the
QueuePool
error, which was then fixed after implementing my fix.A side note: Since the REST API (as far as I know, please back me up here @waychal) only serves data from the database, and never alters or creates data in the database, there is no risk of corrupting the database by bombarding the REST API with requests.
Last thing: @sphuber, should the
close_session
decorator function be placed somewhere else in the code? I think I would like to perhaps use this directly inaiida-optimade
as well, instead of re-implementing it.Also, would I then have to move the
from aiida.manage.manager import get_manager
import line into thefinally:
section of theclose_session()
function again, where it was originally placed, in order to not unintentionally load the AiiDA profile before it's truly time?