Category Archives: Linux

CakePHP + Symlinks = Pain

Now that I’ve had a day or so to recover, I’m going to tell you about what I just spent 2-3 weeks trying to resolve. By way of explanation, our main product at work is a CakePHP based CMS application. It has a lot of neat features, including the ability for users to upgrade to newer versions any time they choose. We keep all versions present in /etc/precious_core/<version_number>/, and each user has a symlink to the relevant directory in their webroot.

When they upgrade, part of the process is to replace that symlink with a new on that points at their new version. For a long time we’ve known there was a problem of some kind related to CakePHP’s cache that developed after upgrades, but it was never a huge problem, so we mostly just ignored it. However, in a recent release, it started causing major problems, and I got tasked with finding and fixing the actual bug. I figured it would take a day or two, and I’d be done with it. Little did I know just how painful this was going to be.

I initially tried several ways of forcing the cache to get cleared when the app was upgraded. That worked well, as far as it went, but then a new problem surfaced. At least half the time, the cache would re-populate with bad data after an upgrade. Some of the cached file paths would be for the wrong version of the central app, for no apparent reason. I tried throwing even more thorough cache clearing at it. Things got a little better, but it still wasn’t working.

Finally, I fully duplicated our production setup on my local dev machine, parallel version directories included, and installed a PHP debugger, so I could step through the code and figure out what exactly was going on. After several hours of research, I determined that the error was happening in this function:

function __map($file, $name, $type, $plugin) {
	if ($plugin) {
		$this->__map['Plugin'][$plugin][$type][$name] = realpath($file);
	} else {
		$this->__map[$type][$name] = realpath($file);
	}
}

The file paths that were getting inserted into $this->__map were sometimes incorrect. I knew that realpath() could cache it’s data, but I was explicitly calling clearpathcache() during the upgrade process. So why was it getting bad paths sometimes? At this point, Ceeram (@ceeram) of #cakephp on Freenode helped me out by pointing me at this blog: PHP, symlinks, and the realpath cache, which explained perfectly what was going on.

It turns out realpath()’s cache is per-thread, so even though I was clearing it in the apache thread that was doing the upgrade, it still had old data in other apache threads. Possibly very old data sometimes. (I once saw file paths for 3 different versions of our app in one cake_core_file_map during testing.) The simplest solution that I was able to discover was to simply add an apache graceful restart during our upgrade process. The impact of a graceful restart is minimal, but it does cause all apache threads to get closed down and new ones opened, which fixes the problem by forcing all of them to clear their cache.

So, the moral of the story? Well, not sure there is one. Except that sometimes you need a breakpoint debugger even when you’re coding PHP.

Screening files on push in a git repo

I recently had the need to make sure that files that were pushed to a git repository had a required string in them. It took a few hours, but here’s what I came up with to do it:

#!/bin/sh
 
refname="$1"
oldrev="$2"
newrev="$3"
 
paths="path/in/repo/"
required="REQUIRED_STRING"
 
hashes=( $(git diff-tree -r $newrev -- $paths | awk '{if ($5 == "A" || $5 == "M") print $4}') )
files=( $(git diff-tree -r $newrev -- $paths | awk '{if ($5 == "A" || $5 == "M") print $6}') )
 
error=0
errors=()
 
for (( c = 0; c < ${#hashes[@]}; c++ ))
do
	check=$(git show ${hashes[$c]} | grep $required | wc -l)
 
	if [ "$check" == "0" ] 
	then
		errors=( "${errors[@]}" ${files[$c]} )
		error=1
	fi
done
 
if [ "$error" == "1" ]
then
	echo
	echo "The following SQL scripts do not have the required string '$required':"
 
	for (( c = 0; c < ${#errors[@]}; c++ ))
	do
		echo "    ${errors[$c]}"
	done
 
	echo 
fi
 
exit $error

Save this as ‘update’ in the ‘hooks’ directory of your bare repo, make it executable, and enjoy. Any files in the paths specified that don’t contain the required text will result in a rejected push.

Randomized .signature file

Here’s a quicky. I like to have a random quote in my signature, but most mail clients only read a static file, and trying to fool them with a command pipe socket is tricky, and often has other issues. So I wrote this little bit of magic:

#!/usr/bin/perl
 
my $fortune = '';
 
do {
	$fortune = `fortune -e -s -n 480 bofh-excuses calvin chalkboard chucknorris computers cookie definitions discworld dune firefly fortunes hitchhiker homer kids law linux magic mormon paradoxum perl pets riddles science scriptures smac startrek starwars taow work`;
} while ($fortune =~ /filter|these|words|out/);
 
print "Matthew Walker                      h: (xxx) xxx-xxxx\n";
print "Software Architect                  m: (xxx) xxx-xxxx\n";
print "Marketecture                        e: fake_email\@domain.com\n\n";
 
print $fortune;

Replace the ‘filter|these|words|out’ bit with a | seperated list of words you don’t want to show up in your fortunes, and adjust the fortune command however you want to tune the type of quotes you get. Then install it as a cron job like this:


* * * * * perl ~/.signature.pl > ~/.signature

There you go! Your .signature file will get regenerated once a minute with a new random quote. Enjoy.

CakePHP mod_rewrite optimization

CakePHP is great, but the default installation has a performance issue that is very easy to resolve, and should be fixed in any production installation of a CakePHP application. The solution is very simple.

In the default install, there is a file in the webroot called .htaccess. It contains mod_rewrite rules, and you have to set you vhost up to allow overrides. This introduces a significant overhead to each request, as apache has to search for applicable htaccess files and merge their settings into the core apache config every request. On a production application this can become significant, especially if you get a significant amount of traffic.

Read more »

Gnome still wins

One of my friends convinced me to try out KDE again. I’ve never been real settled on a window manager for Linux, alternating between Gnome, KDE, and other random ‘lightweight’ WMs. But, I figured I’d give KDE another shot since I’ve been with Gnome for a while.

Unfortunately, KDE 4.1 and 4.2 have definately failed to impress. They have a lot of potential, and have some things I like, but they fail critically in several areas in my opinion.

First, I almost always run multiple monitors for my workstations, and I have a full collection of Digital Blasphemy’s multi-monitor wallpapers that I like to use. But for some reason, in KDE 4.x, there is NO way to span a wallpaper across multiple monitors. It’s just not possible, and there’s no evidence of a fix any time soon.

Secondly, when I upgraded to 4.2 after it was released, I suddenly started experiencing lots of crashes of Kwin and plasma, as well as UI lockups. That isn’t acceptable, and I couldn’t find any reason for it.

Lastly, Amarok has one of the worst UIs for a music player I’ve ever seen. Not to mention the fact that for some reason it refused to shuffle playlists for me. No errors, no reason that I could find. Just didn’t work.

So I’ve switched back to Gnome, despite some weaknesses with Gnome right now. For instance, Gnome Screensaver hides ALL configuration of screensavers, and doesn’t even show the modules from xscreensaver by default. You have to go run a manual command to copy them over to another directory, with xml config files.

Also, while Gnome allows you to span wallpapers across multiple monitors, it has no core functionality for auto-changing wallpapers every X minutes. But at least it’s stable, and has a music player that lets me shuffle my playlist.

I think both Gnome and KDE are trying a little too hard to appeal to the newbie user. I have nothing against helping out new users, but PLEASE don’t do it at the cost of advanced functionality. That way lies Vista.

So, Gnome still wins. For now. I’m still on the lookout for The Perfect WM.