6767
6868 * When block is freed:
6969 - added to unstable list
70- -
70+ -
71+ * Whent to verify csums?
72+ - on every access
73+ - ideally only when it is loaded from disk
74+ - but os does not tell wheter it was loaded or already in pagecache
75+ - bitfield for already verified pages? (in primary area)
76+ - only when requested
77+
78+ * To read from multiple processes:
79+ - read superblock on every tx open
80+ -
7181
7282 RTrx gen=1
7383 WTrx gen=1 -> 2, A:U->F, B:x->F, C:F->x
7484 -
7585}
7686INTERFACE
7787IMPLEMENTATION
78- uses bn_avl_tree;
88+ uses Classes,SysUtils, bn_avl_tree,ObjectModel,BaseUnix,crypto ;
7989
8090Type tDatabase= Class
8191 type tGenNum= QWord;
@@ -89,39 +99,356 @@ type tLLPageGenNode=class
8999 type tLLGeneration=class
90100 private { ro by transactions}
91101 gen: tGenNum;
92- root: tPageix;
102+ root: tCSPtr;
103+ super: tPageix;
93104 private { atomic by transaction}
94105 refc: tRefCount;
95106 private { under write mutex}
96107 next: tLLGeneration;
97108 sync: boolean;
98109 end ;
110+ type tCSPtr= packed object
111+ page: Word4;
112+ csum: DWord;
113+ // procedure clear;
114+ // procedure set(ipage:tPageix; isum: dword);
115+ end ;
99116
100117 public
101118
102- WriteMutex: TRTLCriticalSection ;
119+ FileHandle: THandle ;
103120
104121 (* Memorry-mapped Window into the Primary Branch Area *)
105122 PrimaryWindow: Pointer;
106- WindowSize: LongWord ;
107-
108- (* Current writeback root *)
109- Generation: tLLGeneration
123+ PrimaryWindowEnd: Pointer ;
124+ PrimarySize: LongWord;
125+ MixedStart: tPageIx;
126+ MixedSize: LongWord;
110127
111- (* Current synced root *)
112- GenerationSync: tLLGeneration;
113128
114129 (* Generation number on which the oldest open transaction was created *)
115- OldestGeneration: tLLGeneration;
116- LatestGeneration: tLLGeneration;
130+ { Unused space: page list, each page one generation
131+ $UnusedLatest -> page[gen, next, older, arr[pageix]]
132+ $UnusedOldest -> page (not modified, only moved, freed pages go to unused)
133+ Open read transaction share-locks the latest node page.
134+ @In write trx the oldest used node found by following the older pointer until
135+ exclusive lock succeeds. It still costs syscalls, but so does writing.
136+ @Put list of pages that blocked reclaim in ascending gen to superblock.
137+ @Only store Reusable in superblock, not unused.
138+ $freelist for all areas
139+ $freetree stub for mix only
140+ }
141+
142+ { these should be part of Write transaction (memory only) }
143+ DirtyPages: tAVLTree;
144+
145+
146+ pagesize: LongWord;
147+
148+ constructor OpenH(const ihandle: THandle);
149+ constructor Format(const ihandle: THandle);
150+
151+ procedure LoadDatabase ;
152+ procedure ReadUnaligned (var dest; offset: PtrUInt; size: PtrUInt);
153+ function ChecksumBuf (buffer: pointer; size: PtrUInt): DWord;
154+ function ChecksumPage (buffer: pointer): DWord;
155+
156+ procedure FreePage ( p: pointer );
157+ function GetPage ( ix: tPageIx ): pointer;
158+ function GetPage ( const ptr: tCSPtr ): pointer;
159+ (* Special pages: 0=super0 1=super1 2=super2 3=locks -1=last *)
160+ var SpecialPageIndex: array [0 ..4 ] of tPageIx;
161+ end ;
162+
163+ Type TWriteTrx= Class
164+ db: tDatabase;
165+ constructor Create(idb: tDatabase);
166+ end ;
167+
168+ var Log1: tEventLogBaseSink;
169+ var Log: TEventLog;
170+
171+ constructor tWriteTrx.Create(idb: tDatabase);
172+ begin
173+ Fail;
174+ end ;
175+
176+
177+ type tSuperblockH = packed record
178+ ident: array [1 ..8 ] of char;
179+ checksum: DWord;
180+ gen: Word8;
181+ pagesize: Word4;
182+ version: word2;
183+ sync: bytebool;
184+ height: byte;
185+ prisize, // 1C
186+ secsize,
187+ mixsize, // 24
188+ secpos,
189+ mixpos: Word4; // 2C
190+ root: tDatabase.tCSPtr;
191+ utilroot: tDatabase.tCSPtr;
192+ prifree: tDatabase.tCSPtr;
193+ secfree: tDatabase.tCSPtr;
194+ mixfree: tDatabase.tCSPtr;
195+ levelmix: byte;
196+ levelsec: byte;
197+ end ;
198+ const c_sb_ident: array [1 ..8 ] of char = ' BnBtrDb' #0 ;
199+
200+ type EDbCheckSum= class (Exception)
201+ // constructor Create(ipageno:tPageNo);
202+ // property Message: string read What;
203+ end ;
204+ type EDbInitialize= class (Exception)
205+ end ;
206+ type EDbSuperInit= class (EDbInitialize)
207+ end ;
208+
209+ procedure tDatabase.ReadUnaligned (var dest; offset: PtrUInt; size: PtrUInt);
210+ var rc:TsSize;
211+ begin
212+ rc:=baseunix.fppread(FileHandle,@dest,size,offset);
213+ if rc<>size then raise Exception.Create(' PRead failed' );
214+ end ;
215+
216+ function WrapMMap ( fd:THandle; ofs:PtrUInt; size:PtrUInt ): pointer;
217+ var rc: pointer;
218+ begin
219+ rc:= BaseUnix.Fpmmap( { addr} nil , { len} size,
220+ { prot} PROT_READ,
221+ { flag} MAP_SHARED,
222+ { fd} fd, { off} ofs );
223+ if rc=MAP_FAILED then raise Exception.Create(' MMap failed' );
224+ result:=rc;
225+ end ;
226+
227+ procedure tDatabase.FreePage ( p: pointer );
228+ begin
229+ if (p>=PrimaryWindow) and (p<PrimaryWindowEnd)
230+ then exit;
231+ FreeMem(p,pagesize);
232+ end ;
233+
234+ function tDatabase.GetPage ( ix: tPageIx ): pointer;
235+ begin
236+ if ix < PrimarySize then begin
237+ result:= PrimaryWindow + ( ix * pagesize );
238+ end else begin
239+ result:=GetMem(pagesize);
240+ try
241+ ReadUnaligned( result^, ix * pagesize, pagesize );
242+ except
243+ FreeMem(result,pagesize);
244+ raise;
245+ end
246+ end
247+ end ;
117248
118- (* Pages that can be used for allocation if node.gen<FirstTransaction Populated during BeginWriteTrx from
119- * synced unstable list. Flushed on Sync (not Confirm). *)
120- ReusableFirst tLLPageGenNode;
121- ReusableLast: tLLPageGenNode;
249+ function tDatabase.GetPage ( ptr: tCSPtr ): pointer;
250+ var sum: DWord;
251+ var ix: tPageIx;
252+ begin
253+ ix:= ptr.page;
254+ if ix < PrimarySize then begin
255+ result:= PrimaryWindow + ( ix * pagesize );
256+ sum:= ChecksumPage(result);
257+ if (sum<>ptr.csum)
258+ then raise EDbCheckSum.CreateFmt(' Checksum error at page %Dp, correct %S' ,[DWord(ptr.page),ToHexStr(sum,4 )]);
259+ end else begin
260+ result:=GetMem(pagesize);
261+ try
262+ ReadUnaligned( result^, ix * pagesize, pagesize );
263+ sum:= ChecksumPage(result);
264+ if (sum<>ptr.csum)
265+ then raise EDbCheckSum.CreateFmt(' Checksum error at page %Dp, correct %S' ,[DWord(ptr.page),ToHexStr(sum,4 )]);
266+ except
267+ FreeMem(result,pagesize);
268+ raise;
269+ end
270+ end
271+ end ;
272+
273+ procedure tDatabase.LoadDatabase ();
274+ var sbpagesize: LongWord;
275+ var super: ^tSuperblockH;
276+ procedure insertgen (gen: tLLGeneration);
277+ begin
278+ end ;
279+ procedure CheckSbAt (sbpos:PtrUInt);
280+ (* Early Superblock Check *)
281+ var buf:^tSuperblockH;
282+ var tmp:DWORD;
283+ var gen: tLLGeneration;
284+ var p:^tLLGeneration;
285+ begin
286+ buf:=GetMem(512 );
287+ try
288+ ReadUnaligned(buf^,sbpos,512 );
289+ if buf^.ident <> c_sb_ident then exit;
290+ if (DWord(buf^.pagesize) = 0 )
291+ or ((sbpos mod DWord(buf^.pagesize)) >0 ) then exit;
292+ if buf^.checksum<>ChecksumBuf(pointer(buf)+12 ,500 ) then begin
293+ tmp:=ChecksumBuf(pointer(buf)+12 ,500 );
294+ log.Warn(' .CheckSbAt: Invalid bufblock checksum at %DB' ,[sbpos]);
295+ log.info(' .CheckSbAt: correct %S' ,[ToHexStr(tmp,4 )]);
296+ exit end ;
297+ gen:= tLLGeneration.Create;
298+ gen.gen:= buf^.gen;
299+ gen.super:= sbpos div DWord(buf^.pagesize);
300+ gen.sync:= buf^.sync;
301+ gen.root:= buf^.root;
302+ gen.refc:= 1 ; { the 1 reference is from written superblock}
303+ p:=@OldestGeneration;
304+ while assigned(p^) and (p^.gen<gen.gen)
305+ do p:=@p^.next;
306+ gen.next:=p^;
307+ p^:=gen;
308+ if gen.next=nil then begin
309+ LatestGeneration:=gen;
310+ if assigned(super) then FreeMem(super,512 );
311+ super:=buf;
312+ sbpagesize:=DWord(buf^.pagesize);
313+ end ;
314+ finally
315+ if buf<>super then FreeMem(buf);
316+ end
317+ end ;
318+
319+ procedure LoadAreas ;
320+ begin
321+ { Focus on base functionality. Custom page size may come later.}
322+ { Btw: pagesize means mul instead of shl. performace ??}
323+ if sbpagesize<>4096
324+ then raise EDbSuperInit.Create(' unsupported page size' );
325+ pagesize:= sbpagesize;
326+
327+ { find a way to calculate this with diffrent page size}
328+ SpecialPageIndex[0 ]:=0 ; { byte offset 0}
329+ SpecialPageIndex[1 ]:=65536 div pagesize;
330+ if SpecialPageIndex[1 ] <= 2
331+ then SpecialPageIndex[2 ]:=SpecialPageIndex[1 ]+1
332+ else SpecialPageIndex[2 ]:=1 ;
333+ SpecialPageIndex[3 ]:=SpecialPageIndex[2 ]+1 ;
334+
335+ PrimaryWindow:= nil ;
336+ PrimarySize:= 0 ;
337+ PrimaryWindowEnd:=nil ;
338+
339+ if Word(super^.version)<>1
340+ then raise EDbInitialize.Create(' unknown superblock version' );
122341
123- AllocatedPages: tAVLTree;
342+
343+ if DWord(super^.prisize)>$800000
344+ then raise EDbInitialize.Create(' primary area too large' );
345+ PrimarySize:= DWord(super^.prisize);
346+ if PrimarySize <= SpecialPageIndex[3 ]
347+ then raise EDbInitialize.Create(' primary area too small' );
348+
349+ PrimaryWindow:= WrapMMap(FileHandle, 0 , PrimarySize * pagesize);
350+ PrimaryWindowEnd:= PrimaryWindow + (PrimarySize * pagesize) - 1 ;
351+ log.debug(' .LoadAreas: Primary window at $%P..$%P size %Dp' ,[PrimaryWindow, PrimaryWindowEnd, PrimarySize]);
352+
353+ if DWord(super^.secsize)<>0
354+ then raise EDbInitialize.Create(' unsupported secondary area' );
355+
356+ MixedStart:=super^.mixpos;
357+ MixedSize:= super^.mixsize;
358+
359+ if MixedStart<PrimarySize
360+ then raise EDbInitialize.Create(' mixed area overlaps primary area' );
361+ if MixedSize<24
362+ then raise EDbInitialize.Create(' mixed area too small' );
363+ SpecialPageIndex[4 ]:=MixedStart+(MixedSize-1 );
364+ FreePage(GetPage(SpecialPageIndex[4 ]));
365+ log.debug(' .LoadAreas: Mixed area at %Dp size %Dp' ,[MixedStart, MixedSize]);
366+ end ;
367+
368+ begin
369+ sbpagesize:=0 ;
370+ super:=nil ;
371+ { mark that the allocator is not initialized yet}
372+ DirtyPages:=nil ;
373+ try
374+ CheckSbAt(0 );
375+ CheckSbAt(65536 );
376+ if LatestGeneration=nil then
377+ raise EDbSuperInit.Create(' No valid superblocks found' );
378+ log.Info(' .LoadSuperblocks: found sb at %Dp with page size %DB generation %D' ,[LatestGeneration.super,sbpagesize,LatestGeneration.gen]);
379+ LoadAreas;
380+ finally
381+ if assigned(super) then FreeMem(super,512 );
382+ end ;
383+
384+
385+ { Find latest synced sb and use config from it.}
386+ { Check if dirty sb is pesent and attempt to recover from it.}
387+
388+ { find superblock}
389+ { get config from sb}
390+ end ;
391+
392+ { procedure tDatabase.LoadAreas;
393+ begin
394+ log.Debug('.LoadPrimaryArea ...',[]);
395+ check size
396+ mmap
397+ end;
398+ }
399+
400+ // procedure tDatabase.DoFormat;
401+ // begin
402+ { better?
403+ - only set memory state
404+ - create write transaction
405+ - free all pages
406+ - sync
407+ }
408+ { what?
409+ - determine areas
410+
411+ - write superblock(s)
412+ }
413+ // end;
414+
415+ constructor tDatabase.OpenH(const ihandle: THandle);
416+ begin
417+ Assert(ihandle>=0 );
418+ FileHandle:= ihandle;
419+ log.Debug(' .OpenH handle: %D' ,[FileHandle]);
420+ LoadDatabase();
124421end ;
125422
423+ constructor tDatabase.Format(const ihandle: THandle);
424+ begin
425+ Assert(ihandle>=0 );
426+ FileHandle:= ihandle;
427+ log.Debug(' .Format handle: %D' ,[FileHandle]);
428+ // DoFormat;
429+ end ;
430+
431+ (* Unaligned checksum calculation. *)
432+ function tDatabase.ChecksumBuf (buffer:pointer; size: PtrUInt): DWord;
433+ begin
434+ result:= NtoLE( Crypto.CRC32c(0 , buffer^, size) );
435+ end ;
436+
437+ (* Page-aligned checksum calculation. Open for optimization *)
438+ function tDatabase.ChecksumPage (buffer:pointer): DWord;
439+ begin
440+ assert((PtrUInt(@buffer) and (pagesize-1 )) = 0 );
441+ result:= NtoLE( Crypto.CRC32c(0 , buffer^, self.pagesize) );
442+ end ;
443+
444+
445+ var db:tDatabase;
446+
126447BEGIN
448+ Log1:=tEventLogBaseSink.Create;
449+ Log:=TEventLog.Create(Log1,' db' );
450+ Log.Info(' BtrDBase: %S' ,[' 0.0' ]);
451+ log.Info(' sb size: %D' ,[sizeof(tSuperblockH)]);
452+ db:=tDatabase.OpenH(FileOpen(' btrdb.dat' ,fmOpenReadWrite));
453+ db.Free;
127454END .
0 commit comments