Skip to content

Commit 1b1ce58

Browse files
committed
add missing files
Signed-off-by: Ran Shidlansik <[email protected]>
1 parent 7a89a70 commit 1b1ce58

File tree

2 files changed

+372
-0
lines changed

2 files changed

+372
-0
lines changed

src/entry.c

Lines changed: 347 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,347 @@
1+
#include <stdbool.h>
2+
#include "sds.h"
3+
#include "server.h"
4+
#include "serverassert.h"
5+
#include "entry.h"
6+
7+
#include <stdbool.h>
8+
9+
/*-----------------------------------------------------------------------------
10+
* Hash Entry API
11+
*----------------------------------------------------------------------------*/
12+
13+
/* The entry pointer is the field sds. We encode the entry layout type
14+
* in the field SDS header. Field type SDS_TYPE_5 doesn't have any spare bits to
15+
* encode this so we use it only for the first layout type.
16+
*
17+
* Entry with embedded value, used for small sizes. The value is stored as
18+
* SDS_TYPE_8. The field can use any SDS type.
19+
*
20+
* +--------------+---------------+
21+
* | field | value |
22+
* | hdr "foo" \0 | hdr8 "bar" \0 |
23+
* +------^-------+---------------+
24+
* |
25+
* |
26+
* entry pointer = field sds
27+
*
28+
* Entry with value pointer, used for larger fields and values. The field is SDS
29+
* type 8 or higher.
30+
*
31+
* +-------+--------------+
32+
* | value | field |
33+
* | ptr | hdr "foo" \0 |
34+
* +-------+------^-------+
35+
* |
36+
* |
37+
* entry pointer = field sds
38+
*/
39+
40+
/* The maximum allocation size we want to use for entries with embedded
41+
* values. */
42+
#define EMBED_VALUE_MAX_ALLOC_SIZE 128
43+
44+
/* SDS aux flag. If set, it indicates that the entry has TTL metadata set. */
45+
#define FIELD_SDS_AUX_BIT_ENTRY_HAS_TTL 0
46+
47+
/* SDS aux flag. If set, it indicates that the entry has an embedded value
48+
* pointer located in memory before the embedded field. If unset, the entry
49+
* instead has an embedded value located after the embedded field. */
50+
#define FIELD_SDS_AUX_BIT_ENTRY_HAS_VALUE_PTR 2
51+
52+
bool entryHasValuePtr(const entry *entry) {
53+
return sdsGetAuxBit(entry, FIELD_SDS_AUX_BIT_ENTRY_HAS_VALUE_PTR);
54+
}
55+
56+
bool entryHasExpiry(const entry *entry) {
57+
return sdsGetAuxBit(entry, FIELD_SDS_AUX_BIT_ENTRY_HAS_TTL);
58+
}
59+
60+
/* Returns the location of a pointer to a separately allocated value. Only for
61+
* an entry without an embedded value. */
62+
sds *entryGetValueRef(const entry *entry) {
63+
serverAssert(entryHasValuePtr(entry));
64+
char *field_data = sdsAllocPtr(entry);
65+
field_data -= sizeof(sds *);
66+
return (sds *)field_data;
67+
}
68+
69+
/* The entry pointer is the field sds, but that's an implementation detail. */
70+
sds entryGetField(const entry *entry) {
71+
return (sds)entry;
72+
}
73+
74+
sds entryGetValue(const entry *entry) {
75+
if (entryHasValuePtr(entry)) {
76+
return *entryGetValueRef(entry);
77+
} else {
78+
/* Skip field content, field null terminator and value sds8 hdr. */
79+
size_t offset = sdslen(entry) + 1 + sdsHdrSize(SDS_TYPE_8);
80+
serverAssert((char *)entry + offset);
81+
82+
return (char *)entry + offset;
83+
}
84+
}
85+
86+
entry *entrySetValue(entry *e, sds value) {
87+
if (entryHasValuePtr(e)) {
88+
sds *value_ref = entryGetValueRef(e);
89+
sdsfree(*value_ref);
90+
*value_ref = value;
91+
return e;
92+
} else {
93+
entry *new_entry = entryUpdate(e, value, entryGetExpiry(e));
94+
return new_entry;
95+
}
96+
}
97+
98+
/* Returns the address of the entry allocation. */
99+
void *entryAllocPtr(const entry *entry) {
100+
char *buf = sdsAllocPtr(entry);
101+
if (entryHasValuePtr(entry)) buf -= sizeof(sds *);
102+
if (entryHasExpiry(entry)) buf -= sizeof(long long);
103+
return buf;
104+
}
105+
106+
/**************************************** Entry Expiry API *****************************************/
107+
long long entryGetExpiry(const entry *entry) {
108+
long long expiry = EXPIRY_NONE;
109+
if (entryHasExpiry(entry)) {
110+
char *buf = sdsAllocPtr(entry);
111+
if (entryHasValuePtr(entry)) buf -= sizeof(sds *);
112+
buf -= sizeof(expiry);
113+
memcpy(&expiry, buf, sizeof(expiry));
114+
}
115+
return expiry;
116+
}
117+
118+
entry *entrySetExpiry(entry *e, long long expiry) {
119+
if (entryHasExpiry(e)) {
120+
char *buf = sdsAllocPtr(e);
121+
if (entryHasValuePtr(e)) buf -= sizeof(sds *);
122+
buf -= sizeof(expiry);
123+
memcpy(buf, &expiry, sizeof(expiry));
124+
return e;
125+
}
126+
entry *new_entry = entryUpdate(e, NULL, expiry);
127+
return new_entry;
128+
}
129+
130+
int entryIsExpired(entry *entry) {
131+
/* Don't expire anything while loading. It will be done later. */
132+
if (server.loading) return 0;
133+
if (!timestampIsExpired(entryGetExpiry(entry))) return 0;
134+
if (server.primary_host == NULL && server.import_mode) {
135+
if (server.current_client && server.current_client->flag.import_source) return 0;
136+
}
137+
return 1;
138+
}
139+
/**************************************** Entry Expiry API - End *****************************************/
140+
141+
void freeentry(entry *entry) {
142+
if (entryHasValuePtr(entry)) {
143+
sdsfree(*entryGetValueRef(entry));
144+
}
145+
zfree(entryAllocPtr(entry));
146+
}
147+
148+
/* takes ownership of value, does not take ownership of field */
149+
entry *entryCreate(sds field, sds value, long long expiry) {
150+
/* In case simple sds just return the same field we got. */
151+
if (!value && expiry == EXPIRY_NONE)
152+
return field;
153+
sds embedded_field_sds;
154+
size_t expiry_size = (expiry == EXPIRY_NONE) ? 0 : sizeof(long long);
155+
size_t field_len = sdslen(field);
156+
int field_sds_type = sdsReqType(field_len);
157+
if (field_sds_type == SDS_TYPE_5 && (expiry_size > 0)) {
158+
field_sds_type = SDS_TYPE_8;
159+
}
160+
size_t field_size = sdsReqSize(field_len, field_sds_type);
161+
size_t value_len = value ? sdslen(value) : 0;
162+
size_t value_size = value ? sdsReqSize(value_len, SDS_TYPE_8) : 0;
163+
size_t alloc_size = field_size + expiry_size;
164+
bool embed_value = false;
165+
if (value) {
166+
if (alloc_size + value_size <= EMBED_VALUE_MAX_ALLOC_SIZE) {
167+
/* Embed field and value. Value is fixed to SDS_TYPE_8. Unused
168+
* allocation space is recorded in the embedded value's SDS header.
169+
*
170+
* +------+--------------+---------------+
171+
* | TTL | field | value |
172+
* | | hdr "foo" \0 | hdr8 "bar" \0 |
173+
* +------+--------------+---------------+
174+
*/
175+
embed_value = true;
176+
alloc_size += value_size;
177+
} else {
178+
/* Embed field, but not value. Field must be >= SDS_TYPE_8 to encode to
179+
* indicate this type of entry.
180+
*
181+
* +------+-------+---------------+
182+
* | TTL | value | field |
183+
* | | ptr | hdr8 "foo" \0 |
184+
* +------+-------+---------------+
185+
*/
186+
embed_value = false;
187+
alloc_size += sizeof(sds *);
188+
if (field_sds_type == SDS_TYPE_5) {
189+
field_sds_type = SDS_TYPE_8;
190+
alloc_size -= field_size;
191+
field_size = sdsReqSize(field_len, field_sds_type);
192+
alloc_size += field_size;
193+
}
194+
}
195+
}
196+
/* allocate the buffer */
197+
size_t buf_size;
198+
char *buf = zmalloc_usable(alloc_size, &buf_size);
199+
200+
/* Set The expiry if exists */
201+
if (expiry_size) {
202+
memcpy(buf, &expiry, expiry_size);
203+
buf += expiry_size;
204+
buf_size -= expiry_size;
205+
}
206+
if (value) {
207+
if (!embed_value) {
208+
*(sds *)buf = value;
209+
buf += sizeof(sds *);
210+
buf_size -= sizeof(sds *);
211+
} else {
212+
sdswrite(buf + field_size, buf_size - field_size, SDS_TYPE_8, value, value_len);
213+
sdsfree(value);
214+
buf_size -= value_size;
215+
}
216+
}
217+
/* Set the field data */
218+
embedded_field_sds = sdswrite(buf, field_size, field_sds_type, field, field_len);
219+
220+
/* Field sds aux bits are zero, which we use for this entry encoding. */
221+
sdsSetAuxBit(embedded_field_sds, FIELD_SDS_AUX_BIT_ENTRY_HAS_VALUE_PTR, embed_value ? 0 : 1);
222+
sdsSetAuxBit(embedded_field_sds, FIELD_SDS_AUX_BIT_ENTRY_HAS_TTL, expiry_size > 0 ? 1 : 0);
223+
return (void *)embedded_field_sds;
224+
}
225+
226+
/* Frees previous value, takes ownership of new value, returns entry (may be
227+
* reallocated). */
228+
entry *entryUpdate(entry *e, sds value, long long expiry) {
229+
sds field = (sds)e;
230+
231+
bool update_value = value ? true : false;
232+
long long ttl = entryGetExpiry(e);
233+
bool update_expiry = (expiry != ttl) ? true : false;
234+
if (!update_value && !update_expiry)
235+
return e;
236+
if (update_expiry) ttl = expiry;
237+
value = update_value ? value : entryGetValue(e);
238+
size_t expiry_size = ttl != EXPIRY_NONE ? sizeof(ttl) : 0;
239+
int field_sds_type = sdsReqType(sdslen(field));
240+
if (field_sds_type == SDS_TYPE_5 && (expiry_size > 0)) {
241+
field_sds_type = SDS_TYPE_8;
242+
}
243+
size_t field_size = sdsHdrSize(field_sds_type) + sdsalloc(field) + 1;
244+
size_t value_len = value ? sdslen(value) : 0;
245+
size_t value_size = value ? sdsReqSize(value_len, SDS_TYPE_8) : 0;
246+
247+
size_t required_size = field_size + value_size + expiry_size;
248+
size_t current_allocation_size = entryMemUsage(e);
249+
bool create_new_entry = (update_expiry && (entryGetExpiry(e) == EXPIRY_NONE || ttl == EXPIRY_NONE)) ||
250+
!(update_value && !entryHasValuePtr(e) &&
251+
required_size <= EMBED_VALUE_MAX_ALLOC_SIZE &&
252+
required_size <= current_allocation_size &&
253+
required_size >= current_allocation_size * 3 / 4);
254+
255+
if (!create_new_entry) {
256+
/* In this case we are sure we do not have to allocate new entry, so expiry must already be set. */
257+
if (update_expiry) {
258+
serverAssert(entryHasExpiry(e));
259+
char *buf = sdsAllocPtr(e);
260+
if (entryHasValuePtr(e)) buf -= sizeof(sds *);
261+
buf -= sizeof(expiry);
262+
memcpy(buf, &expiry, sizeof(expiry));
263+
}
264+
/* In this case we are sure we do not have to allocate new entry, so value must already be set or we have enough room to embed it. */
265+
if (update_value) {
266+
if (entryHasValuePtr(e)) {
267+
sds *value_ref = entryGetValueRef(e);
268+
sdsfree(*value_ref);
269+
*value_ref = value;
270+
} else {
271+
/* Skip field content, field null terminator and value sds8 hdr. */
272+
sds old_value = entryGetValue(e);
273+
sdswrite(sdsAllocPtr(old_value), sdsAllocSize(old_value), SDS_TYPE_8, value, sdslen(value));
274+
sdsfree(value);
275+
}
276+
}
277+
return e;
278+
279+
} else {
280+
/* Check if the value can be reused. */
281+
int value_was_embedded = !entryHasValuePtr(e);
282+
/* In case the original entry value is embedded and we know the destination entry will be able to embed the value
283+
* We should duplicate the value.
284+
* TODO: We could basically optimize this case better by signaling entryCreate to take the value and avoid freeing it. */
285+
if (value_was_embedded && required_size <= EMBED_VALUE_MAX_ALLOC_SIZE)
286+
value = sdsdup(value);
287+
/* if not we have to duplicate it, remove it from the original entry since we are going to delete it.*/
288+
else if (!value_was_embedded) {
289+
sds *value_ref = entryGetValueRef(e);
290+
*value_ref = NULL;
291+
}
292+
}
293+
294+
entry *new_entry = entryCreate(entryGetField(e), value, ttl);
295+
if (new_entry != e)
296+
freeentry(e);
297+
return new_entry;
298+
}
299+
300+
/* Returns memory usage of a entry, including all allocations owned by
301+
* the entry. */
302+
size_t entryMemUsage(entry *entry) {
303+
size_t mem = 0;
304+
305+
if (entryHasValuePtr(entry)) {
306+
/* In case the value is not embedded we might not be able to sum all the allocation sizes since the field
307+
* header could be too small for holding the real allocation size. */
308+
mem += zmalloc_usable_size(entryAllocPtr(entry));
309+
} else {
310+
mem += sdsReqSize(sdslen(entry), sdsType(entry));
311+
if (entryHasExpiry(entry)) mem += sizeof(long long);
312+
}
313+
mem += sdsAllocSize(entryGetValue(entry));
314+
return mem;
315+
}
316+
317+
/* Defragments a hashtable entry (field-value pair) if needed, using the
318+
* provided defrag functions. The defrag functions return NULL if the allocation
319+
* was not moved, otherwise they return a pointer to the new memory location.
320+
* A separate sds defrag function is needed because of the unique memory layout
321+
* of sds strings.
322+
* If the location of the entry changed we return the new location,
323+
* otherwise we return NULL. */
324+
entry *entryDefrag(entry *entry, void *(*defragfn)(void *), sds (*sdsdefragfn)(sds)) {
325+
if (entryHasValuePtr(entry)) {
326+
sds *value_ref = entryGetValueRef(entry);
327+
sds new_value = sdsdefragfn(*value_ref);
328+
if (new_value) *value_ref = new_value;
329+
}
330+
char *allocation = entryAllocPtr(entry);
331+
char *new_allocation = defragfn(allocation);
332+
if (new_allocation != NULL) {
333+
/* Return the same offset into the new allocation as the entry's offset
334+
* in the old allocation. */
335+
return new_allocation + ((char *)entry - allocation);
336+
}
337+
return NULL;
338+
}
339+
340+
/* Used for releasing memory to OS to avoid unnecessary CoW. Called when we've
341+
* forked and memory won't be used again. See zmadvise_dontneed() */
342+
void dismissEntry(entry *entry) {
343+
/* Only dismiss values memory since the field size usually is small. */
344+
if (entryHasValuePtr(entry)) {
345+
dismissSds(*entryGetValueRef(entry));
346+
}
347+
}

src/entry.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#ifndef _VFIELD_H_
2+
#define _VFIELD_H_
3+
4+
#include "sds.h"
5+
#include <stdbool.h>
6+
7+
typedef void entry;
8+
9+
sds *entryGetValueRef(const entry *entry);
10+
sds entryGetField(const entry *entry);
11+
sds entryGetValue(const entry *entry);
12+
entry *entrySetValue(entry *entry, sds value);
13+
long long entryGetExpiry(const entry *entry);
14+
bool entryHasExpiry(const entry *entry);
15+
entry *entrySetExpiry(entry *entry, long long expiry);
16+
int entryIsExpired(entry *entry);
17+
18+
void entryFree(entry *entry);
19+
entry *entryCreate(sds field, sds value, long long expiry);
20+
entry *entryUpdate(entry *entry, sds value, long long expiry);
21+
size_t entryMemUsage(entry *entry);
22+
entry *entryDefrag(entry *entry, void *(*defragfn)(void *), sds (*sdsdefragfn)(sds));
23+
void dismissEntry(entry *entry);
24+
25+
#endif

0 commit comments

Comments
 (0)