The Perl Toolchain Summit 2025 Needs You: You can help 🙏 Learn more

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#if !defined(WIN32) || defined(CYGWIN)
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#endif
#ifdef DEBUG
#define ASSERT(x) assert(x)
#include <assert.h>
#else
#define ASSERT(x)
#endif
#ifndef WIN32
#include <sys/wait.h>
#else
#include <stdlib.h>
#include <stdio.h>
#include <windows.h>
double last_rand;
double drand48(void) {
last_rand = rand() / (double)(RAND_MAX+1);
ASSERT(last_rand < 1);
return last_rand;
}
#endif
#include <time.h>
#include "mmap_cache.h"
void * Get(mmap_cache * cache, void * key_ptr, int key_len, int * val_len) {
int found;
void * val_ptr, * val_rtn_ptr = 0;
MU32 hash_page, hash_slot, flags;
/* Hash key to get page and slot */
mmc_hash(cache, key_ptr, key_len, &hash_page, &hash_slot);
/* Get and lock the page */
mmc_lock(cache, hash_page);
/* Get value data pointer */
found = mmc_read(cache, hash_slot, key_ptr, key_len, &val_ptr, val_len, &flags);
/* If not found, use undef */
if (found == -1) {
} else {
/* Put into our own memory */
val_rtn_ptr = (void *)malloc(*val_len);
memcpy(val_rtn_ptr, val_ptr, *val_len);
}
mmc_unlock(cache);
return val_rtn_ptr;
}
void Set(mmap_cache * cache, void * key_ptr, int key_len, void * val_ptr, int val_len) {
MU32 hash_page, hash_slot, flags = 0, new_num_slots, ** expunge_items = 0;
int num_expunge;
/* Hash key to get page and slot */
mmc_hash(cache, key_ptr, key_len, &hash_page, &hash_slot);
/* Get and lock the page */
mmc_lock(cache, hash_page);
num_expunge = mmc_calc_expunge(cache, 2, key_len + val_len, &new_num_slots, &expunge_items);
if (expunge_items) {
mmc_do_expunge(cache, num_expunge, new_num_slots, expunge_items);
}
/* Get value data pointer */
mmc_write(cache, hash_slot, key_ptr, key_len, val_ptr, val_len, 60, flags);
mmc_unlock(cache);
}
char * rand_str(int nchar) {
unsigned char * buf = (unsigned char *)malloc(nchar + 1);
int i;
for (i = 0; i < nchar; i++) {
buf[i] = (char)(rand() % 26) + 'A';
}
buf[i] = 0;
return (char *)buf;
}
char buf[65537];
int BasicTests(mmap_cache * cache) {
int val_len, i;
void * val_ptr;
printf("Basic tests\n");
/* Test empty */
ASSERT(!Get(cache, "", 0, &val_len));
ASSERT(!Get(cache, " ", 0, &val_len));
for (i = 0; i < 65536; i++) { buf[i] = ' '; }
ASSERT(!Get(cache, buf, 1024, &val_len));
ASSERT(!Get(cache, buf, 65536, &val_len));
/* Test basic store/get on key sizes */
Set(cache, "", 0, "abc", 3);
ASSERT(!memcmp(val_ptr = Get(cache, "", 0, &val_len), "abc", 3) && val_len == 3);
free(val_ptr);
Set(cache, " ", 1, "def", 3);
ASSERT(!memcmp(val_ptr = Get(cache, " ", 1, &val_len), "def", 3) && val_len == 3);
free(val_ptr);
Set(cache, buf, 1024, "ghi", 3);
ASSERT(!memcmp(val_ptr = Get(cache, buf, 1024, &val_len), "ghi", 3) && val_len == 3);
free(val_ptr);
/* Bigger than page size - shouldn't work */
Set(cache, buf, 65536, "jkl", 3);
ASSERT(!Get(cache, buf, 65536, &val_len));
/* Test basic store/get on value sizes */
Set(cache, "abc", 3, "", 0);
ASSERT((val_ptr = Get(cache, "abc", 3, &val_len)) && val_len == 0);
free(val_ptr);
Set(cache, "def", 3, "x", 1);
ASSERT(!memcmp(val_ptr = Get(cache, "def", 3, &val_len), "x", 1) && val_len == 1);
free(val_ptr);
for (i = 0; i < 1024; i++) { buf[i] = 'y'; }
buf[0] = 'z'; buf[1023] = 'w';
Set(cache, "ghi", 3, buf, 1024);
ASSERT(!memcmp(val_ptr = Get(cache, "ghi", 3, &val_len), buf, 1024) && val_len == 1024);
free(val_ptr);
/* Bigger than page size - shouldn't work */
Set(cache, "jkl", 3, buf, 65536);
ASSERT(!Get(cache, "jkl", 3, &val_len));
return 0;
}
int LinearTests(mmap_cache * cache) {
int i, gl;
char * str1, * str2, * str3;
printf("Linear tests\n");
for (i = 0; i < 100000; i++) {
str1 = rand_str(10);
str2 = rand_str(10);
Set(cache, str1, strlen(str1)+1, str2, strlen(str2)+1);
str3 = Get(cache, str1, strlen(str1)+1, &gl);
ASSERT(strlen(str2)+1 == gl);
ASSERT(!memcmp(str2, str3, strlen(str2)+1));
free(str1);
free(str2);
free(str3);
if (i % 1000 == 0) {
printf("%d\n", i);
}
}
}
int EdgeTests() {
return 0;
}
typedef struct key_list {
int n_keys;
int buf_size;
char ** keys;
} key_list;
key_list * kl_new() {
key_list * kl = (key_list *)malloc(sizeof(key_list));
kl->buf_size = 8;
kl->keys = (char **)malloc(sizeof(char *) * kl->buf_size);
kl->n_keys = 0;
return kl;
}
void kl_push(key_list * kl, char * key) {
if (kl->n_keys < kl->buf_size) {
kl->keys[kl->n_keys++] = key;
return;
}
kl->buf_size *= 2;
kl->keys = (char **)realloc(kl->keys, sizeof(char *) * kl->buf_size);
kl->keys[kl->n_keys++] = key;
return;
}
void kl_free(key_list * kl) {
int i;
for (i = 0; i < kl->n_keys; i++) {
free(kl->keys[i]);
}
}
int urand_fh = 0;
void RandSeed() {
#ifdef WIN32
//randomize();
#else
char buf[8];
if (!urand_fh) {
urand_fh = open("/dev/urandom", O_RDONLY);
}
read(urand_fh, buf, 8);
srand48(*(long int *)buf);
#endif
}
int RepeatMixTests(mmap_cache * cache, double ratio, key_list * kl) {
int i, val_len;
int read = 0, read_hit = 0;
char valbuf[256];
printf("Repeat mix tests\n");
for (i = 0; i < 10000; i++) {
/* Read/write ratio */
if (drand48() < ratio) {
/* Pick a key from known written ones */
char * k = kl->keys[(int)(drand48() * kl->n_keys)];
void * v = Get(cache, k, strlen(k), &val_len);
read++;
/* Skip if not found in cache */
if (!v) { continue; }
read_hit++;
/* Offset of 10 past first chars of value are key */
memcpy(valbuf, v+10, strlen(k));
valbuf[strlen(k)] = '\0';
ASSERT(!memcmp(valbuf, k, strlen(k)));
free(v);
} else {
char * k = rand_str(10 + (int)(drand48() * 10));
char * v = rand_str(10);
char * ve = rand_str((int)(drand48() * 200));
strcpy(valbuf, v);
strcat(valbuf, k);
strcat(valbuf, ve);
kl_push(kl, k);
Set(cache, k, strlen(k), valbuf, strlen(valbuf));
free(ve);
free(v);
}
}
if (read) {
printf("Read hit pct: %5.3f\n", (double)read_hit/read);
}
return 1;
}
void IteratorTests(mmap_cache * cache) {
MU32 * entry_ptr;
void * key_ptr, * val_ptr;
int key_len, val_len;
MU32 last_access, expire_time, flags;
mmap_cache_it * it = mmc_iterate_new(cache);
printf("Iterator tests\n");
while ((entry_ptr = mmc_iterate_next(it))) {
mmc_get_details(cache, entry_ptr,
&key_ptr, &key_len, &val_ptr, &val_len,
&last_access, &expire_time, &flags);
ASSERT(key_len >= 10 && key_len <= 20);
ASSERT(val_len >= 20 && val_len <= 240);
ASSERT(last_access >= 1000000 && last_access <= time(0));
}
mmc_iterate_close(it);
}
int ForkTests(mmap_cache * cache, key_list * kl) {
#ifndef WIN32
int pid, j, k, kid, kids[20], nkids = 0, status;
struct timeval timeout = { 0, 1000 };
for (j = 0; j < 8; j++) {
if (!(pid = fork())) {
RandSeed();
RepeatMixTests(cache, 0.4, kl);
exit(0);
}
kids[nkids++] = pid;
select(0, 0, 0, 0, &timeout);
}
do {
kid = waitpid(-1, &status, 0);
for (j = 0, k = 0; j < nkids; j++) {
if (kids[j] != kid) { k++; }
kids[j] = kids[k];
}
nkids--;
} while (kid > 0 && nkids);
return 0;
#else
#endif
}
int main(int argc, char ** argv) {
int res;
key_list * kl;
mmap_cache * cache;
cache = mmc_new();
mmc_set_param(cache, "init_file", "1");
res = mmc_init(cache);
kl = kl_new();
BasicTests(cache);
LinearTests(cache);
mmc_close(cache);
cache = mmc_new();
mmc_set_param(cache, "init_file", "1");
res = mmc_init(cache);
RepeatMixTests(cache, 0.0, kl);
RepeatMixTests(cache, 0.5, kl);
RepeatMixTests(cache, 0.8, kl);
IteratorTests(cache);
ForkTests(cache, kl);
kl_free(kl);
mmc_close(cache);
cache = mmc_new();
mmc_set_param(cache, "init_file", "1");
mmc_set_param(cache, "page_size", "8192");
res = mmc_init(cache);
kl = kl_new();
BasicTests(cache);
RepeatMixTests(cache, 0.0, kl);
RepeatMixTests(cache, 0.5, kl);
RepeatMixTests(cache, 0.8, kl);
ForkTests(cache, kl);
kl_free(kl);
mmc_close(cache);
return 0;
}