@@ -8,6 +8,7 @@ import kotlinx.coroutines.*
88import kotlinx.coroutines.internal.*
99import kotlinx.coroutines.selects.*
1010import kotlin.jvm.*
11+ import kotlin.math.*
1112
1213/* *
1314 * Channel with array buffer of a fixed [capacity].
@@ -29,10 +30,14 @@ internal open class ArrayChannel<E>(
2930 }
3031
3132 private val lock = ReentrantLock ()
32- private val buffer: Array <Any ?> = arrayOfNulls<Any ?>(capacity)
33+ /*
34+ * Guarded by lock.
35+ * Allocate minimum of capacity and 16 to avoid excess memory pressure for large channels when it's not necessary.
36+ */
37+ private var buffer: Array <Any ?> = arrayOfNulls<Any ?>(min(capacity, 8 ))
3338 private var head: Int = 0
3439 @Volatile
35- private var size: Int = 0
40+ private var size: Int = 0 // Invariant: size <= capacity
3641
3742 protected final override val isBufferAlwaysEmpty: Boolean get() = false
3843 protected final override val isBufferEmpty: Boolean get() = size == 0
@@ -64,7 +69,8 @@ internal open class ArrayChannel<E>(
6469 }
6570 }
6671 }
67- buffer[(head + size) % capacity] = element // actually queue element
72+ ensureCapacity(size)
73+ buffer[(head + size) % buffer.size] = element // actually queue element
6874 return OFFER_SUCCESS
6975 }
7076 // size == capacity: full
@@ -112,7 +118,8 @@ internal open class ArrayChannel<E>(
112118 this .size = size // restore size
113119 return ALREADY_SELECTED
114120 }
115- buffer[(head + size) % capacity] = element // actually queue element
121+ ensureCapacity(size)
122+ buffer[(head + size) % buffer.size] = element // actually queue element
116123 return OFFER_SUCCESS
117124 }
118125 // size == capacity: full
@@ -123,6 +130,19 @@ internal open class ArrayChannel<E>(
123130 return receive!! .offerResult
124131 }
125132
133+ // Guarded by lock
134+ private fun ensureCapacity (currentSize : Int ) {
135+ if (currentSize >= buffer.size) {
136+ val newSize = min(buffer.size * 2 , capacity)
137+ val newBuffer = arrayOfNulls<Any ?>(newSize)
138+ for (i in 0 until currentSize) {
139+ newBuffer[i] = buffer[(head + i) % buffer.size]
140+ }
141+ buffer = newBuffer
142+ head = 0
143+ }
144+ }
145+
126146 // result is `E | POLL_FAILED | Closed`
127147 protected override fun pollInternal (): Any? {
128148 var send: Send ? = null
@@ -149,9 +169,9 @@ internal open class ArrayChannel<E>(
149169 }
150170 if (replacement != = POLL_FAILED && replacement !is Closed <* >) {
151171 this .size = size // restore size
152- buffer[(head + size) % capacity ] = replacement
172+ buffer[(head + size) % buffer.size ] = replacement
153173 }
154- head = (head + 1 ) % capacity
174+ head = (head + 1 ) % buffer.size
155175 }
156176 // complete send the we're taken replacement from
157177 if (token != null )
@@ -203,7 +223,7 @@ internal open class ArrayChannel<E>(
203223 }
204224 if (replacement != = POLL_FAILED && replacement !is Closed <* >) {
205225 this .size = size // restore size
206- buffer[(head + size) % capacity ] = replacement
226+ buffer[(head + size) % buffer.size ] = replacement
207227 } else {
208228 // failed to poll or is already closed --> let's try to select receiving this element from buffer
209229 if (! select.trySelect(null )) { // :todo: move trySelect completion outside of lock
@@ -212,7 +232,7 @@ internal open class ArrayChannel<E>(
212232 return ALREADY_SELECTED
213233 }
214234 }
215- head = (head + 1 ) % capacity
235+ head = (head + 1 ) % buffer.size
216236 }
217237 // complete send the we're taken replacement from
218238 if (token != null )
@@ -226,7 +246,7 @@ internal open class ArrayChannel<E>(
226246 lock.withLock {
227247 repeat(size) {
228248 buffer[head] = 0
229- head = (head + 1 ) % capacity
249+ head = (head + 1 ) % buffer.size
230250 }
231251 size = 0
232252 }
@@ -237,5 +257,5 @@ internal open class ArrayChannel<E>(
237257 // ------ debug ------
238258
239259 override val bufferDebugString: String
240- get() = " (buffer:capacity=${buffer.size} ,size=$size )"
260+ get() = " (buffer:capacity=$capacity ,size=$size )"
241261}
0 commit comments