21
21
import com .mongodb .ServerCursor ;
22
22
import com .mongodb .client .MongoCursor ;
23
23
import com .mongodb .lang .Nullable ;
24
+ import com .mongodb .reactivestreams .client .internal .BatchCursor ;
24
25
import org .reactivestreams .Publisher ;
25
26
import org .reactivestreams .Subscriber ;
26
27
import org .reactivestreams .Subscription ;
28
+ import reactor .core .CoreSubscriber ;
27
29
import reactor .core .publisher .Flux ;
30
+ import reactor .core .publisher .Hooks ;
31
+ import reactor .core .publisher .Operators ;
32
+ import reactor .util .context .Context ;
28
33
29
34
import java .util .NoSuchElementException ;
30
35
import java .util .concurrent .BlockingDeque ;
36
+ import java .util .concurrent .CompletableFuture ;
31
37
import java .util .concurrent .CountDownLatch ;
38
+ import java .util .concurrent .ExecutionException ;
32
39
import java .util .concurrent .LinkedBlockingDeque ;
33
40
import java .util .concurrent .TimeUnit ;
41
+ import java .util .concurrent .TimeoutException ;
34
42
35
43
import static com .mongodb .ClusterFixture .TIMEOUT ;
36
44
import static com .mongodb .internal .thread .InterruptionUtil .interruptAndCreateMongoInterruptedException ;
37
45
import static com .mongodb .reactivestreams .client .syncadapter .ContextHelper .CONTEXT ;
38
46
import static com .mongodb .reactivestreams .client .syncadapter .SyncMongoClient .getSleepAfterCursorClose ;
39
47
import static com .mongodb .reactivestreams .client .syncadapter .SyncMongoClient .getSleepAfterCursorOpen ;
48
+ import static com .mongodb .reactivestreams .client .syncadapter .SyncMongoClient .isWaitForBatchCursorCreationEnabled ;
40
49
41
50
class SyncMongoCursor <T > implements MongoCursor <T > {
42
51
private static final Object COMPLETED = new Object ();
43
52
private final BlockingDeque <Object > results = new LinkedBlockingDeque <>();
53
+ private final CompletableFuture <Object > batchCursorCompletableFuture = new CompletableFuture <>();
44
54
private final Integer batchSize ;
45
55
private int countToBatchSize ;
46
56
private Subscription subscription ;
@@ -51,6 +61,15 @@ class SyncMongoCursor<T> implements MongoCursor<T> {
51
61
SyncMongoCursor (final Publisher <T > publisher , @ Nullable final Integer batchSize ) {
52
62
this .batchSize = batchSize ;
53
63
CountDownLatch latch = new CountDownLatch (1 );
64
+
65
+ if (isWaitForBatchCursorCreationEnabled ()) {
66
+ // This hook allows us to intercept the `onNext` and `onError` signals for any operation to determine
67
+ // whether the {@link BatchCursor} was created successfully or if an error occurred during its creation process.
68
+ // The result is propagated to a {@link CompletableFuture}, which we use to block until it is completed.
69
+ Hooks .onEachOperator (Operators .lift ((sc , sub ) ->
70
+ new BatchCursorInterceptSubscriber (sub , batchCursorCompletableFuture )));
71
+ }
72
+
54
73
//noinspection ReactiveStreamsSubscriberImplementation
55
74
Flux .from (publisher ).contextWrite (CONTEXT ).subscribe (new Subscriber <T >() {
56
75
@ Override
@@ -83,9 +102,19 @@ public void onComplete() {
83
102
if (!latch .await (TIMEOUT , TimeUnit .SECONDS )) {
84
103
throw new MongoTimeoutException ("Timeout waiting for subscription" );
85
104
}
105
+ if (isWaitForBatchCursorCreationEnabled ()) {
106
+ batchCursorCompletableFuture .get (TIMEOUT , TimeUnit .SECONDS );
107
+ Hooks .resetOnEachOperator ();
108
+ }
86
109
sleep (getSleepAfterCursorOpen ());
87
110
} catch (InterruptedException e ) {
88
111
throw interruptAndCreateMongoInterruptedException ("Interrupted waiting for asynchronous cursor establishment" , e );
112
+ } catch (ExecutionException | TimeoutException e ) {
113
+ Throwable cause = e .getCause ();
114
+ if (cause instanceof RuntimeException ) {
115
+ throw (RuntimeException ) cause ;
116
+ }
117
+ throw new RuntimeException (e );
89
118
}
90
119
}
91
120
@@ -181,4 +210,49 @@ private RuntimeException translateError(final Throwable throwable) {
181
210
}
182
211
return new RuntimeException (throwable );
183
212
}
213
+
214
+
215
+ private static final class BatchCursorInterceptSubscriber implements CoreSubscriber <Object > {
216
+
217
+ private final CoreSubscriber <Object > sub ;
218
+ private final CompletableFuture <Object > batchCursorCompletableFuture ;
219
+
220
+ BatchCursorInterceptSubscriber (final CoreSubscriber <Object > sub ,
221
+ final CompletableFuture <Object > batchCursorCompletableFuture ) {
222
+ this .sub = sub ;
223
+ this .batchCursorCompletableFuture = batchCursorCompletableFuture ;
224
+ }
225
+
226
+ @ Override
227
+ public Context currentContext () {
228
+ return sub .currentContext ();
229
+ }
230
+
231
+ @ Override
232
+ public void onSubscribe (final Subscription s ) {
233
+ sub .onSubscribe (s );
234
+ }
235
+
236
+ @ Override
237
+ public void onNext (final Object o ) {
238
+ if (o instanceof BatchCursor ) {
239
+ // Interception of a cursor means that it has been created at this point.
240
+ batchCursorCompletableFuture .complete (o );
241
+ }
242
+ sub .onNext (o );
243
+ }
244
+
245
+ @ Override
246
+ public void onError (final Throwable t ) {
247
+ if (!batchCursorCompletableFuture .isDone ()) { // Cursor has not been created yet but an error occurred.
248
+ batchCursorCompletableFuture .completeExceptionally (t );
249
+ }
250
+ sub .onError (t );
251
+ }
252
+
253
+ @ Override
254
+ public void onComplete () {
255
+ sub .onComplete ();
256
+ }
257
+ }
184
258
}
0 commit comments