Conditional action-caching stops after a period of time

5 views
Skip to first unread message

spariam

unread,
May 8, 2008, 4:01:27 PM5/8/08
to acts_as_cached
I came across this earlier post, which describes a problem very
similar to the one I'm having now.

http://groups.google.com/group/acts_as_cached/browse_thread/thread/2466f22f523ccfbe/d5c4bb8e67673ae8?lnk=gst&q=conditional#d5c4bb8e67673ae8

Conditional caching seems to be working intermittently and it's only
on our production servers (six servers, I believe 3 mongrels each)
where the problem is showing up (it works fine in development and on
our staging server).

For example, on our main index page, a non-authenticated user should
get the action cached page, but it was obviously not using action
cache (we didn't even see an outbound call to the memcache server with
TCP dump).

I added one logging statement in the :cacheable? method and restarted
servers. At that point I was seeing what I expected and it looked like
action cached was now working on the main index page (CACHEABLE is
the logger output).

Processing HomeController#index (for 64.79.204.16 at 2008-05-08
12:57:18) [GET]
Session ID: d33f8f527f6c62cfdf425ad750e4beff
Parameters: {"action"=>"index", "controller"=>"home"}
CACHEABLE: true
Filter chain halted as
[#<ActionController::Caching::Actions::ActionCacheFilter:0xb7309fc8
@actions={:index=>{:if=>:cacheable_not_logged_in?, :ttl=>1800
seconds}}, @options={:cache_path=>#<Proc:0xb7321dd0@/usr/local/sites/
www.somesite.com/releases/20080428165347/app/controllers/home_controller.rb:4>}>]
rendered_or_redirected.
Filter chain halted as
[#<ActionController::Filters::ClassMethods::ProcWithCallFilter:
0xb7309c08 @filter=#<Proc:0xb7838760@/usr/lib/ruby/gems/1.8/gems/
actionpack-2.0.1/lib/action_controller/filters.rb:657>>]
did_not_yield.
Completed in 0.00212 (471 reqs/sec) | Rendering: 0.00006 (2%) | DB:
0.00000 (0%) 0 queries | 200 OK

After a half hour or so, I see less and less caching...either the
logging statement isn't there (like the condition is ignored), or it
is 'true' but still doesn't action cache (considerably more of the
former):

Processing HomeController#index (for 216.183.47.142 at 2008-05-08
13:51:07) [GET]
Session ID: 8711c577dc9b74cfa3811fb2778a6aef
Parameters: {"action"=>"index", "controller"=>"home"}
Rendering template within layouts/application
Rendering home/index
Completed in 0.13643 (7 reqs/sec) | Rendering: 0.13087 (95%) | DB:
0.00000 (0%) 18 queries | 200 OK

or

Processing HomeController#index (for 127.0.0.1 at 2008-05-08 13:52:23)
[GET]
Session ID: 289cc740f8957738dccd22ce619357fe
Parameters: {"action"=>"index", "controller"=>"home"}
CACHEABLE: true
Rendering template within layouts/application
Rendering home/welcome
Completed in 0.19601 (5 reqs/sec) | Rendering: 0.18986 (96%) | DB:
0.00000 (0%) 22 queries | 200 OK

Any insight or suggestions would be most helpful.

home controller:

caches_action :index => { :ttl => 30.minutes, :if
=> :cacheable? } , :cache_path => Proc.new {|c| c.flash_message }

(the flash_message was needed as when a user logged out, they go to
the home index page with a flash message)

app/controllers/application.rb

def cacheable_not_logged_in?
logged_in? ? false : true
end

We're using Rails 2.0.01, cache_fu, and memcache-cilent.

Thanks...

spariam

unread,
May 8, 2008, 4:14:40 PM5/8/08
to acts_as_cached
Sorry, couple typos:

app/controllers/application.rb

def cacheable?
logged_in? ? false : true
end


Rails 2.0.1


On May 8, 3:01 pm, spariam <doug.sparl...@gmail.com> wrote:
> I came across this earlier post, which describes a problem very
> similar to the one I'm having now.
>
> http://groups.google.com/group/acts_as_cached/browse_thread/thread/24...
>
> Conditional caching seems to be working intermittently and it's only
> on our production servers (six servers, I believe 3 mongrels each)
> where the problem is showing up (it works fine in development and on
> our staging server).
>
> For example, on our main index page, a non-authenticated user should
> get the action cached page, but it was obviously not using action
> cache (we didn't even see an outbound call to the memcache server with
> TCP dump).
>
> I added one logging statement in the :cacheable? method and restarted
> servers. At that point I was seeing what I expected and it looked like
> action cached was now working on the main index page (CACHEABLE is
> the logger output).
>
> Processing HomeController#index (for 64.79.204.16 at 2008-05-08
> 12:57:18) [GET]
> Session ID: d33f8f527f6c62cfdf425ad750e4beff
> Parameters: {"action"=>"index", "controller"=>"home"}
> CACHEABLE: true
> Filter chain halted as
> [#<ActionController::Caching::Actions::ActionCacheFilter:0xb7309fc8
> @actions={:index=>{:if=>:cacheable_not_logged_in?, :ttl=>1800
> seconds}}, @options={:cache_path=>#<Proc:0xb7321dd0@/usr/local/sites/www.somesite.com/releases/20080428165347/app/controllers/home_control...>}>]

Sujal Shah

unread,
Jun 10, 2008, 10:29:08 PM6/10/08
to acts_as...@googlegroups.com
spariam,

I think you're running into the issue I just described in my previous
email to the list. I figured out what the line I asked about is for,
and I'm working out a patch now. Hopefully, this will solve your
problem, too.

Sujal

dsparling

unread,
Jun 12, 2008, 11:08:42 AM6/12/08
to acts_as_cached
Sujal,

It is indeed something with that line you posted. I contacted the
author of the original post (Jan Prill) and he sent me his patch,
which I've been meaning to post here. I had to modify it slightly for
Rails 2 and I'm glad to say that cache_fu has been working great ever
since.

/vendor/plugins/cache_fu/lib/acts_as_cached/fragment_cache.rb
===============================================

module ActsAsCached
module FragmentCache
def self.setup!
class << CACHE
include Extensions
end

setup_fragment_cache_cache
setup_rails_for_memcache_fragments
setup_rails_for_action_cache_options
end

# add :ttl option to cache helper and set cache store memcache
object
def self.setup_rails_for_memcache_fragments
if ::ActionView.const_defined?(:Template)
# Rails 2.1+
::ActionController::Base.cache_store = CACHE
else
# Rails < svn r8619
::ActionView::Helpers::CacheHelper.class_eval do
def cache(name = {}, options = nil, &block)
@controller.cache_erb_fragment(block, name, options)
end
end
::ActionController::Base.fragment_cache_store = CACHE
end
end

def self.setup_fragment_cache_cache
Object.const_set(:FragmentCacheCache, Class.new
{ acts_as_cached :store => CACHE })
end

# add :ttl option to caches_action on the per action level by
passing in a hash instead of an array
#
# Examples:
# caches_action :index #
will use the default ttl from your memcache.yml, or 25 minutes
# caches_action :index => { :ttl => 5.minutes } #
cache index action with 5 minute ttl
# caches_action :page, :feed, :index => { :ttl => 2.hours } #
cache index action with 2 hours ttl, all others use default
#
def self.setup_rails_for_action_cache_options
::ActionController::Caching::Actions::ActionCacheFilter.class_eval
do
# convert all actions into a hash keyed by action named, with
a value of a ttl hash (to match other cache APIs)
def initialize(*actions, &block)
if [].respond_to?(:extract_options!)
#edge
@options = actions.extract_options!
@actions = actions.inject(@options.except(:cache_path)) do
|hsh, action|
action.is_a?(Hash) ? hsh.merge(action) :
hsh.merge(action => { :ttl => nil })
end
@options.slice!(:cache_path)
else
#1.2.5
@actions = actions.inject({}) do |hsh, action|
action.is_a?(Hash) ? hsh.merge(action) :
hsh.merge(action => { :ttl => nil })
end
end
end

# override to skip caching/rendering on evaluated if option
def before(controller)
return unless @actions.include?
(controller.action_name.intern)

# maintaining edge and 1.2.x compatibility with this branch
# Jan Prill - added && false
#if @options && false
# Doug Sparling - removed false - false causing else to
always run - this may be
# because Jan was using Rails 1.2?
if @options
action_cache_path =
ActionController::Caching::Actions::ActionCachePath.new(controller,
path_options_for(controller, @options))
else
action_cache_path =
ActionController::Caching::Actions::ActionCachePath.new(controller)
end

# should probably be like
ActiveRecord::Validations.evaluate_condition. color me lazy.
if conditional = @actions[controller.action_name.intern]
[:if]
conditional = conditional.respond_to?(:call) ?
conditional.call(controller) : controller.send(conditional)
end
# Jan Prill - causing caching to stop after a period of
time?
#@actions.delete(controller.action_name.intern) if
conditional == false

# Jan Prill - added if clause
cache = controller.read_fragment(action_cache_path.path) if
conditional != false
if cache && (conditional || conditional.nil?)
controller.rendered_action_cache = true
if method(:set_content_type!).arity == 2
set_content_type!(controller,
action_cache_path.extension)
else
set_content_type!(action_cache_path)
end
controller.send(:render, :text => cache)
false
else
# 1.2.x compatibility
# Jan Prill added &&
controller.action_cache_path = action_cache_path if
(controller.respond_to?(:action_cache_path) && conditional != false)
end
end

# override to pass along the ttl hash
def after(controller)
# Jan Prill - added || controller.action_cache_path.nil?
return if !@actions.include?(controller.action_name.intern)
|| controller.rendered_action_cache ||
controller.action_cache_path.nil?
# 1.2.x compatibility
path = controller.respond_to?(:action_cache_path) ?
controller.action_cache_path.path :
ActionController::Caching::Actions::ActionCachePath.path_for(controller)
controller.write_fragment(path, controller.response.body,
action_ttl(controller))
end

private
def action_ttl(controller)
@actions[controller.action_name.intern]
end
end
end

module Extensions
def read(*args)
return if ActsAsCached.config[:skip_gets]
FragmentCacheCache.cache_store(:get, args.first)
end

def write(name, content, options = {})
ttl = (options.is_a?(Hash) ? options[:ttl] : nil) ||
ActsAsCached.config[:ttl] || 25.minutes
FragmentCacheCache.cache_store(:set, name, content, ttl)
end
end

module DisabledExtensions
def read(*args) nil end
def write(*args) "" end
end
end
end

Sujal Shah

unread,
Jun 12, 2008, 1:43:18 PM6/12/08
to acts_as...@googlegroups.com
Yeah, this is one way to do it. Might be better than my solution.
Basically, he was trying to keep the app from writing out the current
rendered view to the cache if the conditional was false. Deleting it
from @actions did it, but it also deleted it for any future calls (the
filter is instantiated once and kept around rather than being created
each request).

I made it explicit, just chucking the result of the conditional (true
or false) to Thread.current[:do_cache] or something like that (don't
have it in front of me). I then just checked that in the after
clause. You could also set an ivar. My patch was quick because I was
actually debugging something else at the time -- I need to revisit the
implications of using thread.current[].

Sujal

Doug Sparling

unread,
Jun 12, 2008, 3:15:42 PM6/12/08
to acts_as...@googlegroups.com
I was in the middle of a site launch and this came up - Jan fortunately helped me and it worked and I haven't had time to revisit it - I had meant to post that back to the list a month ago.

==
doug
Reply all
Reply to author
Forward
0 new messages