flag_get_user_flags() is an API function which retrieves all the flagging records (ie rows from {flagging} table) for a given user on a given entity. It statically caches its results.
It is called in several places by flag internals, because this means that when there are multiple flag that are applicable for an entity, the processing of the first flag warms up the cache for all the subsequent flags, and thus the {flagging} table is queried only once.
However, some methods in flag_flag work with a $uid parameter, which they set to 0 if the flag in question is global. They then pass this variable as the $uid parameter to flag_get_user_flags().
But flag_get_user_flags() knows to handle global flags in its query's conditions:
->condition(db_or()
->condition('uid', $uid)
->condition('uid', 0)
)
This means that passing in a 0 for the uid will correctly get the flagging records for global flags, but get nothing (or the wrong records?) for non-global flags, because we're not querying for the right uid. This query result is then put in the static cache with a key of 0 for the uid. And when a subsequent flag is rendered which is non-global, flag_get_user_flags() is called again this time with the correct $uid, and the static cache misses.
| Comment | File | Size | Author |
|---|---|---|---|
| #3 | interdiff-2486969-3-1.txt | 2.17 KB | Anonymous (not verified) |
| #3 | 2486969-3.flag_.flag_get_user_flags-global-flag-cache-miss.patch | 1.89 KB | Anonymous (not verified) |
| #1 | 2486969-1.flag_.flag_get_user_flags-global-flag-cache-miss.patch | 1.96 KB | joachim |
Comments
Comment #1
joachim commentedPatch includes docs fix and clarification to is_flagged() to state explicitly that the $uid parameter should not be doctored for global flags. I've also added docs to _is_flagged() as that works the opposite way!
Comment #2
joachim commentedComment #3
Anonymous (not verified) commentedThis patch didn't apply as the parameter text had been removed and all that was left was "Parameters are the same as is_flagged()'s.".
I re-instated the parameters & applied the code from the patch and removed the now presumingly incorrect "Parameters are the same as is_flagged()'s.".
I'm unsure as to the workings of this patch - it seems to provide the ability to send through a second uid parameter with the session variable and is said to be called in many places but the code for those hasn't changed at all so presume this patch isn't covering that.
The functionality for calculating the uid and sid is removed with this patch, so I'm presuming enabling this means that doesn't need to be done any more as you're not having to assume defaults. I don't know the knock-on effect of this to the system as it is at the moment, but as for a patch review, it works and applies now ;)
Comment #4
joachim commentedArgh, I was sure when I wrote the summary that it made sense and was clear, and now it makes my head hurt :(
Ok so the problem here is that the flag class's method get_flagging_record() tries to be clever with the $uid parameter: if the flag is global, it sets the $uid parameter to zero, whatever you passed in. In a way, that makes sense, because in the database, a global flag's flaggings all have uid set to 0.
However, this 0 is passed to flag_get_user_flags().
Now flag_get_user_flags() has an internal static cache, which is loaded up once per user/entity combination. In other words, the first time you visit this for a given user and entity, data for this user and entity for ALL flags is loaded into the static cache:
So what can happen is this:
- Current user is logged-in, so uid is non-0.
- First flag on the node is global.
-- Flag link rendering needs to know what to render, so it calls is_flagged(), which calls get_flagging_record()
-- get_flagging_record() hacks the $uid to 0 because the flag is global
-- flag_get_user_flags() gets the $uid of 0, and so populates the internal cache at the 0.
- Second flag on the node is non-global
-- Flag link rendering needs to know what to render, so it calls is_flagged(), which calls get_flagging_record()
-- get_flagging_record() does NOT hack the $uid to 0 because the flag is non-global
-- flag_get_user_flags() gets the real $uid. It has no cache data for that, so has to query again: so we've had a cache miss, and we've had to query {flagging} twice instead of once.
Comment #5
joachim commentedSpotted something else:
Suppose we have a global and a non-global flag. The query above has a condition on the $sid, and for a global flag the sid column is always 0. This is fine if we're not using Session API, as without that module a user's $sid is always 0. But if we do have that, then the query won't work.
I'm not sure if it means that the static cache simply can't always cover everything, or that we have another bug here.
EDIT: we do, and I filed it already here: #2487003: possible bug with retrieving data for anon-flagged global flags in flag_get_user_flags().
Comment #6
joachim commentedHmm so it looks like the fixing the bug we're fixing here will expose this bug in the query, and we might actually get flags that don't render correctly.
So I'm going to remove the blocker tag from this, as d.org people have requested a new release for Flag and this one requires more thought.
Comment #7
joachim commentedComment #8
ivnishDrupal 7 is EOL. Issue will be closed, but patches are still here