@@ -10,8 +10,9 @@ use std::{
10
10
task:: { Context , Poll } ,
11
11
} ;
12
12
13
- use bson:: Document ;
14
- use futures_core:: Stream ;
13
+ use bson:: { Document , Timestamp } ;
14
+ use derivative:: Derivative ;
15
+ use futures_core:: { future:: BoxFuture , Stream } ;
15
16
use serde:: { de:: DeserializeOwned , Deserialize } ;
16
17
17
18
use crate :: {
@@ -20,11 +21,12 @@ use crate::{
20
21
options:: ChangeStreamOptions ,
21
22
} ,
22
23
cursor:: { stream_poll_next, BatchValue , CursorStream , NextInBatchFuture } ,
23
- error:: Result ,
24
+ error:: { Error , Result } ,
24
25
operation:: AggregateTarget ,
25
26
options:: AggregateOptions ,
26
27
selection_criteria:: { ReadPreference , SelectionCriteria } ,
27
28
Client ,
29
+ ClientSession ,
28
30
Collection ,
29
31
Cursor ,
30
32
Database ,
@@ -75,34 +77,37 @@ use crate::{
75
77
///
76
78
/// See the documentation [here](https://docs.mongodb.com/manual/changeStreams) for more
77
79
/// details. Also see the documentation on [usage recommendations](https://docs.mongodb.com/manual/administration/change-streams-production-recommendations/).
78
- #[ derive( Debug ) ]
80
+ #[ derive( Derivative ) ]
81
+ #[ derivative( Debug ) ]
79
82
pub struct ChangeStream < T >
80
83
where
81
84
T : DeserializeOwned + Unpin + Send + Sync ,
82
85
{
83
86
/// The cursor to iterate over event instances.
84
87
cursor : Cursor < T > ,
85
88
86
- /// The information associate with this change stream.
89
+ /// Arguments to `watch` that created this change stream.
90
+ args : WatchArgs ,
91
+
92
+ /// Dynamic information associated with this change stream.
87
93
data : ChangeStreamData ,
88
94
89
- /// The cached resume token.
90
- resume_token : Option < ResumeToken > ,
95
+ /// A pending future for a resume.
96
+ #[ derivative( Debug = "ignore" ) ]
97
+ pending_resume : Option < BoxFuture < ' static , Result < ChangeStream < T > > > > ,
91
98
}
92
99
93
100
impl < T > ChangeStream < T >
94
101
where
95
102
T : DeserializeOwned + Unpin + Send + Sync ,
96
103
{
97
- pub ( crate ) fn new (
98
- cursor : Cursor < T > ,
99
- data : ChangeStreamData ,
100
- resume_token : Option < ResumeToken > ,
101
- ) -> Self {
104
+ pub ( crate ) fn new ( cursor : Cursor < T > , args : WatchArgs , data : ChangeStreamData ) -> Self {
105
+ let pending_resume: Option < BoxFuture < ' static , Result < ChangeStream < T > > > > = None ;
102
106
Self {
103
107
cursor,
108
+ args,
104
109
data,
105
- resume_token ,
110
+ pending_resume ,
106
111
}
107
112
}
108
113
@@ -112,16 +117,17 @@ where
112
117
/// See the documentation
113
118
/// [here](https://docs.mongodb.com/manual/changeStreams/#change-stream-resume-token) for more
114
119
/// information on change stream resume tokens.
115
- pub fn resume_token ( & self ) -> Option < & ResumeToken > {
116
- self . resume_token . as_ref ( )
120
+ pub fn resume_token ( & self ) -> Option < ResumeToken > {
121
+ self . data . resume_token . clone ( )
117
122
}
118
123
119
124
/// Update the type streamed values will be parsed as.
120
125
pub fn with_type < D : DeserializeOwned + Unpin + Send + Sync > ( self ) -> ChangeStream < D > {
121
126
ChangeStream {
122
127
cursor : self . cursor . with_type ( ) ,
128
+ args : self . args ,
123
129
data : self . data ,
124
- resume_token : self . resume_token ,
130
+ pending_resume : None ,
125
131
}
126
132
}
127
133
@@ -162,45 +168,48 @@ where
162
168
}
163
169
}
164
170
165
- #[ derive( Debug ) ]
166
- pub ( crate ) struct ChangeStreamData {
171
+ /// Arguments passed to a `watch` method, captured to allow resume.
172
+ #[ derive( Debug , Clone ) ]
173
+ pub ( crate ) struct WatchArgs {
167
174
/// The pipeline of stages to append to an initial `$changeStream` stage.
168
- pipeline : Vec < Document > ,
169
-
170
- /// The client that was used for the initial `$changeStream` aggregation, used for server
171
- /// selection during an automatic resume.
172
- client : Client ,
175
+ pub ( crate ) pipeline : Vec < Document > ,
173
176
174
- /// The original target of the change stream, used for re-issuing the aggregation during
175
- /// an automatic resume.
176
- target : AggregateTarget ,
177
+ /// The original target of the change stream.
178
+ pub ( crate ) target : AggregateTarget ,
177
179
178
180
/// The options provided to the initial `$changeStream` stage.
179
- options : Option < ChangeStreamOptions > ,
181
+ pub ( crate ) options : Option < ChangeStreamOptions > ,
182
+ }
183
+
184
+ /// Dynamic change stream data needed for resume.
185
+ #[ derive( Debug , Default ) ]
186
+ pub ( crate ) struct ChangeStreamData {
187
+ /// The `operationTime` returned by the initial `aggregate` command.
188
+ pub ( crate ) initial_operation_time : Option < Timestamp > ,
189
+
190
+ /// The cached resume token.
191
+ pub ( crate ) resume_token : Option < ResumeToken > ,
180
192
181
193
/// Whether or not the change stream has attempted a resume, used to attempt a resume only
182
194
/// once.
183
- resume_attempted : bool ,
195
+ pub ( crate ) resume_attempted : bool ,
184
196
185
197
/// Whether or not the change stream has returned a document, used to update resume token
186
198
/// during an automatic resume.
187
- document_returned : bool ,
199
+ pub ( crate ) document_returned : bool ,
200
+
201
+ /// The implicit session used to create the original cursor.
202
+ pub ( crate ) implicit_session : Option < ClientSession > ,
188
203
}
189
204
190
205
impl ChangeStreamData {
191
- pub ( crate ) fn new (
192
- pipeline : Vec < Document > ,
193
- client : Client ,
194
- target : AggregateTarget ,
195
- options : Option < ChangeStreamOptions > ,
196
- ) -> Self {
206
+ fn take ( & mut self ) -> Self {
197
207
Self {
198
- pipeline,
199
- client,
200
- target,
201
- options,
202
- resume_attempted : false ,
203
- document_returned : false ,
208
+ initial_operation_time : self . initial_operation_time ,
209
+ resume_token : self . resume_token . clone ( ) ,
210
+ resume_attempted : self . resume_attempted ,
211
+ document_returned : self . document_returned ,
212
+ implicit_session : self . implicit_session . take ( ) ,
204
213
}
205
214
}
206
215
}
@@ -227,11 +236,48 @@ where
227
236
T : DeserializeOwned + Unpin + Send + Sync ,
228
237
{
229
238
fn poll_next_in_batch ( & mut self , cx : & mut Context < ' _ > ) -> Poll < Result < BatchValue > > {
239
+ if let Some ( mut pending) = self . pending_resume . take ( ) {
240
+ match Pin :: new ( & mut pending) . poll ( cx) {
241
+ Poll :: Pending => {
242
+ self . pending_resume = Some ( pending) ;
243
+ return Poll :: Pending ;
244
+ }
245
+ Poll :: Ready ( Ok ( new_stream) ) => {
246
+ // Ensure that the old cursor is killed on the server selected for the new one.
247
+ self . cursor
248
+ . set_drop_address ( new_stream. cursor . address ( ) . clone ( ) ) ;
249
+ self . cursor = new_stream. cursor ;
250
+ self . args = new_stream. args ;
251
+ return Poll :: Pending ;
252
+ }
253
+ Poll :: Ready ( Err ( e) ) => return Poll :: Ready ( Err ( e) ) ,
254
+ }
255
+ }
230
256
let out = self . cursor . poll_next_in_batch ( cx) ;
231
- if let Poll :: Ready ( Ok ( bv) ) = & out {
232
- if let Some ( token) = get_resume_token ( bv, self . cursor . post_batch_resume_token ( ) ) ? {
233
- self . resume_token = Some ( token) ;
257
+ match & out {
258
+ Poll :: Ready ( Ok ( bv) ) => {
259
+ if let Some ( token) = get_resume_token ( bv, self . cursor . post_batch_resume_token ( ) ) ? {
260
+ self . data . resume_token = Some ( token) ;
261
+ }
262
+ if matches ! ( bv, BatchValue :: Some { .. } ) {
263
+ self . data . document_returned = true ;
264
+ }
265
+ }
266
+ Poll :: Ready ( Err ( e) ) if e. is_resumable ( ) && !self . data . resume_attempted => {
267
+ self . data . resume_attempted = true ;
268
+ let client = self . cursor . client ( ) . clone ( ) ;
269
+ let args = self . args . clone ( ) ;
270
+ let mut data = self . data . take ( ) ;
271
+ data. implicit_session = self . cursor . take_implicit_session ( ) ;
272
+ self . pending_resume = Some ( Box :: pin ( async move {
273
+ let new_stream: Result < ChangeStream < ChangeStreamEvent < ( ) > > > = client
274
+ . execute_watch ( args. pipeline , args. options , args. target , Some ( data) )
275
+ . await ;
276
+ new_stream. map ( |cs| cs. with_type :: < T > ( ) )
277
+ } ) ) ;
278
+ return Poll :: Pending ;
234
279
}
280
+ _ => { }
235
281
}
236
282
out
237
283
}
0 commit comments