@@ -2,15 +2,20 @@ package com.ably.chat
2
2
3
3
import io.ably.lib.types.AblyException
4
4
import io.ably.lib.types.ErrorInfo
5
+ import java.util.concurrent.LinkedBlockingQueue
5
6
import kotlin.time.DurationUnit
6
7
import kotlin.time.toDuration
8
+ import kotlinx.coroutines.CompletableDeferred
9
+ import kotlinx.coroutines.CoroutineName
7
10
import kotlinx.coroutines.CoroutineScope
8
11
import kotlinx.coroutines.Deferred
9
12
import kotlinx.coroutines.Dispatchers
10
13
import kotlinx.coroutines.awaitAll
11
14
import kotlinx.coroutines.delay
15
+ import kotlinx.coroutines.launch
12
16
import kotlinx.coroutines.runBlocking
13
17
import kotlinx.coroutines.test.runTest
18
+ import kotlinx.coroutines.withContext
14
19
import org.hamcrest.CoreMatchers.containsString
15
20
import org.junit.Assert
16
21
import org.junit.Test
@@ -69,8 +74,8 @@ class AtomicCoroutineScopeTest {
69
74
}
70
75
operationInProgress = true
71
76
delay((200 .. 800 ).random().toDuration(DurationUnit .MILLISECONDS ))
72
- operationInProgress = false
73
77
val returnValue = counter++
78
+ operationInProgress = false
74
79
return @async returnValue
75
80
}
76
81
deferredResults.add(result)
@@ -86,24 +91,63 @@ class AtomicCoroutineScopeTest {
86
91
}
87
92
88
93
@Test
89
- fun `should perform mutually exclusive operations with custom scope` () = runTest {
90
- val sequentialScope = CoroutineScope (Dispatchers .Default .limitedParallelism(1 ))
94
+ fun `Concurrently perform mutually exclusive operations` () = runTest {
95
+ val atomicCoroutineScope = AtomicCoroutineScope ()
96
+ val deferredResults = LinkedBlockingQueue <CompletableDeferred <Unit >>()
97
+
98
+ var operationInProgress = false
99
+ var counter = 0
100
+ val countedValues = mutableListOf<Int >()
101
+
102
+ // Concurrently schedule 100000 jobs from multiple threads
103
+ withContext(Dispatchers .IO ) {
104
+ repeat(100000 ) {
105
+ launch {
106
+ val result = atomicCoroutineScope.async {
107
+ if (operationInProgress) {
108
+ error(" Can't perform operation when other operation is going on" )
109
+ }
110
+ operationInProgress = true
111
+ countedValues.add(counter++ )
112
+ operationInProgress = false
113
+ }
114
+ deferredResults.add(result)
115
+ }
116
+ }
117
+ }
118
+
119
+ Assert .assertFalse(atomicCoroutineScope.finishedProcessing)
120
+ assertWaiter { deferredResults.size == 100000 }
121
+
122
+ deferredResults.awaitAll()
123
+ assertWaiter { atomicCoroutineScope.finishedProcessing }
124
+ Assert .assertEquals((0 .. 99999 ).toList(), countedValues)
125
+ }
126
+
127
+ @Test
128
+ fun `should perform mutually exclusive operations with custom named scope` () = runTest {
129
+ val sequentialScope = CoroutineScope (Dispatchers .Default .limitedParallelism(1 ) + CoroutineName (" roomId" ))
91
130
val atomicCoroutineScope = AtomicCoroutineScope (sequentialScope)
92
131
val deferredResults = mutableListOf<Deferred <Int >>()
132
+
93
133
val contexts = mutableListOf<String >()
134
+ val contextNames = mutableListOf<String >()
135
+
94
136
var operationInProgress = false
95
137
var counter = 0
96
138
97
139
repeat(10 ) {
98
140
val result = atomicCoroutineScope.async {
99
- contexts.add(this .coroutineContext.toString())
100
141
if (operationInProgress) {
101
142
error(" Can't perform operation when other operation is going on" )
102
143
}
103
144
operationInProgress = true
145
+ contexts.add(coroutineContext.toString())
146
+ contextNames.add(coroutineContext[CoroutineName ]!! .name)
147
+
104
148
delay((200 .. 800 ).random().toDuration(DurationUnit .MILLISECONDS ))
105
- operationInProgress = false
106
149
val returnValue = counter++
150
+ operationInProgress = false
107
151
return @async returnValue
108
152
}
109
153
deferredResults.add(result)
@@ -113,6 +157,7 @@ class AtomicCoroutineScopeTest {
113
157
val results = deferredResults.awaitAll()
114
158
repeat(10 ) {
115
159
Assert .assertEquals(it, results[it])
160
+ Assert .assertEquals(" roomId" , contextNames[it])
116
161
Assert .assertThat(contexts[it], containsString(" Dispatchers.Default.limitedParallelism(1)" ))
117
162
}
118
163
Assert .assertTrue(atomicCoroutineScope.finishedProcessing)
@@ -138,14 +183,14 @@ class AtomicCoroutineScopeTest {
138
183
// Add more jobs, will be processed based on priority
139
184
repeat(10 ) {
140
185
val result = atomicCoroutineScope.async(10 - it) {
141
- contexts.add(this .coroutineContext.toString())
142
186
if (operationInProgress) {
143
187
error(" Can't perform operation when other operation is going on" )
144
188
}
145
189
operationInProgress = true
190
+ contexts.add(this .coroutineContext.toString())
146
191
delay((200 .. 800 ).random().toDuration(DurationUnit .MILLISECONDS ))
147
- operationInProgress = false
148
192
val returnValue = counter++
193
+ operationInProgress = false
149
194
return @async returnValue
150
195
}
151
196
deferredResults.add(result)
@@ -161,6 +206,46 @@ class AtomicCoroutineScopeTest {
161
206
}
162
207
163
208
@Test
164
- fun `reuse AtomicCoroutineScope once cancelled` () = runTest {
209
+ fun `Concurrently execute mutually exclusive operations with given priority` () = runTest {
210
+ val atomicCoroutineScope = AtomicCoroutineScope ()
211
+ val deferredResults = LinkedBlockingQueue <Deferred <Unit >>()
212
+
213
+ var operationInProgress = false
214
+ val processedValues = mutableListOf<Int >()
215
+
216
+ // This will start first internal operation
217
+ deferredResults.add(
218
+ atomicCoroutineScope.async {
219
+ delay(1000 )
220
+ processedValues.add(1000 )
221
+ Unit
222
+ },
223
+ )
224
+
225
+ // Add more jobs, will be processed based on priority
226
+ // Concurrently schedule 1000 jobs with incremental priority from multiple threads
227
+ withContext(Dispatchers .IO ) {
228
+ repeat(1000 ) {
229
+ launch {
230
+ val result = atomicCoroutineScope.async(1000 - it) {
231
+ if (operationInProgress) {
232
+ error(" Can't perform operation when other operation is going on" )
233
+ }
234
+ operationInProgress = true
235
+ processedValues.add(it)
236
+ operationInProgress = false
237
+ }
238
+ deferredResults.add(result)
239
+ }
240
+ }
241
+ }
242
+
243
+ Assert .assertFalse(atomicCoroutineScope.finishedProcessing)
244
+ deferredResults.awaitAll()
245
+ val expectedResults = (1000 downTo 0 ).toList()
246
+ repeat(1001 ) {
247
+ Assert .assertEquals(expectedResults[it], processedValues[it])
248
+ }
249
+ Assert .assertTrue(atomicCoroutineScope.finishedProcessing)
165
250
}
166
251
}
0 commit comments