[Slim-Checkins] r10502 - in /trunk/server: Plugins/Podcast/Plugin.pm Plugins/RssNews.pm Slim/Player/Protocols/MMS.pm Slim/Utils/Cache.pm Slim/Utils/Scanner.pm Slim/Web/Graphics.pm Slim/Web/HTTP.pm slimserver.pl

adrian at svn.slimdevices.com adrian at svn.slimdevices.com
Sun Oct 29 09:37:27 PST 2006


Author: adrian
Date: Sun Oct 29 09:37:17 2006
New Revision: 10502

URL: http://svn.slimdevices.com?rev=10502&view=rev
Log:
Bug: 4432
Description: reduce impact of FileCache purging:
- support multiple namespaces with FileCache and purging them one at a time
- version number per cache so you can bump the version and force cleaning out of the old entries
- callers of new can create namespaces and decide whether they should be periodically purged
- all caches are purged once at startup
- follow on purges are only done if all players are off

Modified:
    trunk/server/Plugins/Podcast/Plugin.pm
    trunk/server/Plugins/RssNews.pm
    trunk/server/Slim/Player/Protocols/MMS.pm
    trunk/server/Slim/Utils/Cache.pm
    trunk/server/Slim/Utils/Scanner.pm
    trunk/server/Slim/Web/Graphics.pm
    trunk/server/Slim/Web/HTTP.pm
    trunk/server/slimserver.pl

Modified: trunk/server/Plugins/Podcast/Plugin.pm
URL: http://svn.slimdevices.com/trunk/server/Plugins/Podcast/Plugin.pm?rev=10502&r1=10501&r2=10502&view=diff
==============================================================================
--- trunk/server/Plugins/Podcast/Plugin.pm (original)
+++ trunk/server/Plugins/Podcast/Plugin.pm Sun Oct 29 09:37:17 2006
@@ -265,7 +265,7 @@
 	};
 		
 	my $cache = Slim::Utils::Cache->new();
-	$cache->set( 'podcasts_opml', $opml );
+	$cache->set( 'podcasts_opml', $opml, '10days' );
 }
 
 # for configuring via web interface

Modified: trunk/server/Plugins/RssNews.pm
URL: http://svn.slimdevices.com/trunk/server/Plugins/RssNews.pm?rev=10502&r1=10501&r2=10502&view=diff
==============================================================================
--- trunk/server/Plugins/RssNews.pm (original)
+++ trunk/server/Plugins/RssNews.pm Sun Oct 29 09:37:17 2006
@@ -242,7 +242,7 @@
 	};
 		
 	my $cache = Slim::Utils::Cache->new();
-	$cache->set( 'rss_opml', $opml );
+	$cache->set( 'rss_opml', $opml, '10days' );
 }
 
 # for configuring via web interface

Modified: trunk/server/Slim/Player/Protocols/MMS.pm
URL: http://svn.slimdevices.com/trunk/server/Slim/Player/Protocols/MMS.pm?rev=10502&r1=10501&r2=10502&view=diff
==============================================================================
--- trunk/server/Slim/Player/Protocols/MMS.pm (original)
+++ trunk/server/Slim/Player/Protocols/MMS.pm Sun Oct 29 09:37:17 2006
@@ -135,7 +135,7 @@
 	my $mmsURL = $url;
 	$mmsURL    =~ s/^http/mms/;
 	
-	my $cache     = Slim::Utils::Cache->instance;
+	my $cache     = Slim::Utils::Cache->new;
 	my $streamNum = $cache->get( 'wma_streamNum_' . $mmsURL );
 	my $wma       = $cache->get( 'wma_metadata_'  . $mmsURL );
 	
@@ -173,7 +173,7 @@
 	my $mmsURL = $url;
 	$mmsURL    =~ s/^http/mms/;
 	
-	my $cache     = Slim::Utils::Cache->instance;
+	my $cache     = Slim::Utils::Cache->new;
 	my $streamNum = $cache->get( 'wma_streamNum_' . $mmsURL );
 
 	setMetadata( $client, $url, $wma, $streamNum || 1 );

Modified: trunk/server/Slim/Utils/Cache.pm
URL: http://svn.slimdevices.com/trunk/server/Slim/Utils/Cache.pm?rev=10502&r1=10501&r2=10502&view=diff
==============================================================================
--- trunk/server/Slim/Utils/Cache.pm (original)
+++ trunk/server/Slim/Utils/Cache.pm Sun Oct 29 09:37:17 2006
@@ -12,7 +12,7 @@
 
 =head1 SYNOPSIS
 
-my $cache = Slim::Utils::Cache->new
+my $cache = Slim::Utils::Cache->new($namespace, $version, $noPeriodicPurge)
 
 $cache->set($file, $data);
 
@@ -28,7 +28,13 @@
 
 =head1 METHODS
 
-=head2 new()
+=head2 new( [ $namespeace ], [ $version ], [ $noPeriodicPurge ] )
+
+$namespace allows unique namespace for cache to give control of purging on per namespace basis
+
+$version - version number of cache content, first new call with different version number empties existing cache
+
+$noPeriodicPurge - set for namespaces expecting large caches so purging only happens at startup
 
 Creates a new Slim::Utils::Cache instance.
 
@@ -39,7 +45,6 @@
 =cut
 
 use strict;
-use base qw(Class::Singleton);
 
 use Cache::FileCache ();
 
@@ -48,26 +53,70 @@
 use Slim::Utils::Prefs;
 use Slim::Utils::Timers;
 
-my $PURGE_INTERVAL = 3600;
+my $DEFAULT_EXPIRES_TIME = '1 hour';
+
+my $PURGE_INTERVAL = 60 * 60 * 24; # interval between purge cycles
+my $PURGE_RETRY    = 60 * 60;      # retry time if players are on
+my $PURGE_NEXT     = 30;           # purge next namespace
+
+my $defaultNameSpace = 'FileCache';
+my $defaultVersion = 1;
+
+# hash of caches which we have created by namespace
+my %caches = ();
+
+my @thisCycle = (); # namespaces to be purged this purge cycle
+my @eachCycle = (); # namespaces to be purged every PURGE_INTERVAL
+
+my $startUpPurge = 1; # Flag for purging at startup
+
+my $log = logger('server');
+
+# create proxy methods
+{
+	my @methods = qw(
+		get set get_object set_object
+		clear purge remove size
+	);
+		
+	no strict 'refs';
+	for my $method (@methods) {
+		*{ __PACKAGE__ . "::$method" } = sub {
+			return shift->{_cache}->$method(@_);
+		};
+	}
+}
 
 sub init {
 	my $class = shift;
-	
-	# Clean up the cache at startup
-	__PACKAGE__->new->purge();
-	
-	# And continue to clean it up regularly
-	Slim::Utils::Timers::setTimer( undef, time() + $PURGE_INTERVAL, \&cleanup );
-}
-
-sub new { shift->instance(@_) }
-
-sub _new_instance {
+
+	# cause the default cache to be created if it is not already
+	__PACKAGE__->new();
+
+	# start purge routine in 10 seconds to purge all caches created during server and plugin startup
+	Slim::Utils::Timers::setTimer( undef, time() + 10, \&cleanup );
+}
+
+sub new {
 	my $class = shift;
-	
+	my $namespace = shift || $defaultNameSpace;
+
+	# return existing instance if exists for this namespace
+	return $caches{$namespace} if $caches{$namespace};
+
+	# otherwise create new cache object taking acount of additional params
+	my ($version, $noPeriodicPurge);
+
+	if ($namespace eq $defaultNameSpace) {
+		$version = $defaultVersion;
+	} else {
+		$version = shift || 0;
+		$noPeriodicPurge = shift;
+	}
+
 	my $cache = Cache::FileCache->new( {
-		namespace          => 'FileCache',
-		default_expires_in => $Cache::FileCache::EXPIRES_NEVER,
+		namespace          => $namespace,
+		default_expires_in => $DEFAULT_EXPIRES_TIME,
 		cache_root         => Slim::Utils::Prefs::get('cachedir'),
 		directory_umask    => umask(),
 	} );
@@ -76,58 +125,85 @@
 		_cache => $cache,
 	}, $class;
 	
-	# create proxy methods
-	{
-		my @methods = qw(
-			get set get_object set_object
-			clear purge remove size
-		);
-		
-		no strict 'refs';
-		for my $method (@methods) {
-			*{"$class\::$method"} = sub {
-				return shift->{_cache}->$method(@_);
-			};
+	# empty existing cache if version number is different
+	my $cacheVersion = $self->get('Slim::Utils::Cache-version');
+
+	unless (defined $cacheVersion && $cacheVersion eq $version) {
+
+		$log->info("Version changed for cache: $namespace - clearing out old entries");
+		$self->clear();
+		$self->set('Slim::Utils::Cache-version', $version, 'never');
+
+	}
+
+	# store cache object and add namespace to purge lists
+	$caches{$namespace} = $self;
+	push @thisCycle, $namespace;
+	push @eachCycle, $namespace unless $noPeriodicPurge;
+
+	return $self;
+}
+
+sub cleanup {
+	# This routine purges the complete list of namespaces, one per timer call
+	# NB Purging is expensive and blocks the server
+	#
+	# namespaces with $noPeriodicPurge set are only purged at server startup
+	# others are purged at max once per $PURGE_INTERVAL.
+	#
+	# To allow disks to spin down, each namespace is purged within a short period 
+	# and then no purging is done for $PURGE_INTERVAL
+	#
+	# After the startup purge, if any players are on it reschedules in $PURGE_RETRY
+
+	my $namespace; # namespace to purge this call
+	my $interval;  # interval to next call
+
+	# take one namespace from list to purge this cycle
+	$namespace = shift @thisCycle;
+
+	# after startup don't purge if a player is on - retry later
+	unless ($startUpPurge) {
+		for my $client ( Slim::Player::Client::clients() ) {
+			if ($client->power()) {
+				unshift @thisCycle, $namespace;
+				$namespace = undef;
+				$interval = $PURGE_RETRY;
+				last;
+			}
 		}
 	}
-	
-	return $self;
-}
-
-sub cleanup {
-	
-	# Use the same method the Scheduler uses to run only when idle
-	my $busy;
-	my $log = logger('server');
-	
-	for my $client ( Slim::Player::Client::clients() ) {
-
-		if (Slim::Player::Source::playmode($client) eq 'play' && 
-		    $client->isPlayer() && 
-		    $client->usage() < 0.5) {
-
-			$busy = 1;
-			last;
+
+	unless ($interval) {
+		if (@thisCycle) {
+			$interval = $startUpPurge ? 0.1 : $PURGE_NEXT;
+		} else {
+			$interval = $PURGE_INTERVAL;
+			$startUpPurge = 0;
+			push @thisCycle, @eachCycle;
 		}
 	}
 	
-	if ( !$busy ) {
-
-		$log->info("Cleaning up expired items...");
-	
-		__PACKAGE__->new->purge();
-	
-		$log->info("Done");
-		
-		Slim::Utils::Timers::setTimer( undef, time() + $PURGE_INTERVAL, \&cleanup );
-	}
-	else {
-
-		# try again soon
-		$log->info("Skipping cache cleanup, server is busy.");
-
-		Slim::Utils::Timers::setTimer( undef, time() + ($PURGE_INTERVAL / 6), \&cleanup );
-	}	
-}
+	my $now = Time::HiRes::time();
+	
+	if ($namespace && $caches{$namespace}) {
+
+		my $cache = $caches{$namespace};
+		my $lastpurge = $cache->get('Slim::Utils::Cache-purgetime'); 
+
+		unless ($lastpurge && ($now - $lastpurge) < $PURGE_INTERVAL) {
+			my $start = $now;
+			$cache->purge;
+			$cache->set('Slim::Utils::Cache-purgetime', $start, 'never');
+			$now = Time::HiRes::time();
+			$log->info(sprintf("Cache purge: $namespace - %f sec", $now - $start));
+		} else {
+			$log->info("Cache purge: $namespace - skipping, purged recently");
+		}
+	}
+
+	Slim::Utils::Timers::setTimer( undef, $now + $interval, \&cleanup );
+}
+
 
 1;

Modified: trunk/server/Slim/Utils/Scanner.pm
URL: http://svn.slimdevices.com/trunk/server/Slim/Utils/Scanner.pm?rev=10502&r1=10501&r2=10502&view=diff
==============================================================================
--- trunk/server/Slim/Utils/Scanner.pm (original)
+++ trunk/server/Slim/Utils/Scanner.pm Sun Oct 29 09:37:17 2006
@@ -1083,7 +1083,7 @@
 	$mmsURL =~ s/^http/mms/;
 	
 	# Cache this metadata for the MMS protocol handler to use
-	my $cache = Slim::Utils::Cache->instance;
+	my $cache = Slim::Utils::Cache->new;
 	$cache->set( 'wma_streamNum_' . $mmsURL, $streamNum,      '1 day' );	
 	$cache->set( 'wma_metadata_'  . $mmsURL, $wma,            '1 day' );
 	

Modified: trunk/server/Slim/Web/Graphics.pm
URL: http://svn.slimdevices.com/trunk/server/Slim/Web/Graphics.pm?rev=10502&r1=10501&r2=10502&view=diff
==============================================================================
--- trunk/server/Slim/Web/Graphics.pm (original)
+++ trunk/server/Slim/Web/Graphics.pm Sun Oct 29 09:37:17 2006
@@ -16,9 +16,12 @@
 
 my $log = logger('artwork');
 
-{
+my $canUseGD = 0;
+my $cache;
+
+sub init {
 	# Artwork resizing support by using GD, requires JPEG support built in
-	my $canUseGD = eval {
+	$canUseGD = eval {
 		require GD;
 		if (GD::Image->can('jpeg')) {
 			return 1;
@@ -27,9 +30,12 @@
 		}
 	};
 
-	sub serverResizesArt {
-		return $canUseGD;
-	}
+	# create cache for artwork which is not purged periodically due to potential size of cache
+	$cache = Slim::Utils::Cache->new('Artwork', 1, 1);
+}
+
+sub serverResizesArt {
+	return $canUseGD;
 }
 
 sub processCoverArtRequest {
@@ -88,7 +94,7 @@
 
 		$log->info("  artwork cache key: $cacheKey");
 
-		$cachedImage = Slim::Utils::Cache->new()->get($cacheKey);
+		$cachedImage = $cache->get($cacheKey);
 		
 		if ($cachedImage && $cachedImage->{'mtime'} != $obj->coverArtMtime($image)) {
 			$cachedImage = undef;
@@ -106,7 +112,7 @@
 
 		$cacheKey = "BLANK-$resizeMode-$requestedWidth-$requestedHeight-$requestedBackColour";	
 
-		$cachedImage = Slim::Utils::Cache->new()->get($cacheKey);
+		$cachedImage = $cache->get($cacheKey);
 
 		unless ($cachedImage) {
 
@@ -125,7 +131,7 @@
 
 	$log->info("  got cover art image $contentType of ". length($imageData) . " bytes");
 
-	if (serverResizesArt() && $typeToMethod{$contentType}) {
+	if ($canUseGD && $typeToMethod{$contentType}) {
 
 		# If this is a thumb, a size has been given, or this is a png and the background color isn't 100% transparent
 		# then the overhead of loading the image with GD is necessary.  Otherwise, the original content
@@ -311,7 +317,7 @@
 
 		$log->info("  caching result key: $cacheKey");
 
-		Slim::Utils::Cache->new()->set($cacheKey, $cached, "10days");
+		$cache->set($cacheKey, $cached, "10days");
 	}
 
 	return ($body, $mtime, $inode, $size, $contentType);

Modified: trunk/server/Slim/Web/HTTP.pm
URL: http://svn.slimdevices.com/trunk/server/Slim/Web/HTTP.pm?rev=10502&r1=10501&r2=10502&view=diff
==============================================================================
--- trunk/server/Slim/Web/HTTP.pm (original)
+++ trunk/server/Slim/Web/HTTP.pm Sun Oct 29 09:37:17 2006
@@ -131,6 +131,9 @@
 
 	# Initialize all the web page handlers.
 	Slim::Web::Pages::init();
+
+	# Initialize graphics resizing
+	Slim::Web::Graphics::init();
 
 	# if we've got an HTTP port specified, open it up!
 	if (Slim::Utils::Prefs::get('httpport')) {

Modified: trunk/server/slimserver.pl
URL: http://svn.slimdevices.com/trunk/server/slimserver.pl?rev=10502&r1=10501&r2=10502&view=diff
==============================================================================
--- trunk/server/slimserver.pl (original)
+++ trunk/server/slimserver.pl Sun Oct 29 09:37:17 2006
@@ -338,8 +338,8 @@
 	$log->info("Async Networking init...");
 	Slim::Networking::Async->init;
 	
-	$log->info("Cache init, cleaning up FileCache...");
-	Slim::Utils::Cache->init;
+	$log->info("Cache init...");
+	Slim::Utils::Cache->init();
 
 	if (!$noupnp) {
 		$log->info("UPnP init...");



More information about the checkins mailing list