We have a couple places in UC where cache_clear_all() is used, apparently for no reason other than to clear the cache. I believe this was due to some early testing to make UC play nice with different cache modes for anonymous users, but it was never followed-through to completion. We need to remove or update these to something a little more specific.

Reading the docs calling this func w/o any arguments deletes any expirable pages. My hunch is we need to change the cart block for anonymous users to not be dynamic and have the cache clear simply affect the cart pages.

Initially reported by christefano.

CommentFileSizeAuthor
#27 uc_cart_block_rewrite.patch25.12 KBrszrama
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

rszrama’s picture

(Tracking this issue in another location but posting my research here to see if it generates any further ideas... note that my text below is related to the D5 version of UC, while this thread here is concerned w/ D6.)

So... I've been doing some research on this issue this morning. I see what you mean now about a single cache_clear_all() wiping all cached pages. I don't think we accounted for the fact that every anonymous pageload has an expire "timestamp" of -1, so they'll all be wiped whether they cached anonymous user cart data or not.

My proposal for a solution is to change the way we're excluding pages from caching. Right now we have a copy of an outdated method from the Cache Exclude module. I checked back, and Larry's done something different now... he manually overrides the cache variable on pageload to disable caching for that request if the page is one that should not be cached.

So, my summary thinking is:

  1. Always disable caching on cart* pages. The cart form, checkout forms, and any callbacks used during checkout obviously needn't be cached.
  2. Disable caching for anonymous users with items in their shopping cart so we never cache a version of a page that shows contents in a cart.

The only problem is that when an anonymous user with stuff in their cart visits a new page, if there's a cached version of that page the user would see that. So, even if they had something in their shopping cart, they would see an empty cart on the pageload. It seems like what I need to do then is on hook_init(), check to see if there is a cached version and check to see if the user has items in the shopping cart. If so, I need to empty the cache and then redirect (301?) back to the same exact URL. Of course... this would then screw up forms...

Obviously, this is a moot point for stores that don't let anonymous users shop. However, for UC at large it seems like this all could've been fixed w/ block level caching... which is not a core feature of D5. The smart alternative would've been to simply not show a detailed shopping cart block for anonymous users, but even that is less than ideal.

So... any thoughts on a fix that's generally good for UC and accomplishes what needs to happen for DDH? I'm not quite overwhelmed here, but I wanted to run my research by you before changing a lot of code. : )

alpritt’s picture

I've only vaguely looked into what you are doing currently, but I imagine that in order for cache_clear_all() to solve this problem, you would effectively have to turn off the cache for the entire site. Except caching wouldn't actually be turned off; you'd just be building and then clearing it all the time, which would make performance worse than if it was turned off completely.

As I understand it hook_init() is not called for cached pages. I guess the Cache Exclude module gets away with it because it always operates on the same few pages and so those pages never get cached. With our shopping cart, the situation is different since we want to show the updated cart on every page.

I was going to suggest checking the session in hook_boot() and disabling caching if there are items in the cart for that session. Unfortunately, hook_boot() is called a fraction too late. There's a recent issue about this: #322104: Allow hook_boot() to disable page cache

You can't just do a cache_clear_all from within hook_boot either, because then other anonymous users will start seeing the wrong cart details. So the redirect you suggested doesn't make sense.

So, unless I'm missing something (quite likely), these are the possible solutions I can think of:

1. First of all make sure there is an option to fix this in Drupal 7 (see issue linked above for starters).

2. Create some kind of temporary user account. (I have no idea how that would actually work, but thought I'd put it out there!)

3. Bring the cart data in through an AJAX request. (I'm pretty sure this will work, but haven't thought it through properly yet.)

4. Disable the dynamic part of the cart block for anonymous users.

5. Manually get people to add the check to the $conf array in settings.php. Perhaps make that a progressive enhancement for one of the above options (might work well with 4).

Might be worth seeing how our e-commerce friends do this too.

What a pain. :)

alpritt’s picture

Priority: Normal » Critical

I've taken a look at e-commerce and they have gone with option 4.

I believe this is serious enough to warrant a critical priority.

rszrama’s picture

Based on your investigation and maybe the latest work in the Cache Exclude module, do you know if there's any way to simply not cache a page if an anonymous user visits it w/ items in their shopping cart? I think this might fall into what you were saying where essentially we'd have to build the cache and then destroy it or something...

alpritt’s picture

The short answer is no, there isn't.

The way that Cache Exclude works is by never allowing a cache to be built for certain pages. The flow goes like this:

- anonymous user requests a page
- bootstrap asks if caching is enabled. It is.
- bootstrap looks for the cached entry. None has been set yet so it returns null
- because there is no cached entry for this page, the page gets rebuilt
- hook_init gets called by Cache Exclude module. Depending on the URL, page caching is set to FALSE.
- drupal_page_footer() is called. It asks if caching is enabled. It is not, so the cache does not get set again for this page.

In other words, it relies on the cache never being set for certain pages.

But with Ubercart we want the cache to be set for most anonymous users; just not those who have something in their cart. The only way you could make this same method work is if you cripple caching for all pages. This would be better than what you are currently doing, but it does still mean you've effectively disabled page caching for all users of ubercart. Site owners would be better off not turning on page caching at all in the admin interface.

What would be preferable is when you display the cart, check if the user is anonymous and also if caching is enabled, and if so just show a link to the cart. Then if the user turns off page caching, the full cart will be displayed. Turn it on and they just get the link.

Not ideal, but much better than what you've got currently and the site owner is in control.

Then you could could put some instructions in the readme file telling them how to have their cake and eat it too, by conditionally setting $conf['cache'] = FALSE in settings.php. This is the only place you can currently disable loading the cache conditionally; since the earliest hook occurs after the cache is already built.

I'm not sure if the AJAX idea would work or not, but it would build on the above in any case.

rszrama’s picture

I really appreciate your help and advice here. My hunch was we'd have to do what you're proposing with the shopping cart block. That really is the only sticking point for caching afaik anyways. Do you know if we'll have to add any special code to make sure pages like /cart/* don't get cached, or will that happen automatically since those aren't nodes?

alpritt’s picture

No, those will get cached also. The only exception is when not using GET for a request (i.e. a form submission) or if there is a message in drupal_set_message(). Using the cache exclude method may be the right answer for /cart/* if you never want them cached.

rszrama’s picture

Alrighty... I'm on it. We've been using Cache Exclude's code in the cart module for a while, though I may tweak it to just work on a wildcard for cart/*. I'll probably get this fix in for both D5 and D6 at the same time. I'll post a patch tomorrow for review if I can.

Anonymous’s picture

greg.harvey’s picture

Can't you just set the block in question to not cache, like the core user module does for the login block? I have done this in modules before, where I didn't want info like username to be cached in a block - here's the actual user module code from user_block():

function user_block($op = 'list', $delta = 0, $edit = array()) {
  global $user;

  if ($op == 'list') {
    $blocks[0]['info'] = t('User login');
    // Not worth caching.
    $blocks[0]['cache'] = BLOCK_NO_CACHE;
    ........
  return $blocks;
  }
  ........
}
rszrama’s picture

Looks like this has actually already been done. The question now is can we take out all those cache_clear_all()'s? I don't know if this has been tested yet.

greg.harvey’s picture

If you can tell me what to check for, I can test it now - I am actually officially testing out Ubercart at work for the next day or so... =)

rszrama’s picture

Awesome! Well, basically, you need to try Ubercart on different levels of caching as set in the performance settings on your test site. Browse around anonymously, add items to the cart, remove them, etc. The problem people have had is that items appear to be an anonymous user's cart that aren't there if someone before them added a product and Drupal cached that page. If the block isn't going to be cached, then this shouldn't be an issue. I believe that was the root behind our cache_clear_all() usage, and there's more discussion of that above.

So, after browsing around to see how it behaves a bit, completing checkout, etc., you'll want to actually alter the code. Search the source for any place where we just do a blanket cache_clear_all() (in Ubercart modules only) and comment those lines out. Then repeat the tests to make sure everything continues to work well.

If you can get this tested, that would be HUGE for Ubercart.

greg.harvey’s picture

Ok - just porting the UC Worldpay module to D6 this afternoon, but I'll test this tomorrow and report back. =)

alpritt’s picture

"Can't you just set the block in question to not cache"

That's fine for block level caching, but won't help if you have page caching on.

greg.harvey’s picture

Really? It seems pretty pointless as an option then, since nearly everyone uses page caching on "production" sites. Are you sure about that?

Edit: I think you're wrong. The manual does not describe this, and other options for blocks include BLOCK_CACHE_PER_PAGE - it would be a nonsense for such an option to exist if block-level caching was not smart enough to affect the page cache.

alpritt’s picture

Block caching is most beneficial for authenticated users, who don't ever see a full page cache.

The manual is stating that the block does not get cached in the block cache. But it still gets cached as part of the overall page cache.

I don't see why BLOCK_CACHE_PER_PAGE would be nonsense. That simply allows a block to look different depending on the page being viewed. The page cache would cache whatever the block outputted for that individual page. Then you'd go to another page, the block would output something different and that difference would be cached.

greg.harvey’s picture

The manual is stating that the block does not get cached in the block cache.

No it isn't... *you* are. The manual makes no such reference. The string "block cache" is simply not there. You're assuming. I'm not saying you're definitely wrong, because without reviewing the block module's code I can't know, but it is not my interpretation of the documentation...

Then you'd go to another page, the block would output something different and that difference would be cached.

Which would, *by definition*, mean it's not in the page cache, or that behaviour wouldn't work! :-/

Oh, I see what you mean - I'm not convinced. Maybe I have too much faith in the block module. ;-)

We need to check the code of block.module to confirm/deny this. I will do so when I check the behaviour of the module, as the success or otherwise of the approach will prove/disprove theories on how the block module works! All will be revealed...

greg.harvey’s picture

After further investigation I have to tell you alpritt is right (thanks for jumping in and saving a load of time).

It seems page cache for anonymous users has no awareness of block cache settings whatsoever. In short, it is absolutely impossible to serve a dynamic block to anonymous users with page caching enabled on a Drupal site. It fundamentally will not work. =(

This AJAX cart claims to bypass the cache:
http://www.ubercart.org/contrib/6148

rszrama’s picture

Assigned: Unassigned » rszrama

I'm gonna dig into this in a bit... just want to assign myself per our project maintenance group chat. : P

rszrama’s picture

Just noticed this old issue where it seems I came up with the same solution in the alphas... :-/

#206013: Modify cart block for anonymous users

greg.harvey’s picture

Yes, AJAX seems to be the only solution I could find too. Shame. =(

Whichever way you look at it, "resolving" the issue with cache_clear_all() is not the answer, as effective you're switching off page caching for everyone without asking them, and in an expensive way (Drupal build cache, Drupal clear cache, Drupal build cache, Drupal clear cache, etc!)

It would be better, for now, to recommend page caching is left OFF if you intend to expose the cart block to anonymous users and recommend the contributed Ajax Cart module as an alternative if you want to run page cache AND expose the cart block.

rszrama’s picture

Version: 6.x-2.x-dev » 6.x-2.0-beta1
Priority: Critical » Normal

Agreed, Greg. As the other issue points out, I'm going to make a static cart block for anonymous users if caching is turned on. If it's off, we may as well serve up the current dynamic block. If it's on, we'll just have the view cart and checkout links. Then I imagine we can go through and get rid of all the cache_clear_all()'s in the Ubercore. Will have patch here for testing later today.

anders.fajerson’s picture

Interesting discussion (subscribing).

alpritt’s picture

FYI, this patch has just gone in for Drupal 7. I've not looked at it closely but it appears to stop pages being cached for anonymous users when there is something stored in $_SESSION, which will solve this problem. Something to look forward to... #201122: Drupal should support disabling anonymous sessions

rszrama’s picture

Status: Active » Needs review

Alrighty... I got as far as rewriting the cart block code last night to make appropriate use of the theme layer and also work with caching by detecting the cache settings and displaying a cachable cart block if enabled and the user is not logged in. This cachable block is in its own theme function for overriding purposes, too.

Please review the patch, and I'll post a follow-up shortly that deals with removing unnecessary instances of cache_clear_all() from various parts of core. Thanks!

...

So, for some reason I can't attach patches to this issue or even view it in Firefox any more. :-/

In the meantime, please review the patch in this thread on Ubercart.org.

rszrama’s picture

Version: 6.x-2.0-beta1 » 6.x-2.0-beta3
FileSize
25.12 KB

Ok, looks like it was a general connectivity issues w/ d.o. I've attached the patch from that thread here as well, though I'm happy to take comments in that other thread. There's also a full outline of changes in that thread. This patch here takes into consideration some feedback from greggles and also includes the fix to remove excess instances of cache_clear_all().

rszrama’s picture

Status: Needs review » Fixed

Day is over... gonna commit what's there and we can address it as need be. Dun dun dun.

Status: Fixed » Closed (fixed)

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

canishk’s picture

Hope we can resolve this by "Block Cache Alter Module". http://drupal.org/project/blockcache_alter