1 module exec.cache; 2 3 import exec.iexecprovider; 4 import std.typecons: Tuple; 5 import std.traits: ReturnType; 6 import std.algorithm: sort; 7 8 import vibe.core.core : sleep; 9 import core.time : msecs; 10 11 /++ 12 Execution provider that implements caching 13 for an allowed white list of source code files. 14 +/ 15 class Cache: IExecProvider 16 { 17 private IExecProvider realExecProvider_; 18 19 private alias ResultTuple = ReturnType!compileAndExecute; 20 private alias HashType = ReturnType!getSourceCodeHash; 21 private ResultTuple[HashType] sourceHashToOutput_; 22 private HashType[] allowedSources_; ///< Hash of allowed source code contents 23 private uint minDelayMs_, maxDelayMs_; 24 25 /++ 26 Params: 27 realExecProvider = the execution provider if cache 28 can't give the answer 29 minDelayMs = minimum delay to add randomly for each 30 cache reply 31 maxDelayMs = maximum delay to add randomly for each 32 cache reply 33 sourceCodeWhitelist = raw source code only allowed 34 to be cached 35 +/ 36 this(IExecProvider realExecProvider, 37 uint minDelayMs, 38 uint maxDelayMs, 39 string[] sourceCodeWhitelist) 40 { 41 import std.algorithm: map; 42 import std.array: array; 43 this.realExecProvider_ = realExecProvider; 44 this.allowedSources_ = sourceCodeWhitelist 45 .map!(x => x.getSourceCodeHash) 46 .array; 47 sort(this.allowedSources_); 48 assert(sourceCodeWhitelist.length == this.allowedSources_.length); 49 50 this.minDelayMs_ = minDelayMs; 51 this.maxDelayMs_ = maxDelayMs; 52 } 53 54 Tuple!(string, "output", bool, "success") compileAndExecute(string source) 55 { 56 import std.range: assumeSorted; 57 import std.algorithm: canFind; 58 auto hash = getSourceCodeHash(source); 59 60 if (!assumeSorted(this.allowedSources_).canFind(hash)) { 61 auto result = realExecProvider_.compileAndExecute(source); 62 return result; 63 } else if (auto cache = hash in sourceHashToOutput_) { 64 import std.random: uniform; 65 auto delay = uniform(minDelayMs_, maxDelayMs_); 66 sleep(delay.msecs); 67 return *cache; 68 } else { 69 auto result = realExecProvider_.compileAndExecute(source); 70 sourceHashToOutput_[hash] = result; 71 return result; 72 } 73 } 74 } 75 76 private uint getSourceCodeHash(string source) 77 { 78 import std.digest.crc: crc32Of; 79 auto crc = crc32Of(source); 80 return (cast(uint)crc[0]) 81 | ((cast(uint)crc[1]) << 8) 82 | ((cast(uint)crc[2]) << 16) 83 | ((cast(uint)crc[3]) << 24); 84 } 85 86 unittest 87 { 88 auto sourceCode1 = "test123"; 89 auto sourceCode2 = "void main() {}"; 90 auto sourceCode3 = "12838389349493"; 91 import std.stdio: writeln; 92 auto hash1 = getSourceCodeHash(sourceCode1); 93 writeln("hash1 = ", hash1); 94 auto hash2 = getSourceCodeHash(sourceCode2); 95 writeln("hash2 = ", hash2); 96 auto hash3 = getSourceCodeHash(sourceCode3); 97 writeln("hash3 = ", hash3); 98 99 assert(hash1 != hash2); 100 assert(hash2 != hash3); 101 102 auto cache = new Cache(null, 0, 0, 103 [ sourceCode1, sourceCode2, sourceCode3 ]); 104 assert(cache.allowedSources_.length == 3); 105 import std.algorithm: canFind; 106 assert(cache.allowedSources_.canFind(hash1)); 107 assert(cache.allowedSources_.canFind(hash2)); 108 assert(cache.allowedSources_.canFind(hash3)); 109 } 110