From: Zooko O'Whielacronx Date: Thu, 19 Apr 2007 00:27:33 +0000 (-0700) Subject: copy repeatable_random from pyutil X-Git-Tag: tahoe_v0.1.0-0-UNSTABLE~59 X-Git-Url: https://git.rkrishnan.org/pf/content/en/service/(%5B%5E?a=commitdiff_plain;h=c5b9d2929d48a755c98bc3ac52a220ab3f2e957c;p=tahoe-lafs%2Ftahoe-lafs.git copy repeatable_random from pyutil --- diff --git a/src/allmydata/util/repeatable_random.py b/src/allmydata/util/repeatable_random.py new file mode 100644 index 00000000..0fa78e3d --- /dev/null +++ b/src/allmydata/util/repeatable_random.py @@ -0,0 +1,90 @@ +""" +If you execute force_repeatability() then the following things are changed in the runtime: + +1. random.random() and its sibling functions, and random.Random.seed() in the random module are seeded with a known seed so that they will return the same sequence on each run. +2. os.urandom() is replaced by a fake urandom that returns a pseudorandom sequence. +3. time.time() is replaced by a fake time that returns an incrementing number. (Original time.time is available as time.realtime.) + +Which seed will be used? + +If the environment variable REPEATABLE_RANDOMNESS_SEED is set, then it will use that. Else, it will use the current real time. In either case it logs the seed that it used. + +Caveats: + +1. If some code has acquired a random.Random object before force_repeatability() is executed, then that Random object will produce non-reproducible results. For example, the tempfile module in the Python Standard Library does this. +2. Likewise if some code called time.time() before force_repeatability() was called, then it will have gotten a real time stamp. For example, trial does this. (Then it later subtracts that real timestamp from a faketime timestamp to calculate elapsed time, resulting in a large negative elapsed time.) +3. The output from the fake urandom has weird distribution for performance reasons-- every byte after the first 20 bytes resulting from a single call to os.urandom() is zero. In practice this hasn't caused any problems. +""" + +import os, random, time +if not hasattr(time, "realtime"): + time.realtime = time.time +if not hasattr(os, "realurandom"): + os.realurandom = os.urandom +if not hasattr(random, "realseed"): + random.realseed = random.seed + +tdelta = 0 +seeded = False +def force_repeatability(): + now = 1043659734.0 + def faketime(): + global tdelta + tdelta += 1 + return now + tdelta + time.faketime = faketime + time.time = faketime + + from idlib import i2b + def fakeurandom(n): + if n > 20: + z = i2b(random.getrandbits(20*8)) + elif n == 0: + return '' + else: + z = i2b(random.getrandbits(n*8)) + x = z + "0" * (n-len(z)) + assert len(x) == n + return x + os.fakeurandom = fakeurandom + os.urandom = fakeurandom + + global seeded + if not seeded: + SEED = os.environ.get('REPEATABLE_RANDOMNESS_SEED', None) + + if SEED is None: + # Generate a seed which is integral and fairly short (to ease cut-and-paste, writing it down, etc.). + t = time.realtime() + subsec = t % 1 + t += (subsec * 1000000) + t %= 1000000 + SEED = long(t) + import sys + sys.stdout.write("REPEATABLE_RANDOMNESS_SEED: %s\n" % SEED) ; sys.stdout.flush() + sys.stdout.write("In order to reproduce this run of the code, set the environment variable \"REPEATABLE_RANDOMNESS_SEED\" to %s before executing.\n" % SEED) ; sys.stdout.flush() + random.seed(SEED) + + def seed_which_refuses(a): + sys.stdout.write("I refuse to reseed to %s. Go away!\n" % (a,)) ; sys.stdout.flush() + return + random.realseed = random.seed + random.seed = seed_which_refuses + seeded = True + + import setutil + setutil.RandomSet.DETERMINISTIC = True + +def restore_real_clock(): + time.time = time.realtime + +def restore_real_urandom(): + os.urandom = os.realurandom + +def restore_real_seed(): + random.seed = random.realseed + +def restore_non_repeatability(): + restore_real_seed() + restore_real_urandom() + restore_real_clock()