Comments

johnalbin’s picture

Status: Active » Needs review
StatusFileSize
new853 bytes

Here's a simple-ish patch to fix. I think. (my D7 test enviro just went ka-blooey, so I can't test.)

Status: Needs review » Needs work

The last submitted patch, module-invoke-fail-752226-1.patch, failed testing.

johnalbin’s picture

Priority: Critical » Normal
Status: Needs work » Needs review
StatusFileSize
new837 bytes

Whoops. && instead of ||

Also, I guess this isn't critical since its just the API that's broken not functionality.

johnalbin’s picture

StatusFileSize
new839 bytes

Need parens around $hook_info = module_hook_info() since its in a conditional expression.

Got my test environ back up, so this patch is actually tested. :-)

Status: Needs review » Needs work

The last submitted patch, module-invoke-fail-752226-4.patch, failed testing.

johnalbin’s picture

Status: Needs work » Needs review

File field test??? I don't think that's related to this patch. :-\

johnalbin’s picture

#4: module-invoke-fail-752226-4.patch queued for re-testing.

Status: Needs review » Needs work

The last submitted patch, module-invoke-fail-752226-4.patch, failed testing.

Crell’s picture

Subscribing. Will try to look at this later.

Stevel’s picture

Status: Needs work » Needs review

#4: module-invoke-fail-752226-4.patch queued for re-testing.

Status: Needs review » Needs work

The last submitted patch, module-invoke-fail-752226-4.patch, failed testing.

Stevel’s picture

Status: Needs work » Needs review

#4: module-invoke-fail-752226-4.patch queued for re-testing.

The errors seem unrelated, not sure what is going on here...

Status: Needs review » Needs work

The last submitted patch, module-invoke-fail-752226-4.patch, failed testing.

sun’s picture

Title: module_invoke doesn't work with hooks placed in .inc files with hook_hook_info() » module_invoke() doesn't work with hooks placed in include files with hook_hook_info()
Priority: Normal » Major
Status: Needs work » Needs review
StatusFileSize
new1.49 KB

Cleaned up the code.

Status: Needs review » Needs work

The last submitted patch, drupal.module-invoke-include.14.patch, failed testing.

sun’s picture

Status: Needs work » Needs review
StatusFileSize
new1.52 KB

oopsie.

sun’s picture

Also note that #977052: Implement hook_hook_info() for Field API hooks depends on this patch now.

Status: Needs review » Needs work

The last submitted patch, drupal.module-invoke-include.16.patch, failed testing.

sun’s picture

I seriously have no idea how this patch is able to break those two tests.

sun’s picture

Assigned: Unassigned » sun
sun’s picture

Title: module_invoke() doesn't work with hooks placed in include files with hook_hook_info() » module_invoke[_all]() doesn't work with hooks placed in include files via hook_hook_info()
Priority: Major » Critical
StatusFileSize
new2.22 KB

hoho-holy cow! This also applies to module_invoke_all().

Plus, it goes even further... module_hook_info() is always empty.

That is, because module_hook_info() is statically cached. module_implements() or something else in the bootstrap process leads to an early invocation of module_hook_info(), which preemptively sets the static cache to an always empty array.

sun’s picture

Title: module_invoke[_all]() doesn't work with hooks placed in include files via hook_hook_info() » module_invoke() doesn't work with hooks placed in include files via hook_hook_info()
Status: Needs work » Needs review
StatusFileSize
new2.42 KB

Alright, module_invoke_all() uses module_implements() already, so hook_hook_info() is already invoked. It didn't work previously, because of the already primed module_hook_info() static cache.

sun’s picture

Alright, last patch passed.

The change to bootstrap_invoke_all() implies that none of the bootstrap hooks are allowed to use module_invoke() either.

If we do not want to go with this potential WTF, then I'd recommend to make module_hook_info() use a bootstrap-phase-specific $cid for both the static cache and the database cache; i.e.,

$cid = __FUNCTION__ . ':' . drupal_get_bootstrap_phase();
$hook_info = &drupal_static($cid);
...
sun’s picture

I've implemented the suggestion in #23 over in #977052-13: Implement hook_hook_info() for Field API hooks

sun’s picture

Status: Needs review » Needs work
carlos8f’s picture

Crazy. subscribing

sun’s picture

Status: Needs work » Needs review
StatusFileSize
new2.3 KB

Extracting revised, relevant parts from #977052: Implement hook_hook_info() for Field API hooks -- which proves to be the actual test case for this functionality.

Anonymous’s picture

subscribe.

chx’s picture

And so dies the last simple function in core. Hooks were supposed to be a simple concept. Can we roll back hook_hook_info?

chx’s picture

As I think more and more , actually rolling back is a very enticing option.

Here is a question: does $module implement $hook? In Drupal 6 you checked for $module .'_'. $hook in $module.module. In Drupal 7 you check for $module . '_' . $hook in $module or $module.$group.inc where $group is defined in $module . '_hook_info' defined in $module.module.

Yes. This is very useful functionality for contributed modules that implement support code for other, non-required contributed modules and load the latter only on demand. However, the possibility of doing that comes at the cost outlined in the previous paragraph.

Decide.

moshe weitzman’s picture

I'd be fine rolling back hook_hook_info. I was initially for it but we are complexifying too fast.

Status: Needs review » Needs work

The last submitted patch, drupal.module-invoke-include.27.patch, failed testing.

EvanDonovan’s picture

As an intermediate Drupal developer, the concept of hook_hook_info() seems like overkill to me. Rather than fixing it at this stage in the game, can it just be removed?

carlos8f’s picture

I would like to keep hook_hook_info(). For large modules, the .module file easily becomes an unmanageable dumping ground of hooks, and to organize it one has to create hook wrappers which module_load_include() and run _hook_actual_hook_implementation(). This is a worse situation and hook_hook_info() can help simplify all that. A huge .module file is also terrible for memory usage. That was acceptable in the simple days of D5, but D7 is a beast and we should be saving memory where we can.

chx’s picture

Issue tags: +Needs committer feedback

Asking for committer feedback.

carlos8f’s picture

Status: Needs work » Needs review
StatusFileSize
new6.07 KB

I've merged #27 with some baseline test coverage for dynamic hook loading with both module_invoke() and module_invoke_all(). We still have failing tests for tokens and testModuleImplements().

I took some debugging adventures into this problem, but there are so many caching layers and edge situations that this could be a really tough problem to fix correctly. I would still love to fix this hook, though. It's just somewhat of a tightrope walk.

Setting to CNR for bot (should show that module_invoke() is broken while module_invoke_all() is OK).

carlos8f’s picture

Status: Needs review » Needs work

Back to CNW to fix the tests.

webchick’s picture

Issue tags: -Needs committer feedback

hook_hook_info() has been in core since 2008, iirc. We're not pulling it out now.

carlos8f’s picture

Status: Needs work » Needs review
StatusFileSize
new6.32 KB

Modified the tests to pull module_implements() into its own method (so caching doesn't interfere with the test), and added the expectation that module_implements() shouldn't actually load the include, only report that the hook is implemented (should reference hook_hook_info() instead of function_exists()). I think that is a reasonable expectation and a requirement of lazy-loading until invoke() calls.

@webchick: hook_hook_info() had a very different function < D7, and was renamed to hook_trigger_info() in D7. See http://drupal.org/update/modules/6/7#trigger_overhaul

webchick’s picture

Yes, I'm aware what hook_hook_info() is and does. :P

The fact remains, it's a very old change, contrib modules are making use of it, we can't remove it.

carlos8f’s picture

Status: Needs review » Needs work
StatusFileSize
new5.66 KB
new12.97 KB

Hmm.. I have some work in progress here.

SimpleTest is having a problem running drupal_install_system(), dying on a cache_set() because MergeQuery_mysql class is not found. That doesn't make sense because MergeQuery_mysql isn't even implemented and should be falling back to MergeQuery via DatabaseConnection::getDriverClass(). I've attached the call stack for reference.

I removed the module_implements() expectation from the tests, because function_exists() has to be checked on a cache miss, but on a cache hit the include should not need to be loaded. Split the function_exists() part out of module_hook() into a new function called module_load_hook(), which takes care of lazy loading the include and conveniently returns the name of the function to run if found.

chx’s picture

Issue tags: +Needs committer feedback

webchick, I do not know what mess is in CVS but that change was introduced a year ago http://drupal.org/node/588766#comment-2157220 http://drupalcode.org/viewvc/drupal/drupal/modules/system/system.module?...

Now that I look at the issue, it seems that it never got a proper discussion and I was really unhappy even back then. Code freeze, what code freeze?

The old hook_hook_info() is now called hook_trigger_info().

chx’s picture

Status: Needs work » Needs review
StatusFileSize
new7.18 KB

Here is a patch.

carlos8f’s picture

StatusFileSize
new6.42 KB

I got this to work.

chx’s picture

Note that the bug described in http://drupal.org/node/588766#comment-2463764 as far as I can see, is still a problem (ie if views module wants to provide node specific hook implementations in node.views.inc then module_load_include will look inside node module directory). Note that this not easily fixable!

Also the "ambush" is also a real possibility -- namely, if two modules happen to define the same dynamic hook (I used hook_form_$form_alter as an example) into different groups.

carlos8f’s picture

StatusFileSize
new6.49 KB

This one has a more correct static cache for module_hook_info().

Stevel’s picture

@chx: Implementing hooks 'on behalf of another module' should be done just like implementing hooks for the module itself, i.e. the hook implmentation should begin with the own modules name, not the module the implementation references. In your example about node.views.inc, views module should just place it's hooks in views.views.inc in that case, and place views_...-hooks in there, not start putting node_...-hook implementations in views.module. Otherwise we really get a mess, and we could set an example for contrib to end up with potentially duplicate function names.

sun’s picture

Issue tags: -Needs committer feedback
StatusFileSize
new7.52 KB

Thanks for adding tests. Partially reverted to module_hook(), because we do not want to introduce a new API at this point.

Kept the idea of returning the $function name instead of TRUE, since that really makes sense and avoids needless subsequent string concatenations.

In the end, I don't think it makes sense to support hook groups/include files prior to full bootstrap, because not all subsystems and modules are loaded yet, so the environment in which code is executed is very limited anyway. Therefore, attached patch shortcuts the pre-full-boostrap case.

chx’s picture

@Stevel care to check the code? When you module_invoke('node', 'views_whatever') then it will module_load_include('inc', 'node', 'node.views'). That, in turn becomes drupal_get_path('module', 'node') . "/node.views.inc"; That's the bug, plainly.

chx’s picture

Status: Needs review » Needs work

Even if we are to keep this (it's crystal clear from the DBTNG and this issue that we do not want Drupal code easily discoverable any more) that does not mean that we can change the function signature of module_hook.

Anonymous’s picture

re. #48, i don't get the "Since module_hook() may needlessly try to load the include file again, function_exists() is used directly here." comments. why bother doing the check on group and loading the file, if that leads to use needing to comment the code and avoid module_hook()? why not just use this for the cache miss:

    foreach ($list as $module) {      
      if (module_hook($module, $hook)) {
        $implementations[$hook][$module] = isset($hook_info[$hook]['group']) : $hook_info[$hook]['group'] ? FALSE;
      }
    }

and this for the cache hit:

    foreach ($implementations[$hook] as $module => $group) {
      // It is possible that a module removed a hook implementation without the
      // implementations cache being rebuilt yet, so we check whether the
      // function exists on each request to avoid undefined function errors.
      if (!module_hook($module, $hook)) {
        // Clear out the stale implementation from the cache and force a cache
        // refresh to forget about no longer existing hook implementations.
        unset($implementations[$hook][$module]);
        $implementations['#write_cache'] = TRUE;
      } 
    } 
sun’s picture

@justinrandell: Performance-wise, what module_implements() does would equal a module_hook_multiple() - i.e., instead of checking each module separately, it checks once whether the hook has a registered group, and if it does, it loads the corresponding include files.

Anonymous’s picture

@sun - thanks for the reply, but i still don't get it :-( can you unpack that a bit more?

carlos8f’s picture

I'm against changing the function return of module_hook(). That was intended for the new module_load_hook() function only.

And I think allowing boot modules to use includes is ok: when boot modules call invoke() their includes and the includes of other boot modules should be honored. The patch in #46 allows this, but we might want to add a separate static cache for the hook_boot() phase to support it.

sun’s picture

@justinrandell: module_implements() iterates over all enabled modules in a fast fashion. module_[load_]hook() separately invokes function_exists(), subsequently needs to re-read the cached module_hook_info() to check whether there's a group, if there is one, try to reload the module include file, and finally invoke function_exists() again. module_implements() already knows all of that upfront, so it conditionally checks that only once and performs the operation for all modules at once.

@carlos8f/all: A (string != '') === TRUE, so this tweak is subtle. It has a positive impact on modules that actually need to use module_hook(); you can see the actual difference in the last and second to last patch over in #977052: Implement hook_hook_info() for Field API hooks. I highly doubt that there is any code that actually checks module_hook('foo', 'bar') === TRUE, since module_hook() can only return TRUE or FALSE thus far, so a type-agnostic comparison would be ill in the first place -- however, no strong feeling about that, so if everyone insists, we can revert to TRUE/FALSE.

However, negative on introducing a new, separate module_load_hook(). It's plain confusing to have an API function module_hook() that doesn't respect its own API and developers suddenly need to use a separate module_load_hook() to actually do what module_hook() is intended to do: check whether a hook exists.

carlos8f’s picture

The more I think and look at this code the more I'm agreeing with @sun that the boot phase should not support lazy includes. It's too much meta data to be loading and caching when we don't even have the benefit of common.inc. So I'm OK with hook_hook_info() returning array() during the boot phase, and #48 looks OK in that light.

@justinrandell, the answer to #51: that because module_implements() calls hook_hook_info() before the foreach(), it follows that hook_hook_info() called again by module_hook() inside the aforementioned foreach would be overkill, and we can just replace with function_exists().

I think this just needs a reroll to revert the module_hook() return value (it's completely unessential to the patch and therefore doesn't warrant even a slight API change) then I would say it's RTBC.

On a slightly related note, there is also a bit of oddness that I am gradually becoming more aware of -- module_load_include() (and friends) is dependent on common.inc, because it uses drupal_get_path() as apposed to directly using drupal_get_filename(). That means that during the bootstrap_invoke_all() phase module_load_include() is broken. As is drupal_get_path(), a very oft-used function. That bug has existed for quite some time, since in a D6 site using module_load_include() in the global space normally works, but not once you implement hook_boot(). I don't see a good reason why we don't just move drupal_get_path() to bootstrap.inc, which would make module.inc less dependent on a full bootstrap.

Anonymous’s picture

@sun and @carlos8f - ok, i see your points. i was kind of hoping there was something else, because if we want to look at this primarily from the point of view of saving cycles, there's more we can do to make it faster (and uglier). i'd be more comfortable with cleaner and not measurably slower, but i'm not going to fight about it.

i agree with #56.

sun’s picture

Status: Needs work » Needs review
StatusFileSize
new7.06 KB

Reverted to TRUE/FALSE, slightly improved comments.

sun’s picture

Latest patch passed. I've updated the patch in #977052: Implement hook_hook_info() for Field API hooks with this code and it works, too.

carlos8f’s picture

Status: Needs review » Reviewed & tested by the community

A minimal bugfix, tests, and a pending use case. That sounds like a plan.

dries’s picture

I personally was never a big fan of the 'group' feature -- it is hard to discover. In core, it seems to be used by the system.token.inc only. We could add a @todo to remove it in D8 -- that would hopefully prevent people from using it in contrib.

webchick’s picture

Well, contrib is already making pretty extensive use of this pattern. For example, almost all of View's hooks go into a module.views.inc, afaik rules has a module.rules.inc, tokens has module.token.inc, etc. (which is how that ended up in core)

It actually helps for discoverability, in my experience, because I can download a module and do "ls" to find out "Oh, ok. This module provides views and token support." When we teach students in Lullabot workshops how to add views integration to their modules, I've never heard anyone confused about "Why does this need to go into a separate file?" It actually makes a lot of sense; per-module is a perfectly logical way to break up the code. So I'm not sure I'd put @todo: remove around this, personally...

And core would be making more use of this, but #345118: Performance: Split .module files by moving hooks was essentially won't fixed by you in http://drupal.org/node/345118#comment-1645264 due to it adding increased complexity on "where do I find that hook?" for minimal gain in only the most sub-optimal of server setups. I think we left with the consensus of "Let's just add the mechanism for now, see if we can make better use of it in D8."

Crell’s picture

Dries: hook_hook_info() is used only by token in core because the other obvious hooks people didn't want to bother moving at the time. It is, primarily, a performance improvement.

For Drupal 6, we managed a 20% performance improvement by moving page callbacks out into lazy-load files. That was the single biggest performance boost we got in Drupal 6, which was nonetheless still slower than Drupal 5.

For Drupal 7, we first tried to use the registry for lazy-loading hooks to reduce our code weight. The overhead from that proved too high so it was removed. hook_hook_info() was the next-best-thing: A clear, straightforward, uniform way to group code in a lazy-load-friendly way to greatly reduce code weight, and a nice DX improvement as a side-effect because you don't have 4000 line files that are impossible to read and cause some IDEs to choke because they're just too damned big.

There was discussion of hook_hook_info() for doing per-hook weights, too, but that never materialized in D7. But the hook was structured in a forward-looking way specifically for that purpose.

We've known for over 3 years, with benchmarks and profiling data, that the two slowest parts of Drupal are bootstrapping all of our code off of disk and the theme/rendering layer. We knew that before Drupal 6 was released. Yet both of those got *slower* in Drupal 7 because we layered more code into the core system and more complex code into the theme system. And yet the typical page load still does not use the vast majority of the code we load off of disk. We have only made a bad problem worse.

Splitting less-used hooks off to lazy-load files is not something core invented, but it is the only way forward I can see to reduce our code bloat short of a massive rearchitecture. In your SF keynote you noted that 95% of Drupal sites still run on cheap-ass-shared-hosting, or run by a sysadmin who barely knows what he's doing. Those sites don't have APC available to them, and so will get *killed* by our wasteful code weight. I already consider Drupal 7 incompatible with shared hosting for this reason, and we are going to get eviscerated in the market for that.

Really, have only 3 options:

1) Increase our use of manual-lazy-load operations for indirect procedural code (hooks, theme functions, etc.) to reduce our baseline code weight.

2) Retool most of Drupal to be heavily OO and use PHP autoload for lazy-loading (and hope our autoload implementation can handle that).

3) Add a hard-requirement on APC to the installer and just refuse to install without it, thus cutting out the vast majority of web hosts.

I hate to sound all doomsday-ish, but that's how I see it right now. The current "load all 100,000 lines of code every page request and use 2000 of them" approach is simply not sustainable.

So yeah, in short "discourage people from using hook_hook_info() in contrib" translates to "encourage people to write bloated code that doesn't run on shared hosts." That's a situation of our own making. If anything, we need to be using it a lot *more* in core than we are now, not less.

Crell’s picture

If I'm reading #58 directly, it reads essentially "if you're not in full bootstrap mode, all of this lazy-load magic doesn't work, sorry, kthxbye, otherwise it's as before". Is that right? If so, I am fine with that restriction. That's where most of our hooks are called anyway.

Anonymous’s picture

Crell: yep, that's about right. if you want a bootstrap hook to get called, its needs to be in .module.

carlos8f’s picture

I'd like to add a 4th option to @Crell's list of options (for D8):

Addressing the case of hooks (theme functions might follow a similar pattern), module_implements(), instead of just returning a flat list of modules that implement a hook, returns an associative array (module => path_to_implementation). This could be discovered by the following pattern:

1. check the global space (includes .module file)
2. check in $module.$group.inc
3. check $info['files']

Once function_exists(), the path can be discovered automatically with ReflectionFunction::getFileName(), and cached. That way, whenever the hook needs to be invoked, it can be loaded knowing the exact location. The code doesn't need to load in any other case, except on a cache miss of module_implements(). Taking that further, any time there is a function name defined as a string in our code, the target file location should be discovered and cached.

I know this is very similar to the ill-fated registry, but perhaps more focused and practical. Knowing where functions are located in a vast codebase is a basic requirement of calling those functions. And let's please not assume APC; if we want to be friendly to folks with shared hosting. For that matter, APC has no shared cache when using fastcgi, so if you want to run PHP under nginx, Cherokee, etc., APC works clumsily at best.

carlos8f’s picture

StatusFileSize
new1.65 KB

Here's a demo that I wrote of the method above.

dries’s picture

Thanks for summarizing the history -- I must admit that my memory was a bit rusty. Reading Crell's comment, it makes more sense. While not pretty, it sounds like a reasonable compromise.

If anyone is up for it, I'd love to see both approaches benchmarked.

carlos8f’s picture

hmm, what is meant by "both" approaches? #58 is RTBC, that is the only approach we have right now.

sun’s picture

yeah. I also think we got slightly sidetracked here. This patch fixes API functionality that ought to work, but doesn't. We are ~3 days before RC1, so any talk about ideas or better concepts is out of scope right now. Happy to discuss for D8.

Anonymous’s picture

maybe Dries is talking about #51?

dries’s picture

With the other approach I meant #67. Probably not D7 material.

sun’s picture

So can we move forward here and fix this issue? #977052: Implement hook_hook_info() for Field API hooks depends on this patch.

Anonymous’s picture

Dries, thanks for clarifying. i think #67 is an awesome idea to flesh out, but definitely not D7 material.

cosmicdreams’s picture

Sounds like this patch would be a good one to commit. Is there anything I can do to help that along?

carlos8f’s picture

#67 is just a peek at what we can try in D8. It was part of a patch I had been optimistically writing for this issue (sharing concerns with others that hook_hook_info() is not totally useful) but it soon became clear that the whole module system would need a rewrite for it to really work :)

#58 is still RTBC.

dries’s picture

Status: Reviewed & tested by the community » Fixed

Committed the patch in #58. Let's come back to this issue once we start work on Drupal 8.

Crell’s picture

I opened a ticket for D8 discussion on this matter: #983470: Improve lazy-load mechanism for procedural code

chx’s picture

So have died the last simple concept in Drupal. RIP.

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.