@@ -32,27 +32,62 @@ internal suspend inline fun Flow<Payload>.collectLimiting(limiter: Limiter, cros
32
32
}
33
33
}
34
34
35
- // TODO revisit 2 atomics and sync object
35
+ /* *
36
+ * Maintains the amount of requests which the client is ready to consume and
37
+ * prevents sending further updates by suspending the sending coroutine
38
+ * if this amount reaches 0.
39
+ *
40
+ * ### Operation
41
+ *
42
+ * Each [useRequest] call decrements the maintained requests amount.
43
+ * Calling coroutine is suspended when this amount reaches 0.
44
+ * The coroutine is resumed when [updateRequests] is called.
45
+ *
46
+ * ### Unbounded mode
47
+ *
48
+ * Limiter enters an unbounded mode when:
49
+ * * [Limiter] is created passing `Int.MAX_VALUE` as `initial`
50
+ * * client sends a `RequestN` frame with `Int.MAX_VALUE`
51
+ * * Internal Long counter overflows
52
+ *
53
+ * In unbounded mode Limiter will assume that the client
54
+ * is able to process requests without limitations, all further
55
+ * [updateRequests] will be NOP and [useRequest] will never suspend.
56
+ */
36
57
internal class Limiter (initial : Int ) : SynchronizedObject() {
37
- private val requests = atomic(initial)
38
- private val awaiter = atomic<CancellableContinuation <Unit >? > (null )
58
+ private val requests: AtomicLong = atomic(initial.toLong())
59
+ private val unbounded: AtomicBoolean = atomic(initial == Int .MAX_VALUE )
60
+ private var awaiter: CancellableContinuation <Unit >? = null
39
61
40
62
fun updateRequests (n : Int ) {
41
- if (n <= 0 ) return
63
+ if (n <= 0 || unbounded.value ) return
42
64
synchronized(this ) {
43
- requests + = n
44
- awaiter.getAndSet(null )?.takeIf (CancellableContinuation <Unit >::isActive)?.resume(Unit )
65
+ val updatedRequests = requests.value + n.toLong()
66
+ if (updatedRequests < 0 ) {
67
+ unbounded.value = true
68
+ requests.value = Long .MAX_VALUE
69
+ } else {
70
+ requests.value = updatedRequests
71
+ }
72
+
73
+ if (awaiter?.isActive == true ) {
74
+ awaiter?.resume(Unit )
75
+ awaiter = null
76
+ }
45
77
}
46
78
}
47
79
48
80
suspend fun useRequest () {
49
- if (requests.getAndDecrement () > 0 ) {
81
+ if (unbounded.value || requests.decrementAndGet () >= 0 ) {
50
82
currentCoroutineContext().ensureActive()
51
83
} else {
52
- suspendCancellableCoroutine<Unit > {
84
+ suspendCancellableCoroutine<Unit > { continuation ->
53
85
synchronized(this ) {
54
- awaiter.value = it
55
- if (requests.value >= 0 && it.isActive) it.resume(Unit )
86
+ if (requests.value >= 0 && continuation.isActive) {
87
+ continuation.resume(Unit )
88
+ } else {
89
+ this .awaiter = continuation
90
+ }
56
91
}
57
92
}
58
93
}
0 commit comments