Showing posts with label perl. Show all posts
Showing posts with label perl. Show all posts

Tuesday, June 2, 2009

Using CruiseControl.rb to manage a Perl Catalyst project

We're working with the Catalyst framework again, porting an old Perl 5 HTML::Mason site to Catalyst and introducing some modern Perl coding standards to a fairly old stack.

One of the things we needed was a Continuous Integration tool for the project. Since we're already using CruiseControl.rb for the Rails projects I thought it should be pretty easy to incorporate the Perl project into it.

And indeed it was:
$ ./cruise add CatalystProject --url https://svn.work.com/svn/catalystproject/trunk/

CruiseControl will run a "rake" task whenever a commit is made. So we need a small Rakefile with just enough code in it to run the standard Perl tools:

$ cat Rakefile
require 'rake'

task :default => :test

desc "Runs make test"
task :test do
  t  = system("eval $(perl -I$HOME/perl5/lib/perl5 -Mlocal::lib) && perl Makefile.PL && make test")
  rc = $?
  puts "\nMake finished with t=#{t} rc=#{rc.exitstatus}"
  raise "Perl make failed (rc=#{rc.exitstatus})" unless t
end

The eval line in the system call is there because we're using local::lib to manage our Perl library dependencies and we need to set the environment variables so that they can be found. To propagate any "make test" errors back out to rake we throw an exception on non-zero exit codes.


Read more...

Sunday, October 5, 2008

Invalidating Page Caches in Mason

We have a number of websites that use Mason, a (relatively) old Perl library reportedly used by Amazon and Salon among other sites. It has built-in and flexible support for caching at pretty much any level the developer needs, but often the easiest thing to do is to cache the output of an entire component, which looks like this:

<%init>
return if $m->cache_self(key => $key,
expires_in => '3 hours' );
[...]
<%/init>


What I wanted to do was invalidate the cache when the users browser had a Cache-Control or Pragma directive that indicated they did not want cached content (for example, when the user holds down the Shift key and clicks "Refresh" or "Reload").

The naive implementation does not work:

<%init>
# Somewhere, $cache_ok is set to 0 if Cache-Control
# indicates no caching wanted
if ( $cache_ok ) {
return if $m->cache_self(key => $key,
expires_in => '3 hours' );
}
[...]
</%init>



I mean, it does work for the initial request but all subsequence requests will continue to get the old cached content because the cache has not been invalidated, only skipped. The very next request for that component will get the old, cached content -- not a cached copy of the freshly calculated content.

Slightly less naive implementations didn't work either because of the way cache_self works. I was basically trying variations of "expires yesterday" but this parameter was not being consulted when Mason considered whether or not to return the cached content.

Cutting a long story short (well, not that long but kinda boring) here's how I got the page cache to be invalidated ("expired") when the user hits Shift-Refresh:

    if (  $cache_ok ) {
$m->cache_self(key => $page_cache_key, expire_if => sub { 1 } );
} else {
return if $m->cache_self(key => $page_cache_key,
expires_in => '4 hours' );
}
[... continue with component...]



The variable $cache_ok defaults to "1" but is set to "0" if no-cache is found in the Cache-Control request header (for completeness, also check the Pragma directive althought that might indicate the presence of a browser so old it wears flares).

What this code does is returns the cached content (provided it's younger than 4 hours) most of the time. But if the requesting client has indicated it does not want cached content then it invalidates Mason's copy of the cached output and then falls through to the bottom of the if block where normal. non-cached processing occurs.

Post-script: Since publishing this post I've changed and reformatted the source code slightly.
Read more...