Normally - when there is no module that is screwing up - the function_exists() checks for 'callback' functions (like the '#process' callbacks for form elements) are totally obsolete.
We already do this for for example form submit/validate handlers; those are called without function_exists() check.
Dropping function_exists() might also improve debugging as you get feedback when you for example misnamed your function.
I also wrote a patch for #value_callback, but that one changes the the API so I didn't include it here: #946570: Use and call explicit #value_callback instead of magic form_type_TYPE_value()
Comment | File | Size | Author |
---|---|---|---|
#29 | drop-function_exists-for-callbacks-1059884-29.patch | 14.83 KB | pillarsdotnet |
#22 | drupal8.function-exists.22.patch | 14.19 KB | sun |
#7 | 1059884-7-without-theme-inc.patch | 15.22 KB | bfroehle |
func_e.patch | 15.94 KB | casey | |
Comments
Comment #1
erikwebb CreditAttribution: erikwebb commentedI think it's definitely a safe assumption to ignore function_exists(). However marginal the performance improvement, seems worthwhile. Philosophically, I agree that if a developer makes a mistake, it should be immediately obvious. A PHP error showing a improperly named function would certainly fit the bill - silent failures are bad for the developer and the end user.
Comment #2
bfroehle CreditAttribution: bfroehle commentedI support the idea, but have not reviewed the patch.
Comment #3
Dries CreditAttribution: Dries commentedAny idea on what the performance gain is? It is not clear how important of an improvement this is. Would be great to see some benchmarking.
Comment #4
catchIn the theme.inc example, is there a valid reason for the function not to exist?
I don't think there's going to be much gain here if any, especially since many places here are rarely called. However I support the idea of removing unnecessary checks since it's a bad pattern that people will copy elsewhere - same as our constant file_exists() checks all over the place.
Comment #5
moshe weitzman CreditAttribution: moshe weitzman commentedyeah, all except for theme.inc look safe.
Comment #6
pillarsdotnet CreditAttribution: pillarsdotnet commentedMarking #994992: _menu_check_access() does not warn when the access callback does not exist as a duplicate.
Comment #7
bfroehle CreditAttribution: bfroehle commentedI've attached the original patch, except the changes in theme.inc (which I think are necessary).
I think this is generally a good idea, not for performance, but so that other issues (like the one pillarsdotnet linked to above) aren't swept under the rug. This, for example, would catch the case of your callback not being called because of a slight typographical error.
Comment #8
David_Rothstein CreditAttribution: David_Rothstein commentedYes, this patch is a good idea - even if it doesn't wind up helping performance.
I ran into an example of this working on a Views patch (#1104860: Breadcrumbs are broken for clone/analyze/export/etc.). I had a situation where the title callback function for some menu items didn't exist (because it was in a file that wasn't loaded). If I had gotten an error message I would have figured out what was wrong in about 10 seconds... Instead, Drupal silently ignored it and just wound up not giving the menu items a title at all (so that they resulted in empty entries being printed in the page's breadcrumb), which is nonsensical and was a whole lot harder to track down.
Comment #9
Dries CreditAttribution: Dries commentedI'm still in support of this patch so I'd love to see someone revisit this patch -- and maybe do some benchmarking too.
Comment #10
chx CreditAttribution: chx commentedyessir!
Comment #11
sunThis change is going to bite us in the a**, just like the registry has led to unrecoverable (and actually unresolvable) states already.
Of course, the function_exists() checks are entirely superfluous on production sites on which nothing is going to change. However, in all other scenarios, totally not limited to but especially staging, module updates, etc, these checks are useful and actually required in order to have Drupal not die.
Hence, I'd recommend, instead of removing the conditions, turn them into fast OR conditions, so production sites can get the performance they deserve, and everything else works like now:
Alternatively, if variable_get() is too slow and inflexible, use a global:
Comment #12
Crell CreditAttribution: Crell commentedI agree that silent failure is a very bad thing for all involved. I don't expect any notable performance difference either way.
sun, why is it going to bite us? The registry can, I admit, lead to states where it thinks a class exists but it doesn't, or vice versa, and that's hard to recover from. We have no function registry, though, so I don't see where this would blow up on us. Have you an example?
If there is a danger of unrecoverable explosion here, then we shouldn't abandon this patch. Instead, the next step would be to replace:
with:
That would avoid explosion but still give us a nice obvious error to aid debugging. I would wait to see if we can find a deadlock condition, though, before we go that route.
Comment #13
irakli CreditAttribution: irakli commentedA rudimentary performance benchmark:
which returns: "Elapsed time: 0.0025150775909424 seconds" i.e. 1,000 function_exists() calls only add up to 0.00251 of a second on a dual-core 2.13GHz Intel with 4GB RAM and ~ 0.00148 seconds when run on a 8-core 32GB RAM server.
So performance difference is indeed negligible.
That said, I do join my vote to the notion of: sweeping a non-existant callback under a rug being a bad pattern, and really like the solution by Crell in #12
Comment #14
chx CreditAttribution: chx commentedI can't benchmark this in a way to show a meaningful difference. I created xdebug trace files on the Examiner dev server. On a single node, I see 1887 vs 1959 function_exists calls. On a list page, I count 792 vs 830. That's just impossible to benchmark (for the latter, there are close to 15K function calls, so we got rid of 0.2% function calls and not all function calls are equal speed and these 0.2% are quite fast) . However ... while wanting to test this patch there, I could not log in because our mongodb session implementation silently fails if the database disk is full and it took some time to figure out why login does not work any more. Ironic, isn't. It shows clearly: silent fails are the death. So I think this is good to go, yes.
Comment #15
chx CreditAttribution: chx commentedOps, cross post, didnt see the others. Let's wait for sun.
Comment #16
sunTo clarify: the direct consequences of this patch are non-obvious. The testbot won't ever be able to catch or even test this kind of patch.
I'm definitely not interested in the module.inc or theme.inc changes, at all. I'm focusing on custom/dynamic and especially cached stuff; i.e., dynamic callbacks, #pre_render, #value_callbacks, anything. Now. Forget about the unimportant peripheral stuff. Forget about the cached stuff. Think about the stuff that relies on the cached stuff, or think about stuff that depends on the cached stuff. What I'm after is 3D or even 4D, both just simply natural horizons of Drupal.
Given that performance is not the goal for this issue, we have to focus on silent failure. Good goal. However, silent failure is not a problem until failure (death) becomes a problem. #996236: drupal_flush_all_caches() does not clear entity info cache clarifies this. If we think one-dimensional, we ignore the possible problems. Drupal is more than 1D, more than 2D, and sometimes even more than 3D. That's why aforementioned issue most likely cannot be resolved for D7, since our approach to "flushing" attempts to do two very different things at the same time: clearing + (re)building.
In order to be sure. In order to be sure that every single function, that we intend to call, actually exists, and in order to ensure that no whatever functionality depends on any function that may or may not depend on yet another function, which may or may not exist, we need to make sure that all data that we are trying to build relies on information that is not outdated and cannot rely on any information or function that may not exist. At any point in time. Regardless of whether we're doing updates, flushing caches, or executing regular runtime behavior.
On a personal remark, TBH, I never had any issues with missing or improperly defined functions or callbacks, in my entire Drupal development history. What attracted me to this issue was the performance improvement idea, but given the actual numbers, performance is no longer an argument here. Only the silent failure remains, but frankly, in my entire dev history, I immediately recognized that my particular function wasn't called, or, I may not even have noticed, but who on earth cares if I didn't expect more functions to be called, if there's no performance penalty involved either way?
Comment #17
chx CreditAttribution: chx commentedsun is a bit vague here and I won't be much better off but let me try: you change code. Now if we fatal on a function non existing, then either because you removed something that was cached. Or when you try to rebuild only then we fatal out. That certainly can be a problem. I would strongly support #12.
Comment #18
catchcall_user_func() and friends throw a warning 'must be a valid callback', not fatal errors, I don't see a reason not to use that here (except for not very useful call graphs, but that'd be the same problem if we introduced a wrapper around function_exists()/trigger_error() too).
This also points to the need for a 'fix my broken site' button like #534594: [meta] system info cache writing and registry rebuilding is broken without a full bootstrap.
Comment #19
David_Rothstein CreditAttribution: David_Rothstein commentedRight, many of the functions in the patch are already using something like call_user_func()... and many of the others aren't ever cached (so wouldn't suffer from this problem anyway).
So the basic approach in the patch seems fine, but we'd just need to look more carefully at some of the individual cases.
Comment #20
David_Rothstein CreditAttribution: David_Rothstein commentedComment #21
xjmA specific use-case for
function_exists()
for a callback: #1281732-4: Fatal error when taxonomy_options_list() tries to call an undefined callback function.Comment #22
sunI'm pretty sure this will blow up, but we still have sufficient time to test and eventually roll it back; so let's do it.
Removing the Performance tag, as benchmarks already showed that there is no real gain.
Adding API clean-up tag instead, as the main idea is to not silently ignore the error when a registered/expected callback cannot be called.
Comment #23
Damien Tournoud CreditAttribution: Damien Tournoud commentedA fatal error (or at least an exception) would be the proper response here. If a validation function does not exist, for example because a file is not loaded, you might end up submit a form that should not have been submitted.
Comment #24
moshe weitzman CreditAttribution: moshe weitzman commentedtests pass, and DX is much better so RTBC
Comment #25
Dries CreditAttribution: Dries commentedWorks for me! Committed to 8.x. Thanks.
Comment #26
David_Rothstein CreditAttribution: David_Rothstein commentedSo is the idea that we're deferring the "trigger a user warning" vs. "throw a fatal error" discussion to another issue?
By not addressing it, the patch wound up naturally doing a little bit of both, but completely randomly... I guess either option is better than the previous behavior of failing silently, but it seems a little odd to have this be inconsistent with no particular rhyme or reason.
Comment #28
pillarsdotnet CreditAttribution: pillarsdotnet commentedShould the patch in #22 should be backported to 7.x, or should an alternative approach be used?
Comment #29
pillarsdotnet CreditAttribution: pillarsdotnet commentedRe-rolled for D7.
Comment #30
xjmHmm, I am not sure about backporting something like this. While it is better DX, it is reminiscent of #1067750: Let Field API fail in a tale-telling way on invalid $entity which made a lot of folks sites' explode and which we still get complaints and issues about eight months later.
Comment #31
pillarsdotnet CreditAttribution: pillarsdotnet commentedNo problem either way; was mainly looking for an administrative decision on whether a backport was the way to go, so I know how to proceed in #994992: _menu_check_access() does not warn when the access callback does not exist.
Comment #32
Vacilando CreditAttribution: Vacilando commentedIn a D7 site I am optimizing, 12% of the exclusive wall time is spent calling function_exists() from _theme_process_registry(). In total, there are almost 400k calls...
So I applied the backported D7 patch from #29 and did the same measurements. Unfortunately, the number of function_exists() calls is virtually unchanged. I wonder, is there anything wrong with the backport or is this not a solution for the problem I face?
Comment #33
erikwebb CreditAttribution: erikwebb commentedThe
function_exists()
calls from_theme_process_registry()
are perfectly normal. This is caused by the theme registry being rebuilt is unrelated to this issue.Comment #34
sunThis patch will not be backported.