Where it happens:
- enable the devel setting "display redirection page"
- visit the manage themes page (Site building > Themes), and change some of the checkboxes.
- submit
- click the redirection link.

Result:
- the checkboxes are still in the same state as before.
- phpmyadmin reveals that nothing has changed in the database either.

I did some debugging, and this is the conclusion:

system_theme_data() is called twice: Before and after system_themes_form_submit().
Each time it deletes all themes from the system table, and re-inserts the information from _system_theme_data().
_system_theme_data() caches that information in a static array, with the effect that it does not know about the updates from system_themes_form_submit().

This means, system_themes_form_submit() does its job correctly, but then system_theme_data() restores the old state.

For my taste this system would need a serious rewrite, but for D6 I'm afraid this would break too much stuff.
So, the easy and ugly solution for D6:
- allow to reset the static array in _system_theme_data(), using additional parameters
- system_themes_form_submit() should invoke a _system_theme_data() reset.
- Find other places that write on the {system} table, and make sure each of them does a _system_theme_data() reset.

For D7 I think some more thinking should be invested. All those static cache arrays are asking for trouble...

------------------------

Detailed information:

Debug backtrace for first call to _system_theme_data, before system_themes_form_submit():

_system_theme_data (Array, 4 elements) 
system_theme_data (Array, 4 elements) 
system_themes_form (Array, 2 elements) 
call_user_func_array (Array, 4 elements) 
drupal_retrieve_form (Array, 2 elements) 
drupal_get_form (Array, 2 elements) 
menu_execute_active_handler (Array, 4 elements)

Debug backtrace for second call to _system_theme_data, after system_themes_form_submit():

_system_theme_data (Array, 4 elements) 
system_theme_data (Array, 4 elements) 
update_get_projects (Array, 4 elements) 
update_get_available (Array, 4 elements) 
update_requirements (Array, 4 elements) 
update_help (Array, 2 elements) 
call_user_func_array (Array, 4 elements) 
module_invoke (Array, 4 elements) 
ctools_menu_get_active_help (Array, 4 elements) 
ctools_menu_help (Array, 2 elements) 
theme (Array, 4 elements) 
template_preprocess_page (Array, 2 elements) 
devel_exit (Array, 2 elements) 
module_invoke_all (Array, 4 elements) 
drupal_goto (Array, 4 elements) 
drupal_redirect_form (Array, 4 elements) 
drupal_process_form (Array, 4 elements) 
drupal_get_form (Array, 2 elements) 
menu_execute_active_handler (Array, 4 elements)
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

donquixote’s picture

donquixote’s picture

And here is the patch.

donquixote’s picture

Title: system_themes_form_submit() should reset the cache of _system_theme_data() » _system_theme_data() should clone the returned objects.

Wow, I noticed this is only the tip of an iceberg! And my first analysis was inaccurate.

The actual problem is this one:
_system_theme_data() returns an array of objects, and it caches that array.
Every time a function modifies any of the attributes of these objects, this can have a side effect.

In this particular case, the side effect happens in system_get_files_database() called from system_theme_update(). This function is supposed to get the current status and other information from the database, and write it to each of the themes in the given array. But it will only do so for those values that are not already set. If the function is called a second time in one request, with objects that have already been processed, then it will not have an effect.

The solution is quite obvious: Don't return the original objects!!
If the system has worked previously in PHP4, then adding a "clone" statement for PHP5 should not do any damage, I think. And it might eliminate a bunch of yet unreported side effects as well (that only apply to PHP5).

donquixote’s picture

And another patch..

donquixote’s picture

Status: Active » Needs review

I mark this as "needs review".
I leave it for D6 only, because D7 does not even have that function any more.

andypost’s picture

Version check should be replaced by drupal_clone()

klonos’s picture

subscribing

mathieu’s picture

I'm testing this on a site right now. Re-rolled the patch as per andypost's comment.

andypost’s picture

Version: 6.14 » 6.x-dev
Status: Needs review » Reviewed & tested by the community

Now it's ready

donquixote’s picture

Please have a look at the latest patches in #305653: Themes disabled during update, which suggest a different solution for D7.
I think the solution proposed here might be better for D6 because it changes less, but we (or the one who is reviewing it) should at least have a look.

andypost’s picture

@donquixote I'm tracking issue you are point to but I'm not sure about ability to backport it.

The David_Rothstein approach is using data from file-system with idea that there's not a lot of themes but it could break current D6 sites like ThemeGarden that use a lot of themes

donquixote’s picture

I'm fine with using the clone patch from #8, just thought that the David_Rothstein approach is worth mentioning.

How often do you think the filesystem scan will run if the data is not cached? In the other issue it was argued that it will only ever run 1 or 2 times. Otherwise, I would rather suggest to add a cache in drupal_system_listing(), that would only remember raw filesystem data.

mathieu’s picture

Same patch, with a bit less comments that were not necessary as this is using drupal_clone.

frjo’s picture

I have tested patch #13 and it works well for me.

All my themes used to be disabled every time I run updates (drush updb). With this patch the themes stays enabled after updates.

Thanks!

jonhattan’s picture

Priority: Normal » Critical
Status: Reviewed & tested by the community » Needs review
jonhattan’s picture

FileSize
1.94 KB

Ops my previous comment gone away in the preview.

I don't know why I'm getting

drupal-6.x-cvs$ patch -p0 < 632080-theme-clone.patch-D6_0.txt 
patching file b/modules/system/system.module
patch: **** malformed patch at line 18:  

Here's an equivalent patch done with cvs.

andypost’s picture

Status: Needs review » Reviewed & tested by the community
FileSize
726 bytes

@jonhattan use http://drupal.org/patch/create and better to point exact file name to minify patches

cvs diff -up modules/system/system.module > 632080-theme-clone-D6.patch
boazr’s picture

sub

muhleder’s picture

subscribing, related to this bug in the Install Profile API module
http://drupal.org/node/515644

kmillecam’s picture

Thanks for this patch.

It worked for me (and kept me from looking quite silly to my clients).

"I don't know why your theme keeps changing ..."

nilsja’s picture

perhaps this is related http://drupal.org/node/794736 ?

only occours with just one (fusion) theme and skinr module activated.

Gábor Hojtsy’s picture

Status: Reviewed & tested by the community » Needs review

Ok, where else does core clone objects instead of returning the original ones in arrays? I'm not aware that this is a general practice to clone objects this way in arrays. Or am I missing something here? Is this not the responsibility of the caller to clone objects if they intend to modify them? Does the issue confined to the internals of this function?

donquixote’s picture

PHP4 does always clone the returned objects, so it is a safe thing to do the same for PHP5.
A static cache gets totally out of hands, if other functions get the chance to mess with the values - and they will.

Ok, where else does core clone objects instead of returning the original ones in arrays?

Probably there are more places with the same issue. And I can easily imagine how this is already causing a bunch of problems, that we don't even know about yet.

We will not be able to eliminate all global state variables in Drupal. Think of global $user - this has a lot of side effects, but a lot of things depend on it.

Instead, we have to look at it case by case.
For static cache variables, we already chose to encapsulate the variable inside a function, to restrict read and write access. The idea is that only the function itself can write on the variables.
And in this case, the returned object could very well be an array instead - in which case it would be copied anyway.

-----------

EDIT:
One more way to say it:
Either we have to clone the returned objects, or return by reference. Otherwise we have inconsistent behavior between PHP4 and PHP5.

---------

I'm not aware that this is a general practice to clone objects this way in arrays.

I would say, it is not a general practice to cache an array with objects inside, unless there is some mechanism to make these objects read-only, or there is a very good reason why calling code should be allowed to modify the cache values.
In the case we are talking about, there is no such a good reason. It is just wrong thinking, nothing else.

Is this not the responsibility of the caller to clone objects if they intend to modify them?

We have full control of the called function, but zero control of the calling code (which could live in contrib, custom, whatever). Allowing the calling code to modify the cached values is careless and should be avoided, unless there is a good reason for it.

andypost’s picture

Related #305653-144: Themes disabled during update

Suppose cloning was chosen

nilsja’s picture

is there anything i can do about this? because sometimes its really disturbing having to re-enable the theme every 20 minutes...

there are so many drupal users. why do i have the feeling that this problem only concerns me? could it be helpful when i post more logs or details of my module-setting or something else?

thanks,

nilsja

donquixote’s picture

Now, what next? This is a burning issue.
The alternative to cloning would be to return arrays (maybe this would look more like a "general practice"). Doing this for D6 would break a lot of custom code.
I think the cloning is really the best option for D6.

-----

Btw, another issue which could be related:
#307756: Backport of #147000: Unify and rewrite module_rebuild_cache() and system_theme_data()
(just a guess)

pwolanin’s picture

I don't see much of a way around for Drupal 6. The patch looks reasonable to me.

The only alternative is just to eliminate the static cache all together.

pwolanin’s picture

Looks like Drupal 7 removes the static caching: http://api.drupal.org/api/function/_system_rebuild_theme_data/7

Do we care more about memory overhead or the possible performance hit? Note that the PHP filesystem stat cache should help with performance/

donquixote’s picture

@pwolanin (#28):
In #305653: Themes disabled during update it was said that we should disable the cache for D7, but clone for D6, to avoid a too heavy change in D6.

Months have passed since then, and nothing happened. I get a deja-vu with this bug for each new project i am invited to work on.
I think we should finally make a choice, and not let people sit on this bug forever.

pwolanin’s picture

@donquixote - Sounds like a little more research is needed to answer Gabor's question in #22, but the code looks fine to me. Did you verify that the patch resolves the bug? This issue is lacking in actual functional reviews of the fix.

donquixote’s picture

For my own projects, it always did the trick. Reproducibly.

Some more feedback can be found over in the other issue, although I find mostly myself talking.
nilsja at #305653-164: Themes disabled during update says it did not help in his specific case (if he is talking about the same patch). So this would be one negative (although he didn't say it damages anything either. could be he just has a different problem, which needs to be solved separately).

As for Gabor's question in #22, I explained my opinion quite lengthy already, and I think I should not further annoy people by repeating it. In short: Drupal might not clone anything else yet, but it should (and implicitly did with PHP4).

donquixote’s picture

@Gabor (#22):
In core modules:

../modules# grep -r drupal_clone .
./taxonomy/taxonomy.module:      $term = drupal_clone($terms[$vid][$child]);
./node/node.module:        return is_object($nodes[$param]) ? drupal_clone($nodes[$param]) : $nodes[$param];
./node/node.module:      $nodes[$node->nid] = is_object($node) ? drupal_clone($node) : $node;
./node/node.module:    if (strstr(node_view(drupal_clone($node)), $keyword) || strstr($node->title, $keyword)) {
./node/node.pages.inc:      $cloned_node = drupal_clone($node);
./node/node.pages.inc:    $output .= node_view(drupal_clone($node), 1, FALSE, 0);

In node.module, node_load(): Yes, we use drupal_clone() to not give other modules a chance to taint values in the cached node objects. Unfortunately, this does not help for nested objects. We'd need a recursive clone for that, which would be quite expensive. Or an effective readonly wrapper class (not realistic for anything < D8).

I can remember an issue with token that was caused by this incompletely cloned nodes, but can't find it atm (or don't want to look)..

Status: Needs review » Needs work

The last submitted patch, 632080.patch, failed testing.

donquixote’s picture

I can absolutely confirm that the bug is gone as long as I have this patch applied.
Whenever I wipe the patch with a core update, the bug comes back. I then have to restore the patch manually.
(yeah I am considering to get a git workflow where I can easily merge updates and patches)

jonhattan’s picture

Status: Needs work » Needs review

Hoping it will pass tests to #17. botsnack!

pwolanin’s picture

Status: Needs review » Reviewed & tested by the community

@jonhattan - the testbot doesn't run on Drupal 6 patches afaik.

I still think this is RTBC - the amount of data being cloned is likely modest and I think that's a better option than eliminating the static variable. However, either option is ok.

Gábor Hojtsy’s picture

Status: Reviewed & tested by the community » Fixed

All right, committed and pushed the last patch from @andypost. Thanks!

Status: Fixed » Closed (fixed)

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

donquixote’s picture

Hi.
I just had some themes disable themselves on a site where the above patch has been applied for a long time.

I do still think the logic behind the patch is reasonable, and it has helped.

But, I also think there must be something else going on.
I just wonder what this could be. I see myself in front of some hard troubleshooting :(

deepeddy’s picture

subscribing