18 using System.Collections.Generic;
24 namespace Deveel.Data.Store {
29 private readonly byte[] binArea =
new byte[128 * 8];
31 private static readonly
int[] BinSizes =
33 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 480,
34 512, 544, 576, 608, 640, 672, 704, 736, 768, 800, 832, 864, 896, 928,
35 960, 992, 1024, 1056, 1088, 1120, 1152, 1184, 1216, 1248, 1280, 1312,
36 1344, 1376, 1408, 1440, 1472, 1504, 1536, 1568, 1600, 1632, 1664, 1696,
37 1728, 1760, 1792, 1824, 1856, 1888, 1920, 1952, 1984, 2016, 2048, 2080,
38 2144, 2208, 2272, 2336, 2400, 2464, 2528, 2592, 2656, 2720, 2784, 2848,
39 2912, 2976, 3040, 3104, 3168, 3232, 3296, 3360, 3424, 3488, 3552, 3616,
40 3680, 3744, 3808, 3872, 3936, 4000, 4064, 4128, 4384, 4640, 4896, 5152,
41 5408, 5664, 5920, 6176, 6432, 6688, 6944, 7200, 7456, 7712, 7968, 8224,
42 10272, 12320, 14368, 16416, 18464, 20512, 22560, 24608, 57376, 90144,
43 122912, 155680, 1204256, 2252832
46 private readonly
static int BinSizeEntries = BinSizes.Length;
47 private readonly
static int MaxBinSize = BinSizes[BinSizeEntries - 1];
49 private const long ActiveFlag = Int64.MaxValue;
50 private const long DeletedFlag = Int64.MinValue;
52 private const long FixedAreaOffset = 128;
55 private const long DataAreaOffset = 256 + 1024 + 32;
57 private const long BinAreaOffset = 256;
59 private const int Magic = 0x0AEAE91;
62 freeBinList =
new long[BinSizeEntries + 1];
63 for (
int i = 0; i < BinSizeEntries + 1; ++i) {
67 WildernessOffset = -1;
69 IsReadOnly = isReadOnly;
76 private long WildernessOffset {
get; set; }
78 public bool IsReadOnly {
get;
private set; }
80 protected abstract long DataAreaEndOffset {
get; }
83 if (offset < DataAreaOffset || offset >= DataAreaEndOffset) {
84 throw new IOException(String.Format(
"The offset is out of range ({0} > {1} > {2})", DataAreaOffset, offset,
90 int i = Array.BinarySearch(BinSizes, (
int)size);
98 Read(BinAreaOffset, binArea, 0, 128 * 8);
99 using (var bin =
new MemoryStream(binArea)) {
100 using (BinaryReader input =
new BinaryReader(bin)) {
101 for (
int i = 0; i < 128; ++i) {
102 freeBinList[i] = input.ReadInt64();
110 for (
int i = 0; i < 128; ++i, p += 8) {
111 long val = freeBinList[i];
115 Write(BinAreaOffset, binArea, 0, 128 * 8);
120 long val = freeBinList[index];
122 Write(BinAreaOffset + p, binArea, p, 8);
126 CheckOffset(pointer);
129 int binChainIndex = MinimumBinSizeIndex(size);
130 var headerInfo =
new long[2];
132 long curOffset = freeBinList[binChainIndex];
134 if (curOffset == -1) {
136 headerInfo[0] = (size | DeletedFlag);
139 ReboundArea(pointer, headerInfo,
true);
140 freeBinList[binChainIndex] = pointer;
142 WriteBinIndex(binChainIndex);
144 bool inserted =
false;
145 long lastOffset = -1;
147 while (curOffset != -1 && inserted ==
false) {
149 ReadAreaHeader(curOffset, headerInfo);
151 long header = headerInfo[0];
152 long next = headerInfo[1];
155 if ((header & DeletedFlag) == 0)
156 throw new IOException(
"Area not marked as deleted.");
158 long areaSize = header ^ DeletedFlag;
159 if (areaSize >= size || searches >= 12) {
162 long previous = lastOffset;
165 headerInfo[0] = (size | DeletedFlag);
166 headerInfo[1] = curOffset;
168 ReboundArea(pointer, headerInfo,
true);
170 if (lastOffset != -1) {
172 ReadAreaHeader(previous, headerInfo);
174 headerInfo[1] = pointer;
175 ReboundArea(previous, headerInfo,
false);
178 freeBinList[binChainIndex] = pointer;
179 WriteBinIndex(binChainIndex);
185 lastOffset = curOffset;
193 headerInfo[0] = (size | DeletedFlag);
195 ReboundArea(pointer, headerInfo,
true);
198 ReadAreaHeader(lastOffset, headerInfo);
199 headerInfo[1] = pointer;
200 ReboundArea(lastOffset, headerInfo,
false);
207 int binChainIndex = MinimumBinSizeIndex(size);
209 var prevOffset = -1L;
210 var curOffset = freeBinList[binChainIndex];
214 while (offset != curOffset) {
216 throw new IOException(
"Area not found input bin chain.");
219 var headerInfo =
new long[2];
220 ReadAreaHeader(curOffset, headerInfo);
222 prevOffset = curOffset;
223 curOffset = headerInfo[1];
227 if (prevOffset == -1) {
228 var headerInfo =
new long[2];
230 ReadAreaHeader(offset, headerInfo);
231 freeBinList[binChainIndex] = headerInfo[1];
232 WriteBinIndex(binChainIndex);
234 var headerInfo =
new long[2];
235 var headerInfo2 =
new long[2];
237 ReadAreaHeader(prevOffset, headerInfo2);
238 ReadAreaHeader(offset, headerInfo);
239 headerInfo2[1] = headerInfo[1];
240 ReboundArea(prevOffset, headerInfo2,
false);
245 private void Free(
long pointer) {
247 var headerInfo =
new long[2];
248 ReadAreaHeader(pointer, headerInfo);
250 if ((headerInfo[0] & DeletedFlag) != 0)
251 throw new IOException(
"Area already marked as unallocated.");
255 bool setAsWilderness = ((pointer + headerInfo[0]) >= DataAreaEndOffset);
257 var rOffset = pointer;
258 var freeingAreaSize = headerInfo[0];
259 var rSize = freeingAreaSize;
262 var headerInfo2 =
new long[2];
263 var leftPointer = GetPreviousAreaHeader(pointer, headerInfo2);
266 if ((headerInfo2[0] & DeletedFlag) != 0) {
268 long areaSize = (headerInfo2[0] & ActiveFlag);
270 rOffset = leftPointer;
271 rSize = rSize + areaSize;
274 RemoveFromBinChain(leftPointer, areaSize);
278 if (!setAsWilderness) {
279 long rightPointer = GetNextAreaHeader(pointer, headerInfo2);
280 if ((headerInfo2[0] & DeletedFlag) != 0) {
282 long areaSize = (headerInfo2[0] & ActiveFlag);
284 rSize = rSize + areaSize;
287 RemoveFromBinChain(rightPointer, areaSize);
288 setAsWilderness = (rightPointer == WildernessOffset);
295 CoalesceArea(rOffset, rSize);
298 AddToBinChain(rOffset, rSize);
302 WildernessOffset = rOffset;
304 totalAllocatedSpace -= freeingAreaSize;
310 if (offset == DataAreaOffset) {
316 Read(offset - 8, headerBuf, 0, 8);
318 sz = sz & ActiveFlag;
319 long previousPointer = offset - sz;
320 Read(previousPointer, headerBuf, 0, 8);
322 return previousPointer;
326 Read(offset, headerBuf, 0, 8);
328 sz = sz & ActiveFlag;
329 long nextOffset = offset + sz;
331 if (nextOffset >= DataAreaEndOffset) {
337 Read(nextOffset, headerBuf, 0, 8);
343 Read(offset, headerBuf, 0, 16);
348 private readonly byte[] headerBuf =
new byte[16];
350 private void ReboundArea(
long offset,
long[] header,
bool writeHeaders) {
354 Write(offset, headerBuf, 0, 16);
357 Write(offset + 8, headerBuf, 8, 8);
368 Write(offset, headerBuf, 0, 8);
369 Write((offset + size) - 8, headerBuf, 0, 8);
372 private void CropArea(
long offset,
long allocatedSize) {
374 var headerInfo =
new long[2];
375 ReadAreaHeader(offset, headerInfo);
377 var header = headerInfo[0];
379 var freeAreaSize = header;
380 var sizeDifference = freeAreaSize - allocatedSize;
381 var isWilderness = (offset == WildernessOffset);
386 if ((isWilderness && sizeDifference >= 32) || sizeDifference >= 512) {
388 SplitArea(offset, allocatedSize);
390 long leftOverPointer = offset + allocatedSize;
392 AddToBinChain(leftOverPointer, sizeDifference);
396 (leftOverPointer + sizeDifference) >= DataAreaEndOffset) {
397 WildernessOffset = leftOverPointer;
403 WildernessOffset = -1;
410 throw new IOException(
"Negative size allocation");
420 long d = size & 0x07L;
422 size = size + (8 - d);
424 long realAllocSize = size;
428 if (size > MaxBinSize) {
429 binChainIndex = BinSizeEntries;
431 int i = MinimumBinSizeIndex(size);
437 int foundBinIndex = -1;
438 long prevOffset = -1;
440 for (
int i = binChainIndex;
441 i < BinSizeEntries + 1 && foundBinIndex == -1;
443 long curOffset = freeBinList[i];
444 if (curOffset != -1) {
453 long lastOffset = -1;
455 while (curOffset != -1 &&
456 foundBinIndex == -1 &&
459 var headerInfo =
new long[2];
460 ReadAreaHeader(curOffset, headerInfo);
462 long areaSize = (headerInfo[0] & ActiveFlag);
466 if (curOffset != WildernessOffset && areaSize >= size) {
468 prevOffset = lastOffset;
472 lastOffset = curOffset;
473 curOffset = headerInfo[1];
484 if (foundBinIndex == -1) {
489 long currentAreaSize;
490 if (WildernessOffset != -1) {
491 workingOffset = WildernessOffset;
493 var headerInfo =
new long[2];
494 ReadAreaHeader(WildernessOffset, headerInfo);
496 long wildernessSize = (headerInfo[0] & ActiveFlag);
499 RemoveFromBinChain(workingOffset, wildernessSize);
502 WildernessOffset = -1;
503 sizeToGrow = size - wildernessSize;
504 currentAreaSize = wildernessSize;
507 workingOffset = DataAreaEndOffset;
512 long expandedSize = 0;
513 if (sizeToGrow > 0) {
515 expandedSize = ExpandDataArea(sizeToGrow);
519 CoalesceArea(workingOffset, currentAreaSize + expandedSize);
522 CropArea(workingOffset, size);
525 totalAllocatedSpace += realAllocSize;
527 return workingOffset;
531 var headerInfo =
new long[2];
535 if (prevOffset == -1) {
536 freeAreaOffset = freeBinList[foundBinIndex];
537 ReadAreaHeader(freeAreaOffset, headerInfo);
538 freeBinList[foundBinIndex] = headerInfo[1];
539 WriteBinIndex(foundBinIndex);
541 var headerInfo2 =
new long[2];
542 ReadAreaHeader(prevOffset, headerInfo2);
543 freeAreaOffset = headerInfo2[1];
544 ReadAreaHeader(freeAreaOffset, headerInfo);
545 headerInfo2[1] = headerInfo[1];
546 ReboundArea(prevOffset, headerInfo2,
false);
550 headerInfo[0] = (headerInfo[0] & ActiveFlag);
551 ReboundArea(freeAreaOffset, headerInfo,
true);
554 CropArea(freeAreaOffset, size);
557 totalAllocatedSpace += realAllocSize;
559 return freeAreaOffset;
565 long endOfDataArea = DataAreaEndOffset;
570 long overGrow = endOfDataArea / 64;
571 long d = (overGrow & 0x07L);
573 overGrow = overGrow + (8 - d);
575 overGrow =
System.Math.Min(overGrow, 262144L);
579 long growBy = minSize + overGrow;
580 long newFileLength = endOfDataArea + growBy;
581 SetDataAreaSize(newFileLength);
585 protected void SplitArea(
long offset,
long newBoundary) {
587 Read(offset, headerBuf, 0, 8);
589 long leftSize = newBoundary;
590 long rightSize = curSize - newBoundary;
593 throw new IOException(
"Could not split the area.");
603 Write((offset + newBoundary) - 8, headerBuf, 0, 16);
605 Write(offset, headerBuf, 0, 8);
606 Write((offset + curSize) - 8, headerBuf, 8, 8);
610 const long maxAreaSize = (long)Int32.MaxValue * 200;
611 size = size & ActiveFlag;
612 return ((size < maxAreaSize) && (size >= 24) && ((size & 0x07) == 0));
617 SetDataAreaSize(DataAreaOffset);
619 using (var stream =
new MemoryStream((
int) BinAreaOffset)) {
620 using (BinaryWriter writer =
new BinaryWriter(stream, Encoding.Unicode)) {
632 writer.Write((byte) 0);
636 byte[] buffer =
new byte[(int) DataAreaOffset];
637 byte[] temp = stream.ToArray();
638 Array.Copy(temp, 0, buffer, 0, temp.Length);
640 for (
int i = (
int) BinAreaOffset; i < (int) DataAreaOffset; ++i) {
644 Write(0, buffer, 0, buffer.Length);
651 protected abstract void SetDataAreaSize(
long length);
655 OpenStore(IsReadOnly);
658 if (DataAreaEndOffset < DataAreaOffset)
661 byte[] readBuf =
new byte[(int) BinAreaOffset];
662 Read(0, readBuf, 0, readBuf.Length);
664 using (var stream =
new MemoryStream(readBuf)) {
665 using (var reader =
new BinaryReader(stream)) {
667 int magic = reader.ReadInt32();
669 throw new IOException(
"Format invalid: Magic value is not as expected.");
671 int version = reader.ReadInt32();
673 throw new IOException(
"Format invalid: unrecognized version.");
676 byte status = reader.ReadByte();
693 long fileLength = DataAreaEndOffset;
694 if (fileLength <= 8) {
695 throw new IOException(
"Format invalid: File size is too small.");
699 if (fileLength == DataAreaOffset) {
700 WildernessOffset = -1;
702 Read(fileLength - 8, readBuf, 0, 8);
704 long lastAreaPointer = fileLength - lastBoundary;
706 if (lastAreaPointer < DataAreaOffset)
707 throw new IOException(
"File corrupt: last area offset is before data part of file.");
709 if (lastAreaPointer > fileLength - 8)
710 throw new IOException(
"File corrupt: last_area_pointer at the end of the file.");
712 Read(lastAreaPointer, readBuf, 0, 8);
717 if ((lastAreaHeader & DeletedFlag) != 0) {
718 WildernessOffset = lastAreaPointer;
720 WildernessOffset = -1;
740 GC.SuppressFinalize(
this);
745 protected virtual void Dispose(
bool disposing) {
757 long pointer = Alloc(size);
758 return new StoreArea(
this, pointer, pointer + 8,
false, size);
771 return new StoreArea(
this,
id, FixedAreaOffset, readOnly, 64);
774 return new StoreArea(
this,
id,
id, readOnly);
777 public abstract void Lock();
779 public abstract void Unlock();
783 public bool ClosedClean {
get;
private set; }
786 var list =
new List<long>();
788 long endOfDataArea = DataAreaEndOffset;
790 long[] header =
new long[2];
792 long offset = DataAreaOffset;
793 while (offset < endOfDataArea) {
794 ReadAreaHeader(offset, header);
796 long areaSize = (header[0] & ActiveFlag);
797 if ((header[0] & DeletedFlag) == 0)
808 var list =
new List<long>(usedAreas);
812 var leakedAreas =
new List<long>();
817 long lookingFor = Int64.MaxValue;
818 if (listIndex < list.Count) {
819 lookingFor = list[listIndex];
823 long endOfDataArea = DataAreaEndOffset;
824 long[] header =
new long[2];
826 long offset = DataAreaOffset;
827 while (offset < endOfDataArea) {
828 ReadAreaHeader(offset, header);
830 long areaSize = (header[0] & ActiveFlag);
831 bool areaFree = (header[0] & DeletedFlag) != 0;
833 if (offset == lookingFor) {
835 throw new IOException(
"Area is not allocated!");
838 if (listIndex < list.Count) {
839 lookingFor = (long)list[listIndex];
842 lookingFor = Int64.MaxValue;
844 }
else if (offset > lookingFor) {
845 throw new IOException(
"IArea (offset = " + lookingFor +
") wasn't found input store!");
851 leakedAreas.Add(offset);
858 return leakedAreas.ToArray();
861 protected abstract void OpenStore(
bool readOnly);
863 protected abstract void CloseStore();
866 var buffer =
new byte[1];
867 var count = Read(offset, buffer, 0, 1);
874 protected abstract int Read(
long offset, byte[] buffer,
int index,
int length);
876 protected void Write(
long offset, byte value) {
877 Write(offset,
new []{value}, 0, 1);
880 protected abstract void Write(
long offset, byte[] buffer,
int index,
int length);
888 private byte[] buffer =
new byte[BufferSize];
891 private const int BufferSize = 8;
896 IsReadOnly = readOnly;
900 store.
Read(offset, buffer, 0, 8);
902 if ((v & DeletedFlag) != 0)
903 throw new IOException(
"Store being constructed on deleted area.");
905 long maxSize = v - 16;
906 StartOffset = offset + 8;
907 position = StartOffset;
908 EndOffset = StartOffset + maxSize;
914 IsReadOnly = readOnly;
917 if (offset != FixedAreaOffset) {
921 StartOffset = offset;
922 position = StartOffset;
923 EndOffset = StartOffset + fixedSize;
926 public long Id {
get;
private set; }
928 private long StartOffset {
get; set; }
930 private long EndOffset {
get; set; }
934 public virtual bool IsReadOnly {
get;
private set; }
936 public long Position {
937 get {
return position - StartOffset; }
939 long actPosition = StartOffset + value;
940 if (actPosition < 0 || actPosition >= EndOffset)
941 throw new IOException(
"Moved position out of the area bounds.");
943 position = actPosition;
948 get {
return (
int)(EndOffset - StartOffset); }
952 long newPos = position + diff;
953 if (newPos > EndOffset)
954 throw new IOException(
"Trying to access a position out of area bounds.");
956 long oldPos = position;
965 const int bufferSize = 2048;
966 byte[] buf =
new byte[bufferSize];
967 int toCopy =
System.Math.Min(size, bufferSize);
970 var read = Read(buf, 0, toCopy);
974 destArea.
Write(buf, 0, read);
976 toCopy =
System.Math.Min(size, bufferSize);
980 public int Read(byte[] buffer,
int offset,
int length) {
981 return Store.Read(CheckAreaOffset(length), buffer, offset, length);
984 public void Write(byte[] buffer,
int offset,
int length) {
986 throw new IOException(
"The area is read-only access.");
988 Store.Write(CheckAreaOffset(length), buffer, offset, length);
void CropArea(long offset, long allocatedSize)
static int MinimumBinSizeIndex(long size)
void ReboundArea(long offset, long[] header, bool writeHeaders)
void SplitArea(long offset, long newBoundary)
An IArea that is backed by a StoreBase.
void CheckOffset(long offset)
long GetPreviousAreaHeader(long offset, long[] header)
void Write(byte[] buffer, int offset, int length)
void CopyTo(IArea destArea, int size)
Copies the given amount of bytes from the current position of the this area to another one...
virtual void Dispose(bool disposing)
IEnumerable< long > FindAllocatedAreasNotIn(List< long > usedAreas)
void Write(long offset, byte value)
long GetNextAreaHeader(long offset, long[] header)
void CoalesceArea(long offset, long size)
abstract int Read(long offset, byte[] buffer, int index, int length)
void RemoveFromBinChain(long offset, long size)
StoreArea(StoreBase store, long id, long offset, bool readOnly)
void WriteBinIndex(int index)
long CheckAreaOffset(int diff)
StoreBase(bool isReadOnly)
IArea CreateArea(long size)
Allocates a block of memory in the store of the specified size and returns an IArea object that can b...
int Read(byte[] buffer, int offset, int length)
Reads an array of bytes from the underlying IArea and advances the position by length ...
An interface for access the contents of an area of a store.
int ReadByte(long offset)
A wrapper for an array of byte.
StoreArea(StoreBase store, long id, long offset, bool readOnly, long fixedSize)
void DeleteArea(long id)
Deletes an area that was previously allocated by the CreateArea method by the area id...
static long ReadInt8(byte[] arr, int offset)
long ExpandDataArea(long minSize)
void ReadAreaHeader(long offset, long[] header)
IArea GetArea(long id, bool readOnly)
Returns an object that allows for the contents of an area (represented by the id parameter) to be Re...
void AddToBinChain(long pointer, long size)
IEnumerable< long > GetAllAreas()
Returns a complete list of pointers to all areas in the Store as long objects sorted from lowest poin...
A store is a resource where areas can be allocated and freed to store information (a memory allocator...
static bool IsValidBoundarySize(long size)
static void WriteInt8(long value, byte[] arr, int offset)
void Write(byte[] buffer, int offset, int length)