-
-
Notifications
You must be signed in to change notification settings - Fork 826
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
CRM-19798: Memory leak in API3 EntityTag get operations #10844
Conversation
- The methods DB_common::execute and DB_common::query creates an instance of DB_result passing the result from the generalized method simpleQuery. The DB_result is added to a global array called $_DB_DATAOBJECT and it was never freed, causing a huge memory leak when using subsequent api calls.
Can one of the admins verify this patch? |
Jenkins ok to test |
@totten @colemanw do either of you want to review this, the screenshots suggest this is useful also ping @eileenmcnaughton |
This is good work & I did have a sneaking suspicion that memory leak had come back over time. Note that the change does affect a lot of pathways. I think @totten should take a look & we should also see if we can get some people to production test this one before we merge - maybe @systopia @xurizaemon @jaapjansma @seamuslee001 might be prepared to. (I'll see if I can too) |
Well, my first concern is a possible race condition to write in the $_DB_DATAOBJECT, since there's no control over the rights to access the variable, then I've assumed that there is no competition to access it. The second problem is exactly the amount of pathways this change affects, since I'm totally new to CiviCRM, someone with deeper knowledge can help to figure out possible caveats. P.S.: I probably earned a few levels with debugging PHP code in this issue >.<. |
I wonder if this supersedes the fix I did for CRM-20754. |
@spelcaster - yup, this is great investigative work. Trying to put this into context, maybe we can imagine this as a dialectic:
Now fast-forward a few years...
Purists might respond in a couple ways:
Neither of those are very satisfying -- in PHP, everyone expects cleanup to be automatic. Any exception requires active communication/education/enforcement. The patch in 10844 seems more pragmatic -- both scripts and APIs/BAOs/utilities can remain blissfully unaware of the cache layer. Education problem solved! What's the flipside? The correctness of the approach is still pretty subjective/use-case-dependent, eg
Anyway, that's some "thinking out loud" to understand the situation -- but I haven't actually formed an opinion. Hopefully some of this helps put it in perspective. |
TBH, I'm not sure the original idea behind DAO-cache is valid today. We've had many years of subsequent development in which folks introduced narrower caches all over the place (for things like option-values / pseudo-constants / settings-metadata -- using That's speculation -- I wish we had better SOP for benchmarking. One thing we can do... check the performance of the test suite. I tried running diff --git a/DB/DataObject.php b/DB/DataObject.php
index 0d74ece..e4b3243 100644
--- a/DB/DataObject.php
+++ b/DB/DataObject.php
@@ -4269,6 +4269,12 @@ class DB_DataObject extends DB_DataObject_Overload
}
+
+ function __destruct()
+ {
+ if (getenv('AUTOFREE')) { $this->free(); }
+ }
+
/**
* Free global arrays associated with this object.
* With caching enabled ( |
@totten The DB_DataObject uses the cache internally (see DB_DataObject::getDatabaseResult), if the cache will be removed, then the class will need a simple refactoring. But if the cache will be kept, because someone still need it, then calling the method free from the destructor based on the autofree flag is the way to go. |
I've been thinking about this and the big problem so far is that there is no So, I have a proposal to improve the query cache: // md5("select * from civicrm_membership") == fbc91bb5b1ece54e0b7a3d05a8572a0c
DB\Cache::queries[<query md5>] = [
'result' => DB/DB_result,
'table' => [
<list of tables used in the query>
],
'ttl' => <time to expire the cache>,
'counter' => <the number of times the cache can be used>
];
DB\Cache::tables[<table>] = [
<query md5> => <query md5> // just for fast access
] Here are some of the business rules:
This is the big problem, what if the result is destroyed while someone is updateIf DB_result::result was an array of associative arrays, we would not even need to |
May be interesting to compare how other PHP DB abstractions handle this:
|
@totten, I've done a slight change to test the results with a correct usage of a cache system, and the difference in execution speed is tremendous, I'm attaching the picture and the patches. |
My suspicion is the DAO caching doesn't work very well anyway - I have a feeling the CRM_Core_DAO::getFieldValue had it's own caching added sometime around 4.0 & the DBResult caching pre-dates that |
Applying this patch caused the following behaviour: https://civicrm.stackexchange.com/questions/21819/is-the-free-method-being-called-inadvertently-on-my-dataobject/21824#21824 |
Hi @michaelmcandrew, until now I didn't even know that this has been accepted, however, as I said In one of my comments above, the problem here is that the query results saved in the cache weren't being used for most part of the code. However, my guess is that you seem to have found a branch that uses this cache somehow, but the query result has been freed. |
@spelcaster - I don't think it has been accepted - at least this branch is not merged. I was working on a site that had manually applied the patch. |
Sounds like there is more work to do on this. Let's reopen it when there is a fix that addresses the bugs raised by @michaelmcandrew and the suggestions made by @totten. |
@spelcaster @MegaphoneJon @totten @michaelmcandrew I did some digging on this & came to the conclusion that the caching is NOT conferring any performance benefits. Similar to Tim, I tried various configs against Jenkins and the various runs did not perform statistically differently. Even the memory use was not notably different on jenkins (#11616 ran the test that demonstrated rapidly inflating memory usage locally) - but locally I got possible speed improvement & clear memory use improvement by Before Start :'49,864,896' After Start :'49,851,680' This improvement came from switching from a method that does not free the DAO to one that does in the function isMultilingual - #11614 I also tried by the DAO deconstruct method (#11615) . Both had the same impact here but I found a LOT of other places that were consuming excessive memory using the $dao->fetch() method or the executeQuery method. Regarding any theoretical benefit of the caching in the PEAR class - the ONLY 2 place that call getDatabaseResult are in functions that explictly free the DAO before returning (singleValueQuery) and fetchRow- and hence would not be affected as they have already freed it before deconstruct freeing happens. The executeQuery & fetch() methods are the main methods where the results are preserved and which hog memory. However, when the DAO object is deconstructed those results are still preserved but no longer accessible. They are the main places for memory issues. Further notes
|
FWIW, I agree that this should not be merged. From what I recall (and I didn't do a thorough investigation), this patch prevented me from working multiple objects that inherited from CRM_Core_DAO at the same time, which seems like a fairly common thing to want to do. |
@michaelmcandrew - could you test #11615 ? I actually removed a bunch of \CRM_Core_DAO::freeResult(); since I think there could be hidden cases of the problem you described & the change in that patch makes it redundant |
@eileenmcnaughton - i was debugging the issue was on a client's test infrastructure that was spun up for the purpose. It wouldn't be easy to get it up and running again and patched with this, unfortunately. |
Overview
The query results were kept in a global array and never freed, causing memory leak for multiple API calls in the same script.
Before
After
Comments