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