Mercurial > urweb
comparison src/c/urweb.c @ 2288:98f96a976ede
Finish locking, but it's not yet tested rigorously.
author | Ziv Scully <ziv@mit.edu> |
---|---|
date | Fri, 13 Nov 2015 11:03:09 -0500 |
parents | 08203f93dbc3 |
children | 50ad02829abd |
comparison
equal
deleted
inserted
replaced
2287:08203f93dbc3 | 2288:98f96a976ede |
---|---|
364 void uw_global_init() { | 364 void uw_global_init() { |
365 clients = malloc(0); | 365 clients = malloc(0); |
366 | 366 |
367 uw_global_custom(); | 367 uw_global_custom(); |
368 uw_init_crypto(); | 368 uw_init_crypto(); |
369 | |
370 // Fast non-cryptographic strength randomness for Sqlcache. | |
371 srandom(clock()); | |
369 } | 372 } |
370 | 373 |
371 void uw_app_init(uw_app *app) { | 374 void uw_app_init(uw_app *app) { |
372 app->client_init(); | 375 app->client_init(); |
373 } | 376 } |
429 char **keys; | 432 char **keys; |
430 uw_Sqlcache_Value *value; | 433 uw_Sqlcache_Value *value; |
431 struct uw_Sqlcache_Update *next; | 434 struct uw_Sqlcache_Update *next; |
432 } uw_Sqlcache_Update; | 435 } uw_Sqlcache_Update; |
433 | 436 |
437 typedef struct uw_Sqlcache_Unlock { | |
438 pthread_rwlock_t *lock; | |
439 struct uw_Sqlcache_Unlock *next; | |
440 } uw_Sqlcache_Unlock; | |
441 | |
434 struct uw_context { | 442 struct uw_context { |
435 uw_app *app; | 443 uw_app *app; |
436 int id; | 444 int id; |
437 | 445 |
438 char *(*get_header)(void *, const char *); | 446 char *(*get_header)(void *, const char *); |
498 // Sqlcache. | 506 // Sqlcache. |
499 int numRecording; | 507 int numRecording; |
500 int recordingOffset; | 508 int recordingOffset; |
501 uw_Sqlcache_Update *cacheUpdate; | 509 uw_Sqlcache_Update *cacheUpdate; |
502 uw_Sqlcache_Update *cacheUpdateTail; | 510 uw_Sqlcache_Update *cacheUpdateTail; |
511 uw_Sqlcache_Unlock *cacheUnlock; | |
503 | 512 |
504 int remoteSock; | 513 int remoteSock; |
505 }; | 514 }; |
506 | 515 |
507 size_t uw_headers_max = SIZE_MAX; | 516 size_t uw_headers_max = SIZE_MAX; |
4554 uw_Sqlcache_Value *value; | 4563 uw_Sqlcache_Value *value; |
4555 unsigned long timeInvalid; | 4564 unsigned long timeInvalid; |
4556 UT_hash_handle hh; | 4565 UT_hash_handle hh; |
4557 } uw_Sqlcache_Entry; | 4566 } uw_Sqlcache_Entry; |
4558 | 4567 |
4559 void uw_Sqlcache_freeValue(uw_Sqlcache_Value *value) { | 4568 static void uw_Sqlcache_freeValue(uw_Sqlcache_Value *value) { |
4560 if (value) { | 4569 if (value) { |
4561 free(value->result); | 4570 free(value->result); |
4562 free(value->output); | 4571 free(value->output); |
4563 free(value); | 4572 free(value); |
4564 } | 4573 } |
4565 } | 4574 } |
4566 | 4575 |
4567 void uw_Sqlcache_freeEntry(uw_Sqlcache_Entry* entry) { | 4576 static void uw_Sqlcache_freeEntry(uw_Sqlcache_Entry* entry) { |
4568 if (entry) { | 4577 if (entry) { |
4569 free(entry->key); | 4578 free(entry->key); |
4570 uw_Sqlcache_freeValue(entry->value); | 4579 uw_Sqlcache_freeValue(entry->value); |
4571 free(entry); | 4580 free(entry); |
4572 } | 4581 } |
4573 } | 4582 } |
4574 | 4583 |
4575 // TODO: pick a number. | 4584 // TODO: pick a number. |
4576 unsigned int uw_Sqlcache_maxSize = 1234567890; | 4585 static unsigned int uw_Sqlcache_maxSize = 1234567890; |
4577 | 4586 |
4578 void uw_Sqlcache_delete(uw_Sqlcache_Cache *cache, uw_Sqlcache_Entry *entry) { | 4587 static void uw_Sqlcache_delete(uw_Sqlcache_Cache *cache, uw_Sqlcache_Entry *entry) { |
4579 HASH_DEL(cache->table, entry); | 4588 HASH_DEL(cache->table, entry); |
4580 uw_Sqlcache_freeEntry(entry); | 4589 uw_Sqlcache_freeEntry(entry); |
4581 } | 4590 } |
4582 | 4591 |
4583 uw_Sqlcache_Entry *uw_Sqlcache_find(uw_Sqlcache_Cache *cache, char *key, size_t len, int bump) { | 4592 static uw_Sqlcache_Entry *uw_Sqlcache_find(uw_Sqlcache_Cache *cache, char *key, size_t len, int bump) { |
4584 uw_Sqlcache_Entry *entry = NULL; | 4593 uw_Sqlcache_Entry *entry = NULL; |
4585 HASH_FIND(hh, cache->table, key, len, entry); | 4594 HASH_FIND(hh, cache->table, key, len, entry); |
4586 if (entry && bump) { | 4595 if (entry && bump) { |
4587 // Bump for LRU purposes. | 4596 // Bump for LRU purposes. |
4588 HASH_DEL(cache->table, entry); | 4597 HASH_DEL(cache->table, entry); |
4590 HASH_ADD_KEYPTR(hh, cache->table, entry->key, len, entry); | 4599 HASH_ADD_KEYPTR(hh, cache->table, entry->key, len, entry); |
4591 } | 4600 } |
4592 return entry; | 4601 return entry; |
4593 } | 4602 } |
4594 | 4603 |
4595 void uw_Sqlcache_add(uw_Sqlcache_Cache *cache, uw_Sqlcache_Entry *entry, size_t len) { | 4604 static void uw_Sqlcache_add(uw_Sqlcache_Cache *cache, uw_Sqlcache_Entry *entry, size_t len) { |
4596 HASH_ADD_KEYPTR(hh, cache->table, entry->key, len, entry); | 4605 HASH_ADD_KEYPTR(hh, cache->table, entry->key, len, entry); |
4597 if (HASH_COUNT(cache->table) > uw_Sqlcache_maxSize) { | 4606 if (HASH_COUNT(cache->table) > uw_Sqlcache_maxSize) { |
4598 // Deletes the first element of the cache. | 4607 // Deletes the first element of the cache. |
4599 uw_Sqlcache_delete(cache, cache->table); | 4608 uw_Sqlcache_delete(cache, cache->table); |
4600 } | 4609 } |
4601 } | 4610 } |
4602 | 4611 |
4603 unsigned long uw_Sqlcache_getTimeNow(uw_Sqlcache_Cache *cache) { | 4612 static unsigned long uw_Sqlcache_getTimeNow(uw_Sqlcache_Cache *cache) { |
4604 return ++cache->timeNow; | 4613 return ++cache->timeNow; |
4605 } | 4614 } |
4606 | 4615 |
4607 unsigned long uw_Sqlcache_timeMax(unsigned long x, unsigned long y) { | 4616 static unsigned long uw_Sqlcache_timeMax(unsigned long x, unsigned long y) { |
4608 return x > y ? x : y; | 4617 return x > y ? x : y; |
4609 } | 4618 } |
4610 | 4619 |
4611 char uw_Sqlcache_keySep = '_'; | 4620 static char uw_Sqlcache_keySep = '_'; |
4612 | 4621 |
4613 char *uw_Sqlcache_allocKeyBuffer(char **keys, size_t numKeys) { | 4622 static char *uw_Sqlcache_allocKeyBuffer(char **keys, size_t numKeys) { |
4614 size_t len = 0; | 4623 size_t len = 0; |
4615 while (numKeys-- > 0) { | 4624 while (numKeys-- > 0) { |
4616 char* k = keys[numKeys]; | 4625 char* k = keys[numKeys]; |
4617 if (!k) { | 4626 if (!k) { |
4618 // Can only happen when flushihg, in which case we don't need anything past the null key. | 4627 // Can only happen when flushihg, in which case we don't need anything past the null key. |
4625 // If nothing is copied into the buffer, it should look like it has length 0. | 4634 // If nothing is copied into the buffer, it should look like it has length 0. |
4626 buf[0] = 0; | 4635 buf[0] = 0; |
4627 return buf; | 4636 return buf; |
4628 } | 4637 } |
4629 | 4638 |
4630 char *uw_Sqlcache_keyCopy(char *buf, char *key) { | 4639 static char *uw_Sqlcache_keyCopy(char *buf, char *key) { |
4631 *buf++ = uw_Sqlcache_keySep; | 4640 *buf++ = uw_Sqlcache_keySep; |
4632 return stpcpy(buf, key); | 4641 return stpcpy(buf, key); |
4633 } | 4642 } |
4634 | 4643 |
4635 // The NUL-terminated prefix of [key] below always looks something like "_k1_k2_k3..._kn". | 4644 // The NUL-terminated prefix of [key] below always looks something like "_k1_k2_k3..._kn". |
4636 | 4645 |
4637 uw_Sqlcache_Value *uw_Sqlcache_check(uw_context ctx, uw_Sqlcache_Cache *cache, char **keys) { | 4646 uw_Sqlcache_Value *uw_Sqlcache_check(uw_context ctx, uw_Sqlcache_Cache *cache, char **keys) { |
4638 pthread_rwlock_rdlock(&cache->lock); | 4647 int doBump = random() % 1024 == 0; |
4648 if (doBump) { | |
4649 pthread_rwlock_wrlock(&cache->lockIn); | |
4650 } else { | |
4651 pthread_rwlock_rdlock(&cache->lockIn); | |
4652 } | |
4639 size_t numKeys = cache->numKeys; | 4653 size_t numKeys = cache->numKeys; |
4640 char *key = uw_Sqlcache_allocKeyBuffer(keys, numKeys); | 4654 char *key = uw_Sqlcache_allocKeyBuffer(keys, numKeys); |
4641 char *buf = key; | 4655 char *buf = key; |
4642 time_t timeInvalid = cache->timeInvalid; | 4656 time_t timeInvalid = cache->timeInvalid; |
4643 uw_Sqlcache_Entry *entry; | 4657 uw_Sqlcache_Entry *entry; |
4644 if (numKeys == 0) { | 4658 if (numKeys == 0) { |
4645 entry = cache->table; | 4659 entry = cache->table; |
4646 if (!entry) { | 4660 if (!entry) { |
4647 free(key); | 4661 free(key); |
4648 pthread_rwlock_unlock(&cache->lock); | 4662 pthread_rwlock_unlock(&cache->lockIn); |
4649 return NULL; | 4663 return NULL; |
4650 } | 4664 } |
4651 } else { | 4665 } else { |
4652 while (numKeys-- > 0) { | 4666 while (numKeys-- > 0) { |
4653 buf = uw_Sqlcache_keyCopy(buf, keys[numKeys]); | 4667 buf = uw_Sqlcache_keyCopy(buf, keys[numKeys]); |
4654 size_t len = buf - key; | 4668 size_t len = buf - key; |
4655 entry = uw_Sqlcache_find(cache, key, len, 1); | 4669 entry = uw_Sqlcache_find(cache, key, len, doBump); |
4656 if (!entry) { | 4670 if (!entry) { |
4657 free(key); | 4671 free(key); |
4658 pthread_rwlock_unlock(&cache->lock); | 4672 pthread_rwlock_unlock(&cache->lockIn); |
4659 return NULL; | 4673 return NULL; |
4660 } | 4674 } |
4661 timeInvalid = uw_Sqlcache_timeMax(timeInvalid, entry->timeInvalid); | 4675 timeInvalid = uw_Sqlcache_timeMax(timeInvalid, entry->timeInvalid); |
4662 } | 4676 } |
4663 free(key); | 4677 free(key); |
4664 } | 4678 } |
4665 // TODO: pass back copy of value and free it in the generated code... or use uw_malloc? | |
4666 uw_Sqlcache_Value *value = entry->value; | 4679 uw_Sqlcache_Value *value = entry->value; |
4667 pthread_rwlock_unlock(&cache->lock); | 4680 pthread_rwlock_unlock(&cache->lockIn); |
4681 // ASK: though the argument isn't trivial, this is safe, right? | |
4682 // Returning outside the lock is safe because updates happen at commit time. | |
4683 // Those are the only times the returned value or its strings can get freed. | |
4684 // Handler output is a new string, so it's safe to free this at commit time. | |
4668 return value && value->timeValid > timeInvalid ? value : NULL; | 4685 return value && value->timeValid > timeInvalid ? value : NULL; |
4669 } | 4686 } |
4670 | 4687 |
4671 void uw_Sqlcache_storeCommitOne(uw_Sqlcache_Cache *cache, char **keys, uw_Sqlcache_Value *value) { | 4688 static void uw_Sqlcache_storeCommitOne(uw_Sqlcache_Cache *cache, char **keys, uw_Sqlcache_Value *value) { |
4672 pthread_rwlock_wrlock(&cache->lock); | 4689 pthread_rwlock_wrlock(&cache->lockIn); |
4673 size_t numKeys = cache->numKeys; | 4690 size_t numKeys = cache->numKeys; |
4674 char *key = uw_Sqlcache_allocKeyBuffer(keys, numKeys); | |
4675 char *buf = key; | |
4676 time_t timeNow = uw_Sqlcache_getTimeNow(cache); | 4691 time_t timeNow = uw_Sqlcache_getTimeNow(cache); |
4677 uw_Sqlcache_Entry *entry; | 4692 uw_Sqlcache_Entry *entry; |
4678 if (numKeys == 0) { | 4693 if (numKeys == 0) { |
4679 entry = cache->table; | 4694 entry = cache->table; |
4680 if (!entry) { | 4695 if (!entry) { |
4681 entry = malloc(sizeof(uw_Sqlcache_Entry)); | 4696 entry = malloc(sizeof(uw_Sqlcache_Entry)); |
4682 entry->key = strdup(key); | 4697 entry->key = NULL; |
4683 entry->value = NULL; | 4698 entry->value = NULL; |
4684 entry->timeInvalid = 0; | 4699 entry->timeInvalid = 0; |
4685 cache->table = entry; | 4700 cache->table = entry; |
4686 } | 4701 } |
4687 } else { | 4702 } else { |
4703 char *key = uw_Sqlcache_allocKeyBuffer(keys, numKeys); | |
4704 char *buf = key; | |
4688 while (numKeys-- > 0) { | 4705 while (numKeys-- > 0) { |
4689 buf = uw_Sqlcache_keyCopy(buf, keys[numKeys]); | 4706 buf = uw_Sqlcache_keyCopy(buf, keys[numKeys]); |
4690 size_t len = buf - key; | 4707 size_t len = buf - key; |
4691 entry = uw_Sqlcache_find(cache, key, len, 1); | 4708 entry = uw_Sqlcache_find(cache, key, len, 1); |
4692 if (!entry) { | 4709 if (!entry) { |
4700 free(key); | 4717 free(key); |
4701 } | 4718 } |
4702 uw_Sqlcache_freeValue(entry->value); | 4719 uw_Sqlcache_freeValue(entry->value); |
4703 entry->value = value; | 4720 entry->value = value; |
4704 entry->value->timeValid = timeNow; | 4721 entry->value->timeValid = timeNow; |
4705 pthread_rwlock_unlock(&cache->lock); | 4722 pthread_rwlock_unlock(&cache->lockIn); |
4706 } | 4723 } |
4707 | 4724 |
4708 void uw_Sqlcache_flushCommitOne(uw_Sqlcache_Cache *cache, char **keys) { | 4725 static void uw_Sqlcache_flushCommitOne(uw_Sqlcache_Cache *cache, char **keys) { |
4709 pthread_rwlock_wrlock(&cache->lock); | 4726 pthread_rwlock_wrlock(&cache->lockIn); |
4710 size_t numKeys = cache->numKeys; | 4727 size_t numKeys = cache->numKeys; |
4711 char *key = uw_Sqlcache_allocKeyBuffer(keys, numKeys); | |
4712 char *buf = key; | |
4713 time_t timeNow = uw_Sqlcache_getTimeNow(cache); | |
4714 uw_Sqlcache_Entry *entry; | |
4715 if (numKeys == 0) { | 4728 if (numKeys == 0) { |
4716 entry = cache->table; | 4729 uw_Sqlcache_Entry *entry = cache->table; |
4717 if (entry) { | 4730 if (entry) { |
4718 uw_Sqlcache_freeValue(entry->value); | 4731 uw_Sqlcache_freeValue(entry->value); |
4719 entry->value = NULL; | 4732 entry->value = NULL; |
4720 } | 4733 } |
4721 } else { | 4734 } else { |
4735 char *key = uw_Sqlcache_allocKeyBuffer(keys, numKeys); | |
4736 char *buf = key; | |
4737 time_t timeNow = uw_Sqlcache_getTimeNow(cache); | |
4738 uw_Sqlcache_Entry *entry = NULL; | |
4722 while (numKeys-- > 0) { | 4739 while (numKeys-- > 0) { |
4723 char *k = keys[numKeys]; | 4740 char *k = keys[numKeys]; |
4724 if (!k) { | 4741 if (!k) { |
4725 if (entry) { | 4742 if (entry) { |
4726 entry->timeInvalid = timeNow; | 4743 entry->timeInvalid = timeNow; |
4727 } else { | 4744 } else { |
4728 // Haven't found an entry yet, so the first key was null. | 4745 // Haven't found an entry yet, so the first key was null. |
4729 cache->timeInvalid = timeNow; | 4746 cache->timeInvalid = timeNow; |
4730 } | 4747 } |
4731 free(key); | 4748 free(key); |
4732 pthread_rwlock_unlock(&cache->lock); | 4749 pthread_rwlock_unlock(&cache->lockIn); |
4733 return; | 4750 return; |
4734 } | 4751 } |
4735 buf = uw_Sqlcache_keyCopy(buf, k); | 4752 buf = uw_Sqlcache_keyCopy(buf, k); |
4736 size_t len = buf - key; | 4753 size_t len = buf - key; |
4737 entry = uw_Sqlcache_find(cache, key, len, 0); | 4754 entry = uw_Sqlcache_find(cache, key, len, 0); |
4738 if (!entry) { | 4755 if (!entry) { |
4756 // Nothing in the cache to flush. | |
4739 free(key); | 4757 free(key); |
4740 pthread_rwlock_unlock(&cache->lock); | 4758 pthread_rwlock_unlock(&cache->lockIn); |
4741 return; | 4759 return; |
4742 } | 4760 } |
4743 } | 4761 } |
4744 free(key); | 4762 free(key); |
4745 // All the keys were non-null and the relevant entry is present, so we delete it. | 4763 // All the keys were non-null and the relevant entry is present, so we delete it. |
4746 uw_Sqlcache_delete(cache, entry); | 4764 uw_Sqlcache_delete(cache, entry); |
4747 } | 4765 } |
4748 pthread_rwlock_unlock(&cache->lock); | 4766 pthread_rwlock_unlock(&cache->lockIn); |
4749 } | 4767 } |
4750 | 4768 |
4751 void uw_Sqlcache_freeUpdate(void *data, int dontCare) { | 4769 static void uw_Sqlcache_commit(void *data) { |
4752 uw_context ctx = (uw_context)data; | |
4753 uw_Sqlcache_Update *update = ctx->cacheUpdate; | |
4754 while (update) { | |
4755 char** keys = update->keys; | |
4756 size_t numKeys = update->cache->numKeys; | |
4757 while (numKeys-- > 0) { | |
4758 free(keys[numKeys]); | |
4759 } | |
4760 free(keys); | |
4761 // Don't free [update->value]: it's in the cache now! | |
4762 uw_Sqlcache_Update *nextUpdate = update->next; | |
4763 free(update); | |
4764 update = nextUpdate; | |
4765 } | |
4766 ctx->cacheUpdate = NULL; | |
4767 ctx->cacheUpdateTail = NULL; | |
4768 } | |
4769 | |
4770 void uw_Sqlcache_commitUpdate(void *data) { | |
4771 uw_context ctx = (uw_context)data; | 4770 uw_context ctx = (uw_context)data; |
4772 uw_Sqlcache_Update *update = ctx->cacheUpdate; | 4771 uw_Sqlcache_Update *update = ctx->cacheUpdate; |
4773 while (update) { | 4772 while (update) { |
4774 uw_Sqlcache_Cache *cache = update->cache; | 4773 uw_Sqlcache_Cache *cache = update->cache; |
4775 char **keys = update->keys; | 4774 char **keys = update->keys; |
4780 } | 4779 } |
4781 update = update->next; | 4780 update = update->next; |
4782 } | 4781 } |
4783 } | 4782 } |
4784 | 4783 |
4785 char **uw_Sqlcache_copyKeys(char **keys, size_t numKeys) { | 4784 static void uw_Sqlcache_free(void *data, int dontCare) { |
4785 uw_context ctx = (uw_context)data; | |
4786 uw_Sqlcache_Update *update = ctx->cacheUpdate; | |
4787 while (update) { | |
4788 char** keys = update->keys; | |
4789 size_t numKeys = update->cache->numKeys; | |
4790 while (numKeys-- > 0) { | |
4791 free(keys[numKeys]); | |
4792 } | |
4793 free(keys); | |
4794 // Don't free [update->value]: it's in the cache now! | |
4795 uw_Sqlcache_Update *nextUpdate = update->next; | |
4796 free(update); | |
4797 update = nextUpdate; | |
4798 } | |
4799 ctx->cacheUpdate = NULL; | |
4800 ctx->cacheUpdateTail = NULL; | |
4801 uw_Sqlcache_Unlock *unlock = ctx->cacheUnlock; | |
4802 while (unlock) { | |
4803 pthread_rwlock_unlock(unlock->lock); | |
4804 uw_Sqlcache_Unlock *nextUnlock = unlock->next; | |
4805 free(unlock); | |
4806 unlock = nextUnlock; | |
4807 } | |
4808 ctx->cacheUnlock = NULL; | |
4809 } | |
4810 | |
4811 static void uw_Sqlcache_pushUnlock(uw_context ctx, pthread_rwlock_t *lock) { | |
4812 if (!ctx->cacheUnlock) { | |
4813 // Just need one registered commit for both updating and unlocking. | |
4814 uw_register_transactional(ctx, ctx, uw_Sqlcache_commit, NULL, uw_Sqlcache_free); | |
4815 } | |
4816 uw_Sqlcache_Unlock *unlock = malloc(sizeof(uw_Sqlcache_Unlock)); | |
4817 unlock->lock = lock; | |
4818 unlock->next = ctx->cacheUnlock; | |
4819 ctx->cacheUnlock = unlock; | |
4820 } | |
4821 | |
4822 void uw_Sqlcache_rlock(uw_context ctx, uw_Sqlcache_Cache *cache) { | |
4823 pthread_rwlock_rdlock(&cache->lockOut); | |
4824 uw_Sqlcache_pushUnlock(ctx, &cache->lockOut); | |
4825 } | |
4826 | |
4827 void uw_Sqlcache_wlock(uw_context ctx, uw_Sqlcache_Cache *cache) { | |
4828 pthread_rwlock_wrlock(&cache->lockOut); | |
4829 uw_Sqlcache_pushUnlock(ctx, &cache->lockOut); | |
4830 } | |
4831 | |
4832 static char **uw_Sqlcache_copyKeys(char **keys, size_t numKeys) { | |
4786 char **copy = malloc(sizeof(char *) * numKeys); | 4833 char **copy = malloc(sizeof(char *) * numKeys); |
4787 while (numKeys-- > 0) { | 4834 while (numKeys-- > 0) { |
4788 char *k = keys[numKeys]; | 4835 char *k = keys[numKeys]; |
4789 copy[numKeys] = k ? strdup(k) : NULL; | 4836 copy[numKeys] = k ? strdup(k) : NULL; |
4790 } | 4837 } |
4796 update->cache = cache; | 4843 update->cache = cache; |
4797 update->keys = uw_Sqlcache_copyKeys(keys, cache->numKeys); | 4844 update->keys = uw_Sqlcache_copyKeys(keys, cache->numKeys); |
4798 update->value = value; | 4845 update->value = value; |
4799 update->next = NULL; | 4846 update->next = NULL; |
4800 if (ctx->cacheUpdateTail) { | 4847 if (ctx->cacheUpdateTail) { |
4801 // An update is already registered, so just extend it. | |
4802 ctx->cacheUpdateTail->next = update; | 4848 ctx->cacheUpdateTail->next = update; |
4803 } else { | 4849 } else { |
4804 ctx->cacheUpdate = update; | 4850 ctx->cacheUpdate = update; |
4805 uw_register_transactional(ctx, ctx, uw_Sqlcache_commitUpdate, NULL, uw_Sqlcache_freeUpdate); | |
4806 } | 4851 } |
4807 ctx->cacheUpdateTail = update; | 4852 ctx->cacheUpdateTail = update; |
4808 } | 4853 } |
4809 | 4854 |
4810 void uw_Sqlcache_flush(uw_context ctx, uw_Sqlcache_Cache *cache, char **keys) { | 4855 void uw_Sqlcache_flush(uw_context ctx, uw_Sqlcache_Cache *cache, char **keys) { |