1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
|
/*
* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Data.Caching.ObjectCache
* File: CacheEntry.cs
*
* CacheEntry.cs is part of VNLib.Data.Caching.ObjectCache which is part of the larger
* VNLib collection of libraries and utilities.
*
* VNLib.Data.Caching.ObjectCache is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* VNLib.Data.Caching.ObjectCache is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
using System;
using System.Buffers;
using System.Diagnostics;
using System.Buffers.Binary;
using System.Runtime.CompilerServices;
using VNLib.Utils.Memory;
namespace VNLib.Data.Caching
{
/// <summary>
/// A structure that represents an item in cache. It contains the binary content
/// of a cache entry by its internal memory handle
/// </summary>
public readonly record struct CacheEntry : IDisposable
{
private const int TIME_SEGMENT_SIZE = sizeof(long);
private const int LENGTH_SEGMENT_SIZE = sizeof(int);
private const int DATA_SEGMENT_START = TIME_SEGMENT_SIZE + LENGTH_SEGMENT_SIZE;
private readonly ICacheEntryMemoryManager _manager;
private readonly object _handle;
/// <summary>
/// Creates a new <see cref="CacheEntry"/> and copies the initial data to the internal buffer
/// </summary>
/// <param name="data">The initial data to store</param>
/// <param name="dataManager">The heap to allocate the buffer from</param>
/// <returns>The newly initialized and ready to use <see cref="CacheEntry"/></returns>
/// <exception cref="ArgumentNullException"></exception>
public static CacheEntry Create(ReadOnlySpan<byte> data, ICacheEntryMemoryManager dataManager)
{
_ = dataManager ?? throw new ArgumentNullException(nameof(dataManager));
//Calc buffer size
uint bufferSize = GetRequiredHandleSize(data.Length);
object handle = dataManager.AllocHandle(bufferSize);
//Create new entry from handle
CacheEntry entry = new(dataManager, handle);
entry.SetLength((uint)data.Length);
//Get the data segment
Span<byte> segment = entry.GetDataSegment();
Debug.Assert(segment.Length == data.Length);
//Copy data segment
data.CopyTo(segment);
return entry;
}
/// <summary>
/// Creates a new <see cref="CacheEntry"/> from an existing handle
/// </summary>
/// <param name="handle">The cache data handle to create the entry around</param>
/// <param name="manager">The cache entry memory manager the handle blongs to</param>
/// <returns>The re-constructed entry</returns>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
public static CacheEntry FromExistingHandle(object handle, ICacheEntryMemoryManager manager)
{
_ = handle ?? throw new ArgumentNullException(nameof(handle));
_ = manager ?? throw new ArgumentNullException(nameof(manager));
//validate handle size it at least the minimum size
if (manager.GetHandleSize(handle) < DATA_SEGMENT_START)
{
throw new ArgumentException("Memory segment is too small to be a valid cache entry");
}
return new(manager, handle);
}
private static uint GetRequiredHandleSize(int size)
{
//Caculate the minimum handle size to store all required information, rounded to nearest page
return (uint)MemoryUtil.NearestPage(size + DATA_SEGMENT_START);
}
private CacheEntry(ICacheEntryMemoryManager manager, object handle)
{
_manager = manager;
_handle = handle;
}
///<inheritdoc/>
public readonly void Dispose() => _manager?.FreeHandle(_handle);
private readonly Span<byte> GetTimeSegment() => _manager.GetSpan(_handle, 0, TIME_SEGMENT_SIZE);
private readonly Span<byte> GetLengthSegment() => _manager.GetSpan(_handle, TIME_SEGMENT_SIZE, LENGTH_SEGMENT_SIZE);
/// <summary>
/// Gets the size of the block of memory held by the underlying handle
/// </summary>
/// <returns>The size of the block held by the current entry</returns>
/// <exception cref="ObjectDisposedException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly nuint GetMemoryUsage() => _manager.GetHandleSize(_handle);
/// <summary>
/// Gets the last set time
/// </summary>
/// <returns>The last date stored</returns>
/// <exception cref="ObjectDisposedException"></exception>
public readonly DateTime GetTime()
{
//Get the time segment and write the value in big endian
ReadOnlySpan<byte> segment = GetTimeSegment();
long ticks = BinaryPrimitives.ReadInt64BigEndian(segment);
//ticks back to
return new(ticks);
}
/// <summary>
/// Sets the last modified time
/// </summary>
/// <param name="time">The new time to set the handle to</param>
/// <exception cref="ObjectDisposedException"></exception>
public readonly void SetTime(DateTime time)
{
//Get native ticks value
long timeData = time.Ticks;
//Get the time segment and write the value in big endian
Span<byte> segment = GetTimeSegment();
BinaryPrimitives.WriteInt64BigEndian(segment, timeData);
}
/// <summary>
/// Gets the length of the data segment
/// </summary>
/// <returns>The length of the data segment</returns>
/// <exception cref="ObjectDisposedException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly uint GetLength()
{
//Get the length segment
ReadOnlySpan<byte> segment = GetLengthSegment();
//Recover the integer
return BinaryPrimitives.ReadUInt32BigEndian(segment);
}
private readonly void SetLength(uint length)
{
//Get the length segment
Span<byte> segment = GetLengthSegment();
//Update the length value
BinaryPrimitives.WriteUInt32BigEndian(segment, length);
}
/// <summary>
/// Gets the stored data segment
/// </summary>
/// <returns>The data segment</returns>
/// <exception cref="ObjectDisposedException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Span<byte> GetDataSegment()
{
//Get the actual length of the segment
uint length = GetLength();
//Get the segment from its begining offset and
return _manager.GetSpan(_handle, DATA_SEGMENT_START, length);
}
/// <summary>
/// Writes the specified segment to the internal buffer and resizes the buffer if necessary.
/// This operation overwrites any previously stored data
/// </summary>
/// <param name="data">The data segment to store</param>
/// <exception cref="ObjectDisposedException"></exception>
public readonly void UpdateData(ReadOnlySpan<byte> data)
{
//Calc required buffer size
uint bufferSize = GetRequiredHandleSize(data.Length);
//Resize buffer if necessary
if(_manager.GetHandleSize(_handle) < bufferSize)
{
//resize handle
_manager.ResizeHandle(_handle, bufferSize);
}
//Reset data length
SetLength((uint)data.Length);
//Get the data segment
Span<byte> segment = GetDataSegment();
//Test segment length is equivalent to the requested data length
Debug.Assert(segment.Length == data.Length);
//Copy data segment
data.CopyTo(segment);
}
/// <summary>
/// Gets a <see cref="MemoryHandle"/> offset to the start of the
/// internal data segment, and avoids calling the fixed keyword.
/// The handle must be disposed/released to avoid memeory leaks.
/// </summary>
/// <remarks>
/// WARNING: You must respect the <see cref="GetLength"/> return value so
/// as not to overrun the valid data segment.
/// </remarks>
/// <returns>A handle that points to the begining of the data segment</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly MemoryHandle UnsafeGetDataSegmentHandle()
{
//Get the handle offset to the data segment start, the caller must know when the data segment ends
return _manager.PinHandle(_handle, DATA_SEGMENT_START);
}
/// <summary>
/// Gets the internal memory handle and manager its associated with
/// </summary>
/// <param name="handle">The opaque memory handle</param>
/// <param name="manager">The associated memory manager</param>
public readonly void GetInternalHandle(out object handle, out ICacheEntryMemoryManager manager)
{
handle = _handle;
manager = _manager;
}
}
}
|