@@ -36,6 +36,9 @@ internal class MultiDatabaseManager : DatabaseManagerBase
3636 // The swap-db operation should take a write lock and any operation that should be swap-db-safe should take a read lock.
3737 SingleWriterMultiReaderLock databasesContentLock ;
3838
39+ // Lock for synchronizing checkpointing of all active DBs (if more than one)
40+ SingleWriterMultiReaderLock multiDbCheckpointingLock ;
41+
3942 // Reusable task array for tracking checkpointing of multiple DBs
4043 // Used by recurring checkpointing task if multiple DBs exist
4144 Task [ ] checkpointTasks ;
@@ -152,20 +155,40 @@ public override bool TakeCheckpoint(bool background, ILogger logger = null, Canc
152155 var lockAcquired = TryGetDatabasesContentReadLock ( token ) ;
153156 if ( ! lockAcquired ) return false ;
154157
155- try
158+ var checkpointTask = Task . Run ( async ( ) =>
156159 {
157- var activeDbIdsMapSize = activeDbIds . ActualSize ;
158- var activeDbIdsMapSnapshot = activeDbIds . Map ;
159- Array . Copy ( activeDbIdsMapSnapshot , dbIdsToCheckpoint , activeDbIdsMapSize ) ;
160+ var checkpointLockTaken = false ;
160161
161- TakeDatabasesCheckpointAsync ( activeDbIdsMapSize , logger : logger , token : token ) . GetAwaiter ( ) . GetResult ( ) ;
162- }
163- finally
164- {
165- databasesContentLock . ReadUnlock ( ) ;
166- }
162+ try
163+ {
164+ var activeDbIdsMapSize = activeDbIds . ActualSize ;
167165
168- return true ;
166+ if ( activeDbIdsMapSize > 1 )
167+ {
168+ if ( ! multiDbCheckpointingLock . TryWriteLock ( ) )
169+ return false ;
170+
171+ checkpointLockTaken = true ;
172+ }
173+
174+ var activeDbIdsMapSnapshot = activeDbIds . Map ;
175+ Array . Copy ( activeDbIdsMapSnapshot , dbIdsToCheckpoint , activeDbIdsMapSize ) ;
176+
177+ return await TakeDatabasesCheckpointAsync ( activeDbIdsMapSize , logger : logger , token : token ) ;
178+ }
179+ finally
180+ {
181+ if ( checkpointLockTaken )
182+ multiDbCheckpointingLock . WriteUnlock ( ) ;
183+
184+ databasesContentLock . ReadUnlock ( ) ;
185+ }
186+ } , token ) . GetAwaiter ( ) ;
187+
188+ if ( background )
189+ return true ;
190+
191+ return checkpointTask . GetResult ( ) ;
169192 }
170193
171194 /// <inheritdoc/>
@@ -175,7 +198,8 @@ public override bool TakeCheckpoint(bool background, int dbId, ILogger logger =
175198 var databasesMapSnapshot = databases . Map ;
176199 Debug . Assert ( dbId < databasesMapSize && databasesMapSnapshot [ dbId ] != null ) ;
177200
178- if ( ! TryPauseCheckpointsContinuousAsync ( dbId , token ) . GetAwaiter ( ) . GetResult ( ) )
201+ // Check if checkpoint already in progress
202+ if ( ! TryPauseCheckpoints ( dbId ) )
179203 return false ;
180204
181205 var checkpointTask = TakeCheckpointAsync ( databasesMapSnapshot [ dbId ] , logger : logger , token : token ) . ContinueWith (
@@ -212,12 +236,12 @@ public override async Task TakeOnDemandCheckpointAsync(DateTimeOffset entryTime,
212236
213237 var db = databasesMapSnapshot [ dbId ] ;
214238
215- // Take lock to ensure no other task will be taking a checkpoint
239+ // Check if checkpoint already in progress
216240 var checkpointsPaused = TryPauseCheckpoints ( dbId ) ;
217241
218242 try
219243 {
220- // If an external task has taken a checkpoint beyond the provided entryTime return
244+ // If another checkpoint is in progress or a checkpoint was taken beyond the provided entryTime - return
221245 if ( ! checkpointsPaused || db . LastSaveTime > entryTime )
222246 return ;
223247
@@ -241,10 +265,21 @@ public override async Task TaskCheckpointBasedOnAofSizeLimitAsync(long aofSizeLi
241265 var lockAcquired = TryGetDatabasesContentReadLock ( token ) ;
242266 if ( ! lockAcquired ) return ;
243267
268+ var activeDbIdsMapSize = activeDbIds . ActualSize ;
269+
270+ var checkpointLockTaken = false ;
271+
244272 try
245273 {
274+ if ( activeDbIdsMapSize > 1 )
275+ {
276+ if ( ! multiDbCheckpointingLock . TryWriteLock ( ) )
277+ return ;
278+
279+ checkpointLockTaken = true ;
280+ }
281+
246282 var databasesMapSnapshot = databases . Map ;
247- var activeDbIdsMapSize = activeDbIds . ActualSize ;
248283 var activeDbIdsMapSnapshot = activeDbIds . Map ;
249284
250285 var dbIdsIdx = 0 ;
@@ -270,6 +305,9 @@ public override async Task TaskCheckpointBasedOnAofSizeLimitAsync(long aofSizeLi
270305 }
271306 finally
272307 {
308+ if ( checkpointLockTaken )
309+ multiDbCheckpointingLock . WriteUnlock ( ) ;
310+
273311 databasesContentLock . ReadUnlock ( ) ;
274312 }
275313 }
@@ -948,7 +986,7 @@ private void CopyDatabases(IDatabaseManager src, bool enableAof)
948986 /// <param name="logger">Logger</param>
949987 /// <param name="token">Cancellation token</param>
950988 /// <returns>False if checkpointing already in progress</returns>
951- private async Task TakeDatabasesCheckpointAsync ( int dbIdsCount , ILogger logger = null ,
989+ private async Task < bool > TakeDatabasesCheckpointAsync ( int dbIdsCount , ILogger logger = null ,
952990 CancellationToken token = default )
953991 {
954992 Debug . Assert ( checkpointTasks != null ) ;
@@ -958,40 +996,32 @@ private async Task TakeDatabasesCheckpointAsync(int dbIdsCount, ILogger logger =
958996 checkpointTasks [ i ] = Task . CompletedTask ;
959997
960998 var lockAcquired = TryGetDatabasesContentReadLock ( token ) ;
961- if ( ! lockAcquired ) return ;
999+ if ( ! lockAcquired ) return false ;
9621000
9631001 try
9641002 {
9651003 var databaseMapSnapshot = databases . Map ;
9661004
967- var currIdx = 0 ;
968- while ( currIdx < dbIdsCount )
1005+ for ( var currIdx = 0 ; currIdx < dbIdsCount ; currIdx ++ )
9691006 {
9701007 var dbId = dbIdsToCheckpoint [ currIdx ] ;
9711008
972- // Prevent parallel checkpoint
973- if ( ! await TryPauseCheckpointsContinuousAsync ( dbId , token ) )
1009+ // If a checkpoint is already in progress for this database, skip it
1010+ if ( ! TryPauseCheckpoints ( dbId ) )
9741011 continue ;
9751012
9761013 checkpointTasks [ currIdx ] = TakeCheckpointAsync ( databaseMapSnapshot [ dbId ] , logger : logger , token : token ) . ContinueWith (
9771014 t =>
9781015 {
979- try
980- {
981- if ( t . IsCompletedSuccessfully )
982- {
983- var storeTailAddress = t . Result . Item1 ;
984- var objectStoreTailAddress = t . Result . Item2 ;
985- UpdateLastSaveData ( dbId , storeTailAddress , objectStoreTailAddress ) ;
986- }
987- }
988- finally
989- {
990- ResumeCheckpoints ( dbId ) ;
991- }
992- } , TaskContinuationOptions . ExecuteSynchronously ) ;
1016+ ResumeCheckpoints ( dbId ) ;
9931017
994- currIdx ++ ;
1018+ if ( ! t . IsCompletedSuccessfully )
1019+ return ;
1020+
1021+ var storeTailAddress = t . Result . Item1 ;
1022+ var objectStoreTailAddress = t . Result . Item2 ;
1023+ UpdateLastSaveData ( dbId , storeTailAddress , objectStoreTailAddress ) ;
1024+ } , TaskContinuationOptions . ExecuteSynchronously ) ;
9951025 }
9961026
9971027 await Task . WhenAll ( checkpointTasks ) ;
@@ -1004,6 +1034,8 @@ private async Task TakeDatabasesCheckpointAsync(int dbIdsCount, ILogger logger =
10041034 {
10051035 databasesContentLock . ReadUnlock ( ) ;
10061036 }
1037+
1038+ return true ;
10071039 }
10081040
10091041 private void UpdateLastSaveData ( int dbId , long ? storeTailAddress , long ? objectStoreTailAddress )
@@ -1022,24 +1054,6 @@ private void UpdateLastSaveData(int dbId, long? storeTailAddress, long? objectSt
10221054 }
10231055 }
10241056
1025- private async Task < bool > TryPauseCheckpointsContinuousAsync ( int dbId , CancellationToken token = default )
1026- {
1027- var databasesMapSize = databases . ActualSize ;
1028- var databasesMapSnapshot = databases . Map ;
1029- Debug . Assert ( dbId < databasesMapSize && databasesMapSnapshot [ dbId ] != null ) ;
1030-
1031- var db = databasesMapSnapshot [ dbId ] ;
1032- var checkpointsPaused = TryPauseCheckpoints ( db ) ;
1033-
1034- while ( ! checkpointsPaused && ! token . IsCancellationRequested && ! Disposed )
1035- {
1036- await Task . Yield ( ) ;
1037- checkpointsPaused = TryPauseCheckpoints ( db ) ;
1038- }
1039-
1040- return checkpointsPaused ;
1041- }
1042-
10431057 public override void Dispose ( )
10441058 {
10451059 if ( Disposed ) return ;
0 commit comments