comparison src/c/urweb.c @ 2279:32a407902d3b

Rewrite LRU cache. Now uses one big hash table and is less buggy.
author Ziv Scully <ziv@mit.edu>
date Wed, 11 Nov 2015 20:01:48 -0500
parents b49d22a4eda8
children 985c8016b592
comparison
equal deleted inserted replaced
2278:b7615e0ac4b0 2279:32a407902d3b
4530 } 4530 }
4531 4531
4532 4532
4533 // Sqlcache 4533 // Sqlcache
4534 4534
4535 void uw_Sqlcache_listDelete(uw_Sqlcache_CacheList *list, uw_Sqlcache_CacheEntry *entry) { 4535 void uw_Sqlcache_freeValue(uw_Sqlcache_Value *value) {
4536 if (list->first == entry) {
4537 list->first = entry->next;
4538 }
4539 if (list->last == entry) {
4540 list->last = entry->prev;
4541 }
4542 if (entry->prev) {
4543 entry->prev->next = entry->next;
4544 }
4545 if (entry->next) {
4546 entry->next->prev = entry->prev;
4547 }
4548 entry->prev = NULL;
4549 entry->next = NULL;
4550 --(list->size);
4551 }
4552
4553 void uw_Sqlcache_listAdd(uw_Sqlcache_CacheList *list, uw_Sqlcache_CacheEntry *entry) {
4554 if (list->last) {
4555 list->last->next = entry;
4556 entry->prev = list->last;
4557 list->last = entry;
4558 } else {
4559 list->first = entry;
4560 list->last = entry;
4561 }
4562 ++(list->size);
4563 }
4564
4565 void uw_Sqlcache_listBump(uw_Sqlcache_CacheList *list, uw_Sqlcache_CacheEntry *entry) {
4566 uw_Sqlcache_listDelete(list, entry);
4567 uw_Sqlcache_listAdd(list, entry);
4568 }
4569
4570 // TODO: deal with time properly.
4571
4572 time_t uw_Sqlcache_getTimeNow() {
4573 return time(NULL);
4574 }
4575
4576 time_t uw_Sqlcache_timeMax(time_t x, time_t y) {
4577 return difftime(x, y) > 0 ? x : y;
4578 }
4579
4580 void uw_Sqlcache_free(uw_Sqlcache_CacheValue *value) {
4581 if (value) { 4536 if (value) {
4582 free(value->result); 4537 free(value->result);
4583 free(value->output); 4538 free(value->output);
4584 free(value); 4539 free(value);
4585 } 4540 }
4586 } 4541 }
4587 4542
4588 void uw_Sqlcache_delete(uw_Sqlcache_Cache *cache, uw_Sqlcache_CacheEntry* entry) { 4543 void uw_Sqlcache_freeEntry(uw_Sqlcache_Entry* entry) {
4589 //uw_Sqlcache_listUw_Sqlcache_Delete(cache->lru, entry); 4544 if (entry) {
4590 HASH_DELETE(hh, cache->table, entry); 4545 free(entry->key);
4591 uw_Sqlcache_free(entry->value); 4546 uw_Sqlcache_freeValue(entry->value);
4592 free(entry->key); 4547 free(entry);
4593 free(entry); 4548 }
4594 } 4549 }
4595 4550
4596 uw_Sqlcache_CacheValue *uw_Sqlcache_checkHelper(uw_Sqlcache_Cache *cache, char **keys, int timeInvalid) { 4551 // TODO: pick a number.
4597 char *key = keys[cache->height]; 4552 unsigned int uw_Sqlcache_maxSize = 1234567890;
4598 uw_Sqlcache_CacheEntry *entry; 4553
4599 HASH_FIND(hh, cache->table, key, strlen(key), entry); 4554 void uw_Sqlcache_delete(uw_Sqlcache_Cache *cache, uw_Sqlcache_Entry *entry) {
4600 timeInvalid = uw_Sqlcache_timeMax(timeInvalid, cache->timeInvalid); 4555 HASH_DEL(cache->table, entry);
4601 if (entry && difftime(entry->timeValid, timeInvalid) > 0) { 4556 uw_Sqlcache_freeEntry(entry);
4602 if (cache->height == 0) { 4557 }
4603 // At height 0, entry->value is the desired value. 4558
4604 //uw_Sqlcache_listBump(cache->lru, entry); 4559 uw_Sqlcache_Entry *uw_Sqlcache_find(uw_Sqlcache_Cache *cache, char *key, size_t len, int bump) {
4605 return entry->value; 4560 uw_Sqlcache_Entry *entry = NULL;
4606 } else { 4561 HASH_FIND(hh, cache->table, key, len, entry);
4607 // At height n+1, entry->value is a pointer to a cache at heignt n. 4562 if (entry && bump) {
4608 return uw_Sqlcache_checkHelper(entry->value, keys, timeInvalid); 4563 // Bump for LRU purposes.
4609 } 4564 HASH_DEL(cache->table, entry);
4610 } else { 4565 // Important that we use [entry->key], because [key] might be ephemeral.
4611 return NULL; 4566 HASH_ADD_KEYPTR(hh, cache->table, entry->key, len, entry);
4612 } 4567 }
4613 } 4568 return entry;
4614 4569 }
4615 uw_Sqlcache_CacheValue *uw_Sqlcache_check(uw_Sqlcache_Cache *cache, char **keys) { 4570
4616 return uw_Sqlcache_checkHelper(cache, keys, 0); 4571 void uw_Sqlcache_add(uw_Sqlcache_Cache *cache, uw_Sqlcache_Entry *entry, size_t len) {
4617 } 4572 HASH_ADD_KEYPTR(hh, cache->table, entry->key, len, entry);
4618 4573 if (HASH_COUNT(cache->table) > uw_Sqlcache_maxSize) {
4619 void uw_Sqlcache_storeHelper(uw_Sqlcache_Cache *cache, char **keys, uw_Sqlcache_CacheValue *value, int timeNow) { 4574 // Deletes the first element of the cache.
4620 uw_Sqlcache_CacheEntry *entry; 4575 uw_Sqlcache_delete(cache, cache->table);
4621 char *key = keys[cache->height]; 4576 }
4622 HASH_FIND(hh, cache->table, key, strlen(key), entry); 4577 }
4623 if (!entry) { 4578
4624 entry = malloc(sizeof(uw_Sqlcache_CacheEntry)); 4579 unsigned long uw_Sqlcache_getTimeNow(uw_Sqlcache_Cache *cache) {
4625 entry->key = strdup(key); 4580 return ++cache->timeNow;
4626 entry->value = NULL; 4581 }
4627 HASH_ADD_KEYPTR(hh, cache->table, entry->key, strlen(entry->key), entry); 4582
4628 } 4583 unsigned long uw_Sqlcache_timeMax(unsigned long x, unsigned long y) {
4629 entry->timeValid = timeNow; 4584 return x > y ? x : y;
4630 if (cache->height == 0) { 4585 }
4631 //uw_Sqlcache_listAdd(cache->lru, entry); 4586
4632 uw_Sqlcache_free(entry->value); 4587 char uw_Sqlcache_keySep = '_';
4633 entry->value = value; 4588
4634 //if (cache->lru->size > MAX_SIZE) { 4589 char *uw_Sqlcache_allocKeyBuffer(char **keys, int numKeys) {
4635 //uw_Sqlcache_delete(cache, cache->lru->first); 4590 size_t len = 0;
4636 // TODO: return flushed value. 4591 while (numKeys-- > 0) {
4637 //} 4592 char* k = keys[numKeys];
4638 } else { 4593 if (!k) {
4639 if (!entry->value) { 4594 // Can only happen when flushihg, in which case we don't need anything past the null key.
4640 uw_Sqlcache_Cache *newuw_Sqlcache_Cache = malloc(sizeof(uw_Sqlcache_Cache)); 4595 break;
4641 newuw_Sqlcache_Cache->table = NULL; 4596 }
4642 newuw_Sqlcache_Cache->timeInvalid = timeNow; 4597 // Leave room for separator.
4643 newuw_Sqlcache_Cache->lru = cache->lru; 4598 len += 1 + strlen(k);
4644 newuw_Sqlcache_Cache->height = cache->height - 1; 4599 }
4645 entry->value = newuw_Sqlcache_Cache; 4600 char *buf = malloc(len+1);
4646 } 4601 // If nothing is copied into the buffer, it should look like it has length 0.
4647 uw_Sqlcache_storeHelper(entry->value, keys, value, timeNow); 4602 buf[0] = 0;
4648 } 4603 return buf;
4649 } 4604 }
4650 4605
4651 void uw_Sqlcache_store(uw_Sqlcache_Cache *cache, char **keys, uw_Sqlcache_CacheValue *value) { 4606 char *uw_Sqlcache_keyCopy(char *buf, char *key) {
4652 uw_Sqlcache_storeHelper(cache, keys, value, uw_Sqlcache_getTimeNow()); 4607 *buf++ = uw_Sqlcache_keySep;
4653 } 4608 return stpcpy(buf, key);
4654 4609 }
4655 void uw_Sqlcache_flushHelper(uw_Sqlcache_Cache *cache, char **keys, int timeNow) { 4610
4656 uw_Sqlcache_CacheEntry *entry; 4611 // The NUL-terminated prefix of [key] below always looks something like "_k1_k2_k3..._kn".
4657 char *key = keys[cache->height]; 4612 // TODO: strlen(key) = buf - key?
4658 if (key) { 4613
4659 HASH_FIND(hh, cache->table, key, strlen(key), entry); 4614 uw_Sqlcache_Value *uw_Sqlcache_check(uw_Sqlcache_Cache *cache, char **keys, int numKeys) {
4660 if (entry) { 4615 char *key = uw_Sqlcache_allocKeyBuffer(keys, numKeys);
4661 if (cache->height == 0) { 4616 char *buf = key;
4662 uw_Sqlcache_delete(cache, entry); 4617 time_t timeInvalid = cache->timeInvalid;
4618 uw_Sqlcache_Entry *entry;
4619 while (numKeys-- > 0) {
4620 buf = uw_Sqlcache_keyCopy(buf, keys[numKeys]);
4621 size_t len = buf - key;
4622 entry = uw_Sqlcache_find(cache, key, len, 1);
4623 if (!entry) {
4624 free(key);
4625 return NULL;
4626 }
4627 timeInvalid = uw_Sqlcache_timeMax(timeInvalid, entry->timeInvalid);
4628 }
4629 free(key);
4630 uw_Sqlcache_Value *value = entry->value;
4631 return value && value->timeValid > timeInvalid ? value : NULL;
4632 }
4633
4634 void uw_Sqlcache_store(uw_Sqlcache_Cache *cache, char **keys, int numKeys, uw_Sqlcache_Value *value) {
4635 char *key = uw_Sqlcache_allocKeyBuffer(keys, numKeys);
4636 char *buf = key;
4637 time_t timeNow = uw_Sqlcache_getTimeNow(cache);
4638 uw_Sqlcache_Entry *entry;
4639 while (numKeys-- > 0) {
4640 buf = uw_Sqlcache_keyCopy(buf, keys[numKeys]);
4641 size_t len = buf - key;
4642 entry = uw_Sqlcache_find(cache, key, len, 1);
4643 if (!entry) {
4644 entry = malloc(sizeof(uw_Sqlcache_Entry));
4645 entry->key = strdup(key);
4646 entry->value = NULL;
4647 entry->timeInvalid = 0; // ASK: is this okay?
4648 uw_Sqlcache_add(cache, entry, len);
4649 }
4650 }
4651 free(key);
4652 uw_Sqlcache_freeValue(entry->value);
4653 entry->value = value;
4654 entry->value->timeValid = timeNow;
4655 }
4656
4657 void uw_Sqlcache_flush(uw_Sqlcache_Cache *cache, char **keys, int numKeys) {
4658 char *key = uw_Sqlcache_allocKeyBuffer(keys, numKeys);
4659 char *buf = key;
4660 time_t timeNow = uw_Sqlcache_getTimeNow(cache);
4661 uw_Sqlcache_Entry *entry;
4662 while (numKeys-- > 0) {
4663 char *k = keys[numKeys];
4664 if (!k) {
4665 if (entry) {
4666 entry->timeInvalid = timeNow;
4663 } else { 4667 } else {
4664 uw_Sqlcache_flushHelper(entry->value, keys, timeNow); 4668 // Haven't found an entry yet, so the first key was null.
4669 cache->timeInvalid = timeNow;
4665 } 4670 }
4666 } 4671 free(key);
4667 } else { 4672 return;
4668 // Null key means invalidate the entire subtree. 4673 }
4669 cache->timeInvalid = timeNow; 4674 buf = uw_Sqlcache_keyCopy(buf, k);
4670 } 4675 size_t len = buf - key;
4671 } 4676 entry = uw_Sqlcache_find(cache, key, len, 0);
4672 4677 if (!entry) {
4673 void uw_Sqlcache_flush(uw_Sqlcache_Cache *cache, char **keys) { 4678 free(key);
4674 uw_Sqlcache_flushHelper(cache, keys, uw_Sqlcache_getTimeNow()); 4679 return;
4675 } 4680 }
4681 }
4682 free(key);
4683 // All the keys were non-null and the relevant entry is present, so we delete it.
4684 uw_Sqlcache_delete(cache, entry);
4685 }