@@ -8,14 +8,12 @@ package kotlinx.rpc.grpc.internal
8
8
9
9
import cnames.structs.grpc_call
10
10
import kotlinx.atomicfu.atomic
11
+ import kotlinx.atomicfu.locks.SynchronizedObject
12
+ import kotlinx.atomicfu.locks.synchronized
11
13
import kotlinx.cinterop.*
12
- import kotlinx.coroutines.NonCancellable
13
- import kotlinx.coroutines.suspendCancellableCoroutine
14
- import kotlinx.coroutines.withContext
14
+ import kotlinx.coroutines.CompletableDeferred
15
15
import libgrpcpp_c.*
16
16
import platform.posix.memset
17
- import kotlin.coroutines.Continuation
18
- import kotlin.coroutines.resume
19
17
import kotlin.experimental.ExperimentalNativeApi
20
18
import kotlin.native.ref.createCleaner
21
19
@@ -33,20 +31,24 @@ internal sealed interface BatchResult {
33
31
*/
34
32
internal class CompletionQueue {
35
33
36
- private enum class State { OPEN , SHUTTING_DOWN , CLOSED }
34
+ internal enum class State { OPEN , SHUTTING_DOWN , CLOSED }
37
35
38
36
// if the queue was called with forceShutdown = true,
39
37
// it will reject all new batches and wait for all current ones to finish.
40
38
private var forceShutdown = false
41
- private val state = atomic(State .OPEN )
42
39
43
40
// internal as it must be accessible from the SHUTDOWN_CB,
44
41
// but it shouldn't be used from outside this file.
45
42
@Suppress(" PropertyName" )
46
- internal val _shutdownDone = kotlinx.coroutines.CompletableDeferred <Unit >()
43
+ internal val _state = atomic(State .OPEN )
44
+
45
+ // internal as it must be accessible from the SHUTDOWN_CB,
46
+ // but it shouldn't be used from outside this file.
47
+ @Suppress(" PropertyName" )
48
+ internal val _shutdownDone = CompletableDeferred <Unit >()
47
49
48
50
// used for spinning lock. false means not used (available)
49
- private val batchStartGuard = atomic( false )
51
+ private val batchStartGuard = SynchronizedObject ( )
50
52
51
53
private val thisStableRef = StableRef .create(this )
52
54
@@ -71,105 +73,75 @@ internal class CompletionQueue {
71
73
}
72
74
73
75
// TODO: Remove this method
74
- suspend fun runBatch (call : NativeClientCall <* , * >, ops : CPointer <grpc_op>, nOps : ULong ) =
76
+ fun runBatch (call : NativeClientCall <* , * >, ops : CPointer <grpc_op>, nOps : ULong ) =
75
77
runBatch(call.raw, ops, nOps)
76
78
77
- suspend fun runBatch (call : CPointer <grpc_call>, ops : CPointer <grpc_op>, nOps : ULong ): BatchResult =
78
- suspendCancellableCoroutine< BatchResult > { cont ->
79
- val tag = newCbTag(cont , OPS_COMPLETE_CB )
79
+ fun runBatch (call : CPointer <grpc_call>, ops : CPointer <grpc_op>, nOps : ULong ): CompletableDeferred < BatchResult > {
80
+ val completion = CompletableDeferred < BatchResult >()
81
+ val tag = newCbTag(completion , OPS_COMPLETE_CB )
80
82
81
- var err = grpc_call_error.GRPC_CALL_ERROR
83
+ var err = grpc_call_error.GRPC_CALL_ERROR
84
+ synchronized(batchStartGuard) {
82
85
// synchronizes access to grpc_call_start_batch
83
- withBatchStartLock {
84
- if (forceShutdown || state.value == State .CLOSED ) {
85
- // if the queue is either closed or in the process of a FORCE shutdown,
86
- // new batches will instantly fail.
87
- deleteCbTag(tag)
88
- cont.resume(BatchResult .CQShutdown )
89
- return @suspendCancellableCoroutine
90
- }
91
-
92
- err = grpc_call_start_batch(call, ops, nOps, tag, null )
93
- }
94
-
95
- if (err != grpc_call_error.GRPC_CALL_OK ) {
96
- // if the call was not successful, the callback will not be invoked.
86
+ if (forceShutdown || _state .value == State .CLOSED ) {
87
+ // if the queue is either closed or in the process of a FORCE shutdown,
88
+ // new batches will instantly fail.
97
89
deleteCbTag(tag)
98
- cont.resume (BatchResult .CallError (grpc_call_error. GRPC_CALL_ERROR ) )
99
- return @suspendCancellableCoroutine
90
+ completion.complete (BatchResult .CQShutdown )
91
+ return completion
100
92
}
101
93
94
+ err = grpc_call_start_batch(call, ops, nOps, tag, null )
95
+ }
102
96
103
- cont.invokeOnCancellation {
104
- @Suppress(" UnusedExpression" )
105
- // keep reference, otherwise the cleaners might get invoked before the batch finishes
106
- this
107
- // cancel the call if one of its batches is canceled.
108
- // grpc_call_cancel is thread-safe and can be called several times.
109
- // the callback is invoked anyway, so the tag doesn't get deleted here.
110
- if (it != null ) {
111
- grpc_call_cancel_with_status(
112
- call,
113
- grpc_status_code.GRPC_STATUS_CANCELLED ,
114
- " Call got cancelled: ${it.message} " ,
115
- null
116
- )
117
- } else {
118
- grpc_call_cancel(call, null )
119
- }
120
- }
97
+ if (err != grpc_call_error.GRPC_CALL_OK ) {
98
+ // if the call was not successful, the callback will not be invoked.
99
+ deleteCbTag(tag)
100
+ completion.complete(BatchResult .CallError (err))
101
+ return completion
121
102
}
122
103
104
+ return completion
105
+ }
106
+
123
107
// must not be canceled as it cleans resources and sets the state to CLOSED
124
- suspend fun shutdown (force : Boolean = false) = withContext( NonCancellable ) {
108
+ fun shutdown (force : Boolean = false): CompletableDeferred < Unit > {
125
109
if (force) {
126
110
forceShutdown = true
127
111
}
128
- if (! state .compareAndSet(State .OPEN , State .SHUTTING_DOWN )) {
112
+ if (! _state .compareAndSet(State .OPEN , State .SHUTTING_DOWN )) {
129
113
// the first call to shutdown() makes transition and to SHUTTING_DOWN and
130
114
// initiates shut down. all other invocations await the shutdown.
131
- _shutdownDone .await()
132
- return @withContext
115
+ return _shutdownDone
133
116
}
134
117
135
118
// wait until all batch operations since the state transitions were started.
136
- // this is required to prevent batches from starting after shutdown was initialized
137
- withBatchStartLock { }
138
-
139
- grpc_completion_queue_shutdown(raw)
140
- _shutdownDone .await()
141
- state.value = State .CLOSED
142
- }
143
-
144
- private inline fun withBatchStartLock (block : () -> Unit ) {
145
- try {
146
- // spin until this thread occupies the guard
147
- @Suppress(" ControlFlowWithEmptyBody" )
148
- while (! batchStartGuard.compareAndSet(expect = false , update = true )) {
149
- }
150
- block()
151
- } finally {
152
- // set guard to "not occupied"
153
- batchStartGuard.value = false
119
+ // this is required to prevent batches from starting after shutdown was initialized.
120
+ // however, this lock will be available very fast, so it shouldn't be a problem.'
121
+ synchronized(batchStartGuard) {
122
+ grpc_completion_queue_shutdown(raw)
154
123
}
124
+
125
+ return _shutdownDone
155
126
}
156
127
}
157
128
158
129
// kq stands for kompletion_queue lol
159
130
@CName(" kq_ops_complete_cb" )
160
131
private fun opsCompleteCb (functor : CPointer <grpc_completion_queue_functor>? , ok : Int ) {
161
132
val tag = functor!! .reinterpret< grpc_cb_tag> ()
162
- val cont = tag.pointed.user_data!! .asStableRef<Continuation <BatchResult >>().get()
133
+ val cont = tag.pointed.user_data!! .asStableRef<CompletableDeferred <BatchResult >>().get()
163
134
deleteCbTag(tag)
164
- if (ok != 0 ) cont.resume (BatchResult .Success )
165
- else cont.resume (BatchResult .ResultError )
135
+ if (ok != 0 ) cont.complete (BatchResult .Success )
136
+ else cont.complete (BatchResult .ResultError )
166
137
}
167
138
168
139
@CName(" kq_shutdown_cb" )
169
140
private fun shutdownCb (functor : CPointer <grpc_completion_queue_functor>? , ok : Int ) {
170
141
val tag = functor!! .reinterpret< grpc_cb_tag> ()
171
142
val cq = tag.pointed.user_data!! .asStableRef<CompletionQueue >().get()
172
143
cq._shutdownDone .complete(Unit )
144
+ cq._state .value = CompletionQueue .State .CLOSED
173
145
grpc_completion_queue_destroy(cq.raw)
174
146
}
175
147
0 commit comments