@@ -129,7 +129,7 @@ public override void Run()
129129 /// </summary>
130130 public const string INFO_STREAM_COMPONENT = "ReplicationThread" ;
131131
132- private readonly IReplicator ? replicator ;
132+ private readonly IReplicator replicator ;
133133 private readonly IAsyncReplicator ? asyncReplicator ;
134134 private readonly IReplicationHandler handler ;
135135 private readonly ISourceDirectoryFactory factory ;
@@ -147,26 +147,16 @@ public override void Run()
147147 /// <summary>
148148 /// Constructor.
149149 /// </summary>
150- /// <param name="replicator">The <see cref="IReplicator"/> used for checking for updates</param>
150+ /// <param name="replicator">The <see cref="IReplicator"/> used for checking for updates.
151+ /// If the replicator also implements <see cref="IAsyncReplicator"/>, the async
152+ /// methods will use the native async implementation; otherwise, they will fall back
153+ /// to wrapping the synchronous methods.</param>
151154 /// <param name="handler">The <see cref="IReplicationHandler"/> notified when new revisions are ready</param>
152155 /// <param name="factory">The <see cref="ISourceDirectoryFactory"/> for returning a <see cref="Directory"/> for a given source and session</param>
153156 public ReplicationClient ( IReplicator replicator , IReplicationHandler handler , ISourceDirectoryFactory factory )
154157 {
155158 this . replicator = replicator ?? throw new ArgumentNullException ( nameof ( replicator ) ) ;
156- this . handler = handler ?? throw new ArgumentNullException ( nameof ( handler ) ) ;
157- this . factory = factory ?? throw new ArgumentNullException ( nameof ( factory ) ) ;
158- }
159-
160- /// <summary>
161- /// Constructor for async replicators.
162- /// </summary>
163- /// <param name="asyncReplicator"></param>
164- /// <param name="handler"></param>
165- /// <param name="factory"></param>
166- /// <exception cref="ArgumentNullException"></exception>
167- public ReplicationClient ( IAsyncReplicator asyncReplicator , IReplicationHandler handler , ISourceDirectoryFactory factory )
168- {
169- this . asyncReplicator = asyncReplicator ?? throw new ArgumentNullException ( nameof ( asyncReplicator ) ) ;
159+ this . asyncReplicator = replicator as IAsyncReplicator ;
170160 this . handler = handler ?? throw new ArgumentNullException ( nameof ( handler ) ) ;
171161 this . factory = factory ?? throw new ArgumentNullException ( nameof ( factory ) ) ;
172162 }
@@ -184,8 +174,6 @@ private void CopyBytes(IndexOutput output, Stream input)
184174 /// <exception cref="IOException"></exception>
185175 private void DoUpdate ( )
186176 {
187- if ( replicator is null ) throw new InvalidOperationException ( "Replicator not initialized." ) ;
188-
189177 SessionToken ? session = null ;
190178 Dictionary < string , Directory > sourceDirectory = new Dictionary < string , Directory > ( ) ;
191179 Dictionary < string , IList < string > > copiedFiles = new Dictionary < string , IList < string > > ( ) ;
@@ -285,13 +273,13 @@ private void DoUpdate()
285273 }
286274
287275 /// <summary>
288- /// Performs the async update logic, mirrors DoUpdate but uses IAsyncReplicator.
276+ /// Performs the async update logic. When the replicator implements
277+ /// <see cref="IAsyncReplicator"/>, native async methods are used;
278+ /// otherwise, the synchronous <see cref="IReplicator"/> methods are
279+ /// called and their results wrapped.
289280 /// </summary>
290281 private async Task DoUpdateAsync ( CancellationToken cancellationToken )
291282 {
292- if ( asyncReplicator is null )
293- throw new InvalidOperationException ( "AsyncReplicator not initialized." ) ;
294-
295283 SessionToken ? session = null ;
296284 var sourceDirectory = new Dictionary < string , Directory > ( ) ;
297285 var copiedFiles = new Dictionary < string , IList < string > > ( ) ;
@@ -300,15 +288,17 @@ private async Task DoUpdateAsync(CancellationToken cancellationToken)
300288 try
301289 {
302290 string ? version = handler . CurrentVersion ;
303- session = await asyncReplicator . CheckForUpdateAsync ( version , cancellationToken ) . ConfigureAwait ( false ) ;
291+
292+ session = asyncReplicator is not null
293+ ? await asyncReplicator . CheckForUpdateAsync ( version , cancellationToken ) . ConfigureAwait ( false )
294+ : replicator . CheckForUpdate ( version ) ;
304295
305296 WriteToInfoStream ( $ "DoUpdateAsync(): handlerVersion={ version } session={ session } ") ;
306297
307298 if ( session is null )
308299 return ;
309300
310301 IDictionary < string , IList < RevisionFile > > requiredFiles = RequiredFiles ( session . SourceFiles ) ;
311- WriteToInfoStream ( $ "DoUpdateAsync(): handlerVersion={ version } session={ session } ") ;
312302
313303 foreach ( var pair in requiredFiles )
314304 {
@@ -327,7 +317,10 @@ private async Task DoUpdateAsync(CancellationToken cancellationToken)
327317 IndexOutput ? output = null ;
328318 try
329319 {
330- input = await asyncReplicator . ObtainFileAsync ( session . Id ! , source , file . FileName , cancellationToken ) . ConfigureAwait ( false ) ;
320+ input = asyncReplicator is not null
321+ ? await asyncReplicator . ObtainFileAsync ( session . Id ! , source , file . FileName , cancellationToken ) . ConfigureAwait ( false )
322+ : replicator . ObtainFile ( session . Id ! , source , file . FileName ) ;
323+
331324 output = directory . CreateOutput ( file . FileName , IOContext . DEFAULT ) ;
332325
333326 int numBytes ;
@@ -354,7 +347,10 @@ private async Task DoUpdateAsync(CancellationToken cancellationToken)
354347 {
355348 try
356349 {
357- await asyncReplicator . ReleaseAsync ( session . Id , cancellationToken ) . ConfigureAwait ( false ) ;
350+ if ( asyncReplicator is not null )
351+ await asyncReplicator . ReleaseAsync ( session . Id , cancellationToken ) . ConfigureAwait ( false ) ;
352+ else
353+ replicator . Release ( session . Id ) ;
358354 }
359355 finally
360356 {
@@ -367,18 +363,28 @@ private async Task DoUpdateAsync(CancellationToken cancellationToken)
367363 }
368364 }
369365
370- if ( notify && ! disposed )
366+ // notify outside the try-finally above, so the session is released sooner.
367+ // the handler may take time to finish acting on the copied files, but the
368+ // session itself is no longer needed.
369+ try
371370 {
372- handler . RevisionReady ( session . Version , session . SourceFiles , Collections . AsReadOnly ( copiedFiles ) , sourceDirectory ) ;
371+ if ( notify && ! disposed )
372+ {
373+ // no use to notify if we are closed already
374+ // LUCENENET specific - pass the copiedFiles as read only
375+ handler . RevisionReady ( session . Version , session . SourceFiles , Collections . AsReadOnly ( copiedFiles ) , sourceDirectory ) ;
376+ }
373377 }
378+ finally
379+ {
380+ IOUtils . Dispose ( sourceDirectory . Values ) ;
374381
375- IOUtils . Dispose ( sourceDirectory . Values ) ;
376-
377- // LUCENENET specific: removed redundant check. Code above either ensures it is not null (by returning early) or an exception is thrown.
378- // if (session != null)
379- // {
380- factory . CleanupSession ( session . Id ) ;
381- // }
382+ // LUCENENET specific: removed redundant check. Code above either ensures it is not null (by returning early) or an exception is thrown.
383+ // if (session != null)
384+ // {
385+ factory . CleanupSession ( session . Id ) ;
386+ // }
387+ }
382388 }
383389
384390 /// <summary>Throws <see cref="ObjectDisposedException"/> if the client has already been disposed.</summary>
0 commit comments