1 module discord.w.ratelimiter; 2 3 import core.time; 4 5 mixin template RateLimitObject(Duration repeat) 6 { 7 import std.datetime.systime; 8 import core.sync.mutex; 9 import vibe.core.core; 10 import core.time; 11 12 __gshared Mutex lock; 13 __gshared long access; 14 15 void waitFor() @trusted 16 { 17 if (!lock) 18 lock = new Mutex(); 19 while (true) 20 { 21 long t; 22 synchronized (lock) 23 { 24 auto now = Clock.currStdTime; 25 t = now - access; 26 if (t > repeat.total!"hnsecs") 27 { 28 access = now; 29 return; 30 } 31 } 32 auto wait = repeat - (t + 1000).hnsecs; 33 if (wait.total!"hnsecs" > 0) 34 sleep(wait); 35 } 36 } 37 } 38 39 mixin template DynamicRateLimitObject(size_t limit, Duration range, Duration repeat) 40 { 41 import std.datetime.systime; 42 import core.sync.mutex; 43 import vibe.core.core; 44 import core.time; 45 46 __gshared Mutex lock; 47 48 static assert(limit > 0, "Requires a limit of at least 1"); 49 50 __gshared size_t index; 51 __gshared long[limit] accesses; 52 53 void waitFor() @trusted 54 { 55 if (!lock) 56 lock = new Mutex(); 57 while (true) 58 { 59 Duration wait; 60 synchronized (lock) 61 { 62 auto now = Clock.currStdTime; 63 auto prevAccess = accesses[(index + limit - 1) % $]; 64 auto access = accesses[index]; 65 bool hasLeft = now - access >= range.total!"hnsecs"; 66 if (hasLeft) 67 { 68 auto t = now - prevAccess; 69 if (t > repeat.total!"hnsecs") 70 { 71 accesses[index] = now; 72 index = (index + 1) % accesses.length; 73 return; 74 } 75 wait = repeat - (t + 1000).hnsecs; 76 } 77 else 78 wait = range - (now - access + 1000).hnsecs; 79 } 80 if (wait.total!"hnsecs" > 0) 81 sleep(wait); 82 } 83 } 84 } 85 86 unittest 87 { 88 import std.datetime.stopwatch; 89 import vibe.core.core; 90 91 auto a = runTask({ 92 mixin DynamicRateLimitObject!(4, 60.msecs, 10.msecs) testLimit; 93 StopWatch sw; 94 sw.start(); 95 testLimit.waitFor(); 96 assert(sw.peek >= 0.msecs && sw.peek < 10.msecs); 97 testLimit.waitFor(); 98 assert(sw.peek >= 10.msecs && sw.peek < 20.msecs); 99 testLimit.waitFor(); 100 assert(sw.peek >= 20.msecs && sw.peek < 30.msecs); 101 testLimit.waitFor(); 102 assert(sw.peek >= 30.msecs && sw.peek < 40.msecs); 103 testLimit.waitFor(); 104 assert(sw.peek >= 60.msecs && sw.peek < 70.msecs); 105 sw.stop(); 106 }); 107 108 auto b = runTask({ 109 mixin RateLimitObject!(10.msecs) testLimit; 110 StopWatch sw; 111 sw.start(); 112 testLimit.waitFor(); 113 assert(sw.peek >= 0.msecs && sw.peek < 10.msecs); 114 testLimit.waitFor(); 115 assert(sw.peek >= 10.msecs && sw.peek < 20.msecs); 116 testLimit.waitFor(); 117 assert(sw.peek >= 20.msecs && sw.peek < 30.msecs); 118 testLimit.waitFor(); 119 assert(sw.peek >= 30.msecs && sw.peek < 40.msecs); 120 sw.stop(); 121 }); 122 123 a.join(); 124 b.join(); 125 }