-
Notifications
You must be signed in to change notification settings - Fork 549
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
mysql2 gem improperly closing MySQL connections #606
Comments
Noticed this here also, and on older MySQL as well. Nice repro. |
Could you run the same tests against multiple gem versions - e.g. from 0.3.12 - 0.3.18, and see if it started at a particular release? |
Looks like it came in 0.3.16: 0.3.15
0.3.16
|
👍 -- same issue encountered when upgraded from 0.3.14 -> 0.3.18 ... resulted in too many connection errors on ruby 2.0.0 |
Hi, Is anyone else having an issue with this? I'm getting a lot of dead connection errors using Rails and Puma when load increases. Is the workaround to move down to 0.3.15 for the time being? I would help out but I have no experience of writing a C gem (not written any C in 20 years) - I will take a look though. |
This was an intentional change; previously, closing the connection (or even having a connection object get garbage-collected) in one process would close the connection in all other process which had also inherited the same open connection handle. This is because Is there actually a negative effect on your MySQL server? |
@sodabrew |
Ah, I didn't realize this would end up as a log line, I feel your pain on that. I wonder if there's a way to ask MySQL not to be noisy about this, and we can recommend that in the docs. |
Yes I'm disabling warning log competely for now, by |
Queries aren't necessary to reproduce. Opening and closing a connection with valid credentials will do the same. ruby -r mysql2 -e 'Mysql2::Client.new(host: "...", username: "...").close' With this I was able to see that version |
That's correct, we're no longer sending it. This is to avoid the situation where a connection is inherited into a child process and then gets garbage collected. The child process would be quitting the connection on behalf of the parent process. The motivation was actually to avoid the |
@sodabrew I consider the original change mis-informed, to say the least. When running in a server/client architecture like this, it is pretty safe to assume that when one side (the client) closes the connection, the server is no longer supposed to treat that connection as valid. The real answer to "how to stop closing a connection from affecting other processes" is to ensure that you don't share connections across processes, which is -- AFAIK -- an informed best-practice in any case. Changing the mysql2 gem in a questionable way in order to support people being able to share connections across processes seems like an inadvisable path. The log error/warning spam caused by this is extreme. I'd ask that the change be reverted, so that the mysql2 gem properly closes down a connection when it is asked to do so. |
I totally hear that argument, and I'm comfortable at least adding an option to control this behavior. Are you handy with this codebase to submit a PR to kick off the coding for it? |
@sodabrew Sure, I'm willing to kick things off. I'll see if I can spin something up over the next day or so. |
Awesome, thanks. Currently the master branch is about to become 0.4.0, with prepared statements support, and there's already one new flag ( |
@sodabrew I've been thinking about this a fair amount, though I haven't started writing any code yet. It is my understanding that what the implemented fix tries to solve is unexpected inheritance and side-effects of MySQL connections -- it is trying to ensure that principle of least surprise attaches to the behavior of the connections, such that if you open a connection in the parent, the child won't disconnect it. I'm wondering if the more correct implementation isn't to enforce "ownership" for the connection. Meaning, whenever MySQL2 has a connection, that connection is tagged with an "owner" (a PID). Then, when disconnecting/closing/being garbage collected, it either:
It's my understanding that this is how a number of other projects have pursued the same problem, so perhaps this is a better way forward than hard-enforcing either way (clean vs. unclean) or providing a hard-enforcement toggle to switch back and forth between them. Any thoughts? |
It has already been considered. The parent process created the connection, handed it to a child process, then garbage collected the connection. Garbage collection by the parent/owner was the original problem. |
@cbandy Ah, OK -- I'm just trying to make this work in a way that doesn't involve a straight configuration. What about detecting whether we are being garbage-collected or not; and doing a full shutdown if we are not being garbage-collected; and doing the unclean shutdown (without COM_QUIT) if we are being garbage-collected? There is still an edge case where the MySQL would throw errors/complain about aborted connections if connection objects are being garbage-collected without being properly closed first, but in that instance it would always be possible for the user of the library to "fix" the problem by explicitly closing the connection before the object is thrown away to be GC'ed. |
It's a good idea – having the explicit Client#close do a shutdown makes sense. It's still kind of surprising that explicit close would be cleaner than allowing the GC to work, but it's not terrible. Unfortunately this is a reasonably common pattern: A parent process opens a connection, and spawns a child process which uses that connection. If the parent processes exits and reaps the connection, now the child has a broken connection. If the child exits and reaps the connection, now the parent has a broken connection that it cannot hand to a different child process. One solution has been to recommend automatic reconnect, while solves the immediate problem of the broken connection but loses the performance benefits of keeping connections alive in a pool. A tuning flag to choose "loose" vs "correct" connection close behavior is probably still very useful. |
Sounds good.
Sounds good. Should the default be to send COM_QUIT or not? |
I think I've done this in After familiarizing myself with this code and the MySQL C API, I'm curious why we go to such great lengths to try and share copies of
|
The branch looks good, thanks for digging into this. I'll provide some connects on commit and you can open a PR when you're ready. We're not copying or hacking the MYSQL connection structure. A fork is a complete copy of the program that retains the same resources as the parent process. The problem that #463 went after is that if your program has some MySQL handles open, and then forks, the child may find it doesn't need those MySQL handles. So it garbage collects them. At that point the GC calls Client#close, which sends the COM_QUIT message and calls shutdown() on the socket -- but the effect of this is to break the connection for all users, including the parent process. |
Thank you for phrasing it yet another way for my thick head. Yes, we want to not Can you offer some guidance on the configuration interface? I'm thinking it should look very much like My strong opinion is that we should revert the current behavior so that any GC will send the |
@cbandy The terminate-on-close fork that you've done up looks exactly correct, thanks! Effectively what is being discussed now is a configuration interface that controls how connections that are being garbage-collected are disposed of, correct? I'm curious what the significant benefit of that configuration is, @sodabrew (i.e. when would it be used)? I've been thinking about it, and as much as I agree that it is best practice to not share FDs between parents and children; it is also a fairly common paradigm. At some level, having the GC only GC the object (and not tear down the MySQL connection) is correct as well, and at that point the error on the MySQL server is completely due to the connection not being properly closed with close() -- which the user should be doing anyway, correct? I feel like the configuration parameter is not entirely necessary, as the same effect can be achieved by people by just properly calling .close() on their mysql connections before allowing the object to be GC'ed. |
The posted change looks like it can drop right in as the only behavior. I need to think about this a little more, but I think it's clean. The fact is that if multiple processes share a single connection, they also cannot interleave their read/write on that connection, because the connection itself is stateful (i.e. can be in query results mode, can be in the middle of a transaction, etc.) |
The mysql2 gem doesn't properly close the connection, even when explicitly calling the 'client.close' method, resulting in increase of 'Aborted_clients' counter indicating an improper closure of the connection. My setup is MySQL 5.6.23 installed from MySQL's ubuntu repo on an Ubuntu Trusty EC2 instance, mysql2 gem v0.3.18, ruby 2.0.0p353 (embedded sensu version), libmysqlclient18
I have the following example script:
Which produces output like this, for both socket and TCP connections:
We can see the entry in the error log at the same time the script calls the 'client.close' method.
I have verified that the mysql gem does not increase the 'Aborted_clients' counter with the same script and linked libraries.
The text was updated successfully, but these errors were encountered: