@@ -10,6 +10,109 @@ namespace LiteDB.Engine
1010{
1111 public partial class LiteEngine
1212 {
13+ private void HealCorruptedFreeEmptyPageList ( )
14+ {
15+ if ( _header . FreeEmptyPageList == uint . MaxValue )
16+ {
17+ return ;
18+ }
19+
20+ using var reader = _disk . GetReader ( ) ;
21+
22+ var current = _header . FreeEmptyPageList ;
23+ var visited = new HashSet < uint > ( ) ;
24+
25+ while ( current != uint . MaxValue )
26+ {
27+ if ( current > _header . LastPageID || visited . Add ( current ) == false )
28+ {
29+ this . RepairFreeEmptyPageList ( current , null ) ;
30+ return ;
31+ }
32+
33+ var page = this . ReadLatestPage ( current , reader ) ;
34+
35+ try
36+ {
37+ if ( page . PageType != PageType . Empty )
38+ {
39+ this . RepairFreeEmptyPageList ( current , page . PageType ) ;
40+ return ;
41+ }
42+
43+ current = page . NextPageID ;
44+ }
45+ finally
46+ {
47+ page . Buffer . Release ( ) ;
48+ }
49+ }
50+ }
51+
52+ private BasePage ReadLatestPage ( uint pageID , DiskReader reader )
53+ {
54+ var position = _walIndex . GetPageIndex ( pageID , int . MaxValue , out _ ) ;
55+
56+ if ( position != long . MaxValue )
57+ {
58+ return new BasePage ( reader . ReadPage ( position , false , FileOrigin . Log ) ) ;
59+ }
60+
61+ return new BasePage ( reader . ReadPage ( BasePage . GetPagePosition ( pageID ) , false , FileOrigin . Data ) ) ;
62+ }
63+
64+ private void RepairFreeEmptyPageList ( uint pageID , PageType ? pageType )
65+ {
66+ LOG (
67+ pageType . HasValue
68+ ? $ "detected legacy corruption in free empty page list at page { pageID } ({ pageType . Value } ); resetting header free list"
69+ : $ "detected legacy corruption in free empty page list near page { pageID } ; resetting header free list",
70+ "RECOVERY" ) ;
71+
72+ lock ( _header )
73+ {
74+ var savepoint = _header . Savepoint ( ) ;
75+
76+ try
77+ {
78+ _header . FreeEmptyPageList = uint . MaxValue ;
79+ _header . TransactionID = uint . MaxValue ;
80+ _header . IsConfirmed = false ;
81+ _header . UpdateBuffer ( ) ;
82+
83+ if ( _settings . ReadOnly == false )
84+ {
85+ this . PersistRecoveredHeader ( ) ;
86+ }
87+ }
88+ catch
89+ {
90+ _header . Restore ( savepoint ) ;
91+ throw ;
92+ }
93+ }
94+ }
95+
96+ private void PersistRecoveredHeader ( )
97+ {
98+ var transactionID = _walIndex . NextTransactionID ( ) ;
99+
100+ _header . TransactionID = transactionID ;
101+ _header . IsConfirmed = true ;
102+
103+ var buffer = _header . UpdateBuffer ( ) ;
104+ var clone = _disk . NewPage ( ) ;
105+
106+ Buffer . BlockCopy ( buffer . Array , buffer . Offset , clone . Array , clone . Offset , clone . Count ) ;
107+
108+ _disk . WriteLogDisk ( new [ ] { clone } ) ;
109+ _walIndex . ConfirmTransaction ( transactionID , new [ ] { new PagePosition ( 0 , clone . Position ) } ) ;
110+
111+ _header . TransactionID = uint . MaxValue ;
112+ _header . IsConfirmed = false ;
113+ _header . UpdateBuffer ( ) ;
114+ }
115+
13116 /// <summary>
14117 /// Recovery datafile using a rebuild process. Run only on "Open" database
15118 /// </summary>
@@ -28,4 +131,4 @@ private void Recovery(Collation collation)
28131 rebuilder . Rebuild ( options ) ;
29132 }
30133 }
31- }
134+ }
0 commit comments