Skip to content

Commit dade78e

Browse files
author
Dmitry Voronov
committed
[ETCM-266]-replaced-rate-limiter-built-on-twitter
1 parent 84a2f9b commit dade78e

File tree

3 files changed

+95
-7
lines changed

3 files changed

+95
-7
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package io.iohk.ethereum.db.cache
2+
3+
import java.util
4+
5+
/**
6+
* Simplest-possible implementation of insert-time based LRU
7+
*
8+
* @param maxElements - capacity of the inner map, between 0 and this value.
9+
* @param ttlMillis - entry invalidation interval
10+
* @tparam K - stands for the type of the keys
11+
*/
12+
class SimpleLRU[K](maxElements: Int, ttlMillis: Long) {
13+
14+
private[this] val inner = new util.LinkedHashMap[K, Long](maxElements, 0.75f, false) {
15+
override def removeEldestEntry(old: util.Map.Entry[K, Long]): Boolean = {
16+
size() > maxElements || tooOld(old.getValue)
17+
}
18+
}
19+
20+
/**
21+
* @param key - will be searching and added
22+
* @return
23+
* true - if there was such entry already
24+
* false - if not
25+
*/
26+
def checkAndRefreshEntry(key: K): Boolean = inner.synchronized {
27+
val existing = Option(inner.put(key, currentTime))
28+
existing.exists(!tooOld(_))
29+
}
30+
31+
// Override this to test
32+
protected def currentTime: Long = System.currentTimeMillis()
33+
34+
private[this] def tooOld(time: Long) = time + ttlMillis < currentTime
35+
36+
}

src/main/scala/io/iohk/ethereum/jsonrpc/server/http/JsonRpcHttpServer.scala

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ trait JsonRpcHttpServer extends Json4sSupport with Logger {
5454
}
5555
.result()
5656

57-
protected val rateLimit = new RateLimit(config.rateLimit)
57+
val rateLimit = new RateLimit(config.rateLimit)
5858

5959
val route: Route = cors(corsSettings) {
6060
(path("healthcheck") & pathEndOrSingleSlash & get) {
@@ -66,12 +66,11 @@ trait JsonRpcHttpServer extends Json4sSupport with Logger {
6666
entity(as[JsonRpcRequest]) {
6767
case statusReq if statusReq.method == FaucetJsonRpcController.Status =>
6868
handleRequest(statusReq)
69-
case jsonReq =>
70-
rateLimit {
71-
handleRequest(jsonReq)
72-
}
73-
// TODO: separate paths for single and multiple requests
74-
// TODO: to prevent repeated body and json parsing
69+
case jsonReq => rateLimit {
70+
handleRequest(jsonReq)
71+
}
72+
// TODO: separate paths for single and multiple requests
73+
// TODO: to prevent repeated body and json parsing
7574
} ~ entity(as[Seq[JsonRpcRequest]]) {
7675
case _ if config.rateLimit.enabled =>
7776
complete(StatusCodes.MethodNotAllowed, JsonRpcError.MethodNotFound)
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package io.iohk.ethereum.db.cache
2+
3+
import org.scalatest.wordspec.AnyWordSpec
4+
5+
class SimpleLRUSpec extends AnyWordSpec {
6+
7+
var time = 0L
8+
9+
private object MockedLRU extends SimpleLRU[Int](10, 100) {
10+
override protected def currentTime: Long = SimpleLRUSpec.this.time
11+
}
12+
13+
"It" should {
14+
15+
"Respond false with all missing entries and preserve length" in {
16+
val results = (0 until 100).map { i => MockedLRU.checkAndRefreshEntry(i) }
17+
assert( results.forall( _ == false ) )
18+
}
19+
20+
"Drop the records according to maxElements" in {
21+
val existing = (90 until 100).map { i => MockedLRU.checkAndRefreshEntry(i) }
22+
assert( existing.forall( _ == true ) )
23+
24+
// maxElements guaranteed that we have no space for those
25+
val absent = (0 until 10).map { i => MockedLRU.checkAndRefreshEntry(i) }
26+
assert( absent.forall( _ == false ) )
27+
}
28+
29+
"Obsolete the records according to ttlMillis" in {
30+
this.time = 0L
31+
var results = (0 until 5).map { i => MockedLRU.checkAndRefreshEntry(i) }
32+
assert( results.forall( _ == true ) )
33+
34+
this.time = 50L
35+
results = (5 until 10).map { i => MockedLRU.checkAndRefreshEntry(i) }
36+
assert( results.forall( _ == true ) )
37+
38+
this.time = 150L
39+
results = (0 until 5).map { i => MockedLRU.checkAndRefreshEntry(i) }
40+
assert( results.forall( _ == false ) )
41+
42+
// those were added at 50ms and still valid
43+
results = (5 until 10).map { i => MockedLRU.checkAndRefreshEntry(i) }
44+
assert( results.forall( _ == true ) )
45+
46+
this.time = 300L
47+
results = (0 until 10).map { i => MockedLRU.checkAndRefreshEntry(i) }
48+
assert( results.forall( _ == false ) )
49+
}
50+
51+
}
52+
53+
}

0 commit comments

Comments
 (0)