From 3216494cf6e0ad9425fc80798cc98495f0e62b73 Mon Sep 17 00:00:00 2001 From: Alexandre Duret-Lutz Date: Wed, 8 Jun 2011 16:39:04 +0200 Subject: [PATCH] Implement cache pruning in the CGI script. * wrap/python/ajax/spot.in (finish): Prune the cache once in a while. We rely on hardlinks for garbage collecting the pictures and dot sources that may be shared by different requests. --- ChangeLog | 8 ++++++++ wrap/python/ajax/spot.in | 31 ++++++++++++++++++++++++++++--- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/ChangeLog b/ChangeLog index e08ce24df..254237b3f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +2011-06-08 Alexandre Duret-Lutz + + Implement cache pruning in the CGI script. + + * wrap/python/ajax/spot.in (finish): Prune the cache once in a + while. We rely on hardlinks for garbage collecting the pictures + and dot sources that may be shared by different requests. + 2011-06-08 Alexandre Duret-Lutz Cache dot sources in the CGI script. diff --git a/wrap/python/ajax/spot.in b/wrap/python/ajax/spot.in index 7a3b5b781..f6a7634f3 100755 --- a/wrap/python/ajax/spot.in +++ b/wrap/python/ajax/spot.in @@ -38,7 +38,7 @@ if qs: import hashlib # We (optimistically) assume no collision from sha1(qs) imgprefix = imgdir + '/' + hashlib.sha1(qs).hexdigest() - cachename = imgprefix + '.html' + cachename = imgprefix + '/html' try: # Is this a request we have already processed? cache = open(cachename, "r", 0) @@ -84,6 +84,8 @@ import sys import cgi import cgitb; cgitb.enable() import signal +import time +import os.path sys.stdout.flush() # Reopen stdout without buffering @@ -95,6 +97,7 @@ os.dup2(sys.stdout.fileno(), sys.stderr.fileno()) # Redirect stdout to the cache file, at a low-level # for similar reason. +os.mkdir(imgprefix, 0755) fd = os.open(cachename, os.O_CREAT | os.O_WRONLY, 0644) os.dup2(fd, sys.stdout.fileno()) @@ -105,9 +108,25 @@ def finish(kill = False): print cache.read() if kill: os.kill(0, signal.SIGTERM) + # Should we prune the cache? + stamp = imgdir + '/cache.stamp' + now = time.time() + try: + # Prune at most once every 15 minutes + if now - os.path.getmtime(stamp) < 900: + exit(0) + except OSError: + pass + # Erase all directories that are older then 60 minutes, and all + # files that have only one hardlinks. Files that have more than + # one hardlinks are referenced to by directories; so the hardlink + # count will decrease when the directory is purged. + os.system('find ' + imgdir + ' -mindepth 1 -maxdepth 1 -mmin +60 ' + + '\( -type d -o -links 1 \) -exec rm -rf {} +') + # Create or update the stamp so we know when to run the next prune. + open(stamp, "w", 0) exit(0) - # Assume Spot is installed sys.path.insert(0, '@pythondir@') @@ -159,6 +178,9 @@ def render_dot(basename): os.spawnlp(os.P_WAIT, dot, dot, dot_bgcolor, '-T' + ext, '-Gsize=8.2,8.2', '-o', outname, basename + '.txt') reset_alarm() + # Create an unused hardlink that point to the output picture + # just to remember how many cache entries are sharing it. + os.link(outname, imgprefix + "/" + ext) b = cgi.escape(basename) if svg_output: print ('' @@ -179,6 +201,9 @@ def render_dot_maybe(dotsrc, dont_run_dot): dotout = open(dotname, "w", 0) dotout.write(dotsrc) dotout.close() + # Create an unused hardlink that point to the output picture + # just to remember how many cache entries are sharing it. + os.link(dotname, imgprefix + "/txt") if dont_run_dot: print ('

' + dont_run_dot + ''' to be rendered on-line. However @@ -226,7 +251,7 @@ def print_stats(automaton): print "no acceptance condition (all cycles are accepting)" print "

" # Decide whether we will render the automaton or not. - # (A webserver is not a calcul center...) + # (A webserver is not a computation center...) if stats.states > 64: return "Automaton has too much states" if float(stats.transitions)/stats.states > 10: