Fix a race condition on the CGI script.

* wrap/python/ajax/spot.in: Create all cache files in a temporary
directory, and only rename this directory at the end.  This way if
two processes are processing the same request, they won't attempt
to populate the same directory (and only one of the first of two
renames will succeed, but that is OK).
This commit is contained in:
Alexandre Duret-Lutz 2012-02-15 11:57:13 +01:00
parent 547715463a
commit 62914059f7
2 changed files with 37 additions and 10 deletions

View file

@ -1,3 +1,13 @@
2012-02-15 Alexandre Duret-Lutz <adl@lrde.epita.fr>
Fix a race condition on the CGI script.
* wrap/python/ajax/spot.in: Create all cache files in a temporary
directory, and only rename this directory at the end. This way if
two processes are processing the same request, they won't attempt
to populate the same directory (and only one of the first of two
renames will succeed, but that is OK).
2012-01-24 Alexandre Duret-Lutz <adl@lrde.epita.fr> 2012-01-24 Alexandre Duret-Lutz <adl@lrde.epita.fr>
Fix a segfault reported by Etienne Renault using dve2check. Fix a segfault reported by Etienne Renault using dve2check.

View file

@ -37,15 +37,15 @@ qs = os.getenv('QUERY_STRING')
if qs: if qs:
import hashlib import hashlib
# We (optimistically) assume no collision from sha1(qs) # We (optimistically) assume no collision from sha1(qs)
imgprefix = imgdir + '/' + hashlib.sha1(qs).hexdigest() cachedir = imgdir + '/' + hashlib.sha1(qs).hexdigest()
cachename = imgprefix + '/html' cachename = cachedir + '/html'
try: try:
# Is this a request we have already processed? # Is this a request we have already processed?
cache = open(cachename, "r", 0) cache = open(cachename, "r", 0)
print cache.read() print cache.read()
# Touch the directory containing the files we used, so # Touch the directory containing the files we used, so
# it that it survives the browser's cache. # it that it survives the browser's cache.
os.utime(imgprefix, None) os.utime(cachedir, None)
exit(0) exit(0)
except IOError: except IOError:
# We failed to open the file. # We failed to open the file.
@ -90,6 +90,11 @@ import signal
import time import time
import os.path import os.path
# We do not output in cachedir directely, in case two
# CGI scripts process the same request concurrently.
tmpdir = cachedir + '-' + str(os.getpid())
cachename = tmpdir + '/html'
sys.stdout.flush() sys.stdout.flush()
# Reopen stdout without buffering # Reopen stdout without buffering
sys.stdout = os.fdopen(sys.stdout.fileno(), "w", 0) sys.stdout = os.fdopen(sys.stdout.fileno(), "w", 0)
@ -98,11 +103,9 @@ sys.stdout = os.fdopen(sys.stdout.fileno(), "w", 0)
# even errors from subprocesses get printed). # even errors from subprocesses get printed).
os.dup2(sys.stdout.fileno(), sys.stderr.fileno()) os.dup2(sys.stdout.fileno(), sys.stderr.fileno())
# Create a cache directory if one does not already exist. # Create the temporary cache directory
try: os.mkdir(tmpdir, 0755)
os.mkdir(imgprefix, 0755)
except OSError:
pass
# Redirect stdout to the cache file, at a low level # Redirect stdout to the cache file, at a low level
# for similar reason. # for similar reason.
fd = os.open(cachename, os.O_CREAT | os.O_WRONLY, 0644) fd = os.open(cachename, os.O_CREAT | os.O_WRONLY, 0644)
@ -113,6 +116,20 @@ def finish(kill = False):
os.dup2(sys.stderr.fileno(), sys.stdout.fileno()) os.dup2(sys.stderr.fileno(), sys.stdout.fileno())
cache = open(cachename, "r", 0) cache = open(cachename, "r", 0)
print cache.read() print cache.read()
# Rename tmpdir to its permanent name for caching purpose.
# os.rename will fail if cachedir already exist. Since we tested
# that initially, it can only happen when two CGI script are
# processing the same request concurrently. In that case the
# other result is as good as ours, so we just ignore the error.
# (We don't bother removing the temporary directory -- it will be
# removed by the next cache prune and cannot be created again in
# the meantime.)
try:
os.rename(tmpdir, cachedir)
except OSError:
pass
if kill: if kill:
os.kill(0, signal.SIGTERM) os.kill(0, signal.SIGTERM)
# Should we prune the cache? # Should we prune the cache?
@ -187,7 +204,7 @@ def render_dot(basename):
reset_alarm() reset_alarm()
# Create an unused hardlink that point to the output picture # Create an unused hardlink that point to the output picture
# just to remember how many cache entries are sharing it. # just to remember how many cache entries are sharing it.
os.link(outname, imgprefix + "/" + ext) os.link(outname, tmpdir + "/" + ext)
b = cgi.escape(basename) b = cgi.escape(basename)
if svg_output: if svg_output:
print ('<object type="image/svg+xml" data="' + b + '.svg">' print ('<object type="image/svg+xml" data="' + b + '.svg">'
@ -210,7 +227,7 @@ def render_dot_maybe(dotsrc, dont_run_dot):
dotout.close() dotout.close()
# Create an unused hardlink that points to the output picture # Create an unused hardlink that points to the output picture
# just to remember how many cache entries are sharing it. # just to remember how many cache entries are sharing it.
os.link(dotname, imgprefix + "/txt") os.link(dotname, tmpdir + "/txt")
if dont_run_dot: if dont_run_dot:
print ('<p>' + dont_run_dot + ''' to be rendered on-line. However print ('<p>' + dont_run_dot + ''' to be rendered on-line. However