From 24edb607ef64db1ab12b3d5b9ccd3848c50780d1 Mon Sep 17 00:00:00 2001 From: Ziv Scully Date: Sun, 28 Jun 2015 12:46:51 -0700 Subject: Progress on LRU cache but still more known bugs to fix. --- src/lru_cache.sml | 171 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 src/lru_cache.sml (limited to 'src/lru_cache.sml') diff --git a/src/lru_cache.sml b/src/lru_cache.sml new file mode 100644 index 00000000..87e939fa --- /dev/null +++ b/src/lru_cache.sml @@ -0,0 +1,171 @@ +structure LruCache : sig + val cache : Cache.cache +end = struct + + +(* Mono *) + +open Mono + +val dummyLoc = ErrorMsg.dummySpan +val stringTyp = (TFfi ("Basis", "string"), dummyLoc) +val optionStringTyp = (TOption stringTyp, dummyLoc) +fun withTyp typ = map (fn exp => (exp, typ)) + +fun ffiAppCache' (func, index, argTyps) = + EFfiApp ("Sqlcache", func ^ Int.toString index, argTyps) + +fun check (index, keys) = + ffiAppCache' ("check", index, withTyp stringTyp keys) + +fun store (index, keys, value) = + ffiAppCache' ("store", index, (value, stringTyp) :: withTyp stringTyp keys) + +fun flush (index, keys) = + ffiAppCache' ("flush", index, withTyp optionStringTyp keys) + + +(* Cjr *) + +open Print +open Print.PD + +fun setupQuery {index, params} = + let + + val i = Int.toString index + + fun paramRepeat itemi sep = + let + fun f n = + if n < 0 then "" + else if n = 0 then itemi (Int.toString 0) + else f (n-1) ^ sep ^ itemi (Int.toString n) + in + f (params - 1) + end + + fun paramRepeatRev itemi sep = + let + fun f n = + if n < 0 then "" + else if n = 0 then itemi (Int.toString 0) + else itemi (Int.toString n) ^ sep ^ f (n-1) + in + f (params - 1) + end + + fun paramRepeatInit itemi sep = + if params = 0 then "" else sep ^ paramRepeat itemi sep + + val typedArgs = paramRepeatInit (fn p => "uw_Basis_string p" ^ p) ", " + + val revArgs = paramRepeatRev (fn p => "p" ^ p) ", " + + in + Print.box + [string ("static Cache cacheStruct" ^ i ^ " = {"), + newline, + string " .table = NULL,", + newline, + string " .timeInvalid = 0,", + newline, + string " .lru = NULL,", + newline, + string (" .height = " ^ Int.toString (params - 1) ^ "};"), + newline, + string ("static Cache *cache" ^ i ^ " = &cacheStruct" ^ i ^ ";"), + newline, + newline, + + string ("static uw_Basis_string uw_Sqlcache_check" ^ i), + string ("(uw_context ctx" ^ typedArgs ^ ") {"), + newline, + string (" char *ks[] = {" ^ revArgs ^ "};"), + newline, + string (" CacheValue *v = check(cache" ^ i ^ ", ks);"), + newline, + string " if (v) {", + newline, + string (" puts(\"SQLCACHE: hit " ^ i ^ ".\");"), + newline, + string " uw_write(ctx, v->output);", + newline, + string " return v->result;", + newline, + string " } else {", + newline, + string (" puts(\"SQLCACHE: miss " ^ i ^ ".\");"), + newline, + string " uw_recordingStart(ctx);", + newline, + string " return NULL;", + newline, + string " }", + newline, + string "}", + newline, + newline, + + string ("static uw_unit uw_Sqlcache_store" ^ i), + string ("(uw_context ctx, uw_Basis_string s" ^ typedArgs ^ ") {"), + newline, + string (" char *ks[] = {" ^ revArgs ^ "};"), + newline, + string (" CacheValue *v = malloc(sizeof(CacheValue));"), + newline, + string " v->result = strdup(s);", + newline, + string " v->output = uw_recordingRead(ctx);", + newline, + string (" puts(\"SQLCACHE: stored " ^ i ^ ".\");"), + newline, + string (" store(cache" ^ i ^ ", ks, v);"), + newline, + string " return uw_unit_v;", + newline, + string "}", + newline, + newline, + + string ("static uw_unit uw_Sqlcache_flush" ^ i), + string ("(uw_context ctx" ^ typedArgs ^ ") {"), + newline, + string (" char *ks[] = {" ^ revArgs ^ "};"), + newline, + string (" flush(cache" ^ i ^ ", ks);"), + newline, + string " return uw_unit_v;", + newline, + string "}", + newline, + newline] + end + +val setupGlobal = string "/* No global setup for LRU cache. */" + + +(* Bundled up. *) + +(* For now, use the toy implementation if there are no arguments. *) +fun toyIfNoKeys numKeys implLru implToy args = + if numKeys args = 0 + then implToy args + else implLru args + +val cache = + let + val {check = toyCheck, + store = toyStore, + flush = toyFlush, + setupQuery = toySetupQuery, + ...} = ToyCache.cache + in + {check = toyIfNoKeys (length o #2) check toyCheck, + store = toyIfNoKeys (length o #2) store toyStore, + flush = toyIfNoKeys (length o #2) flush toyFlush, + setupQuery = toyIfNoKeys #params setupQuery toySetupQuery, + setupGlobal = setupGlobal} + end + +end -- cgit v1.2.3 From bc38beafd07b7ae6106a2fffda82084a08af7f06 Mon Sep 17 00:00:00 2001 From: Ziv Scully Date: Sun, 19 Jul 2015 19:03:11 -0700 Subject: Rename C functions and remove functors nested inside modules. --- include/urweb/types_cpp.h | 28 ++++++++--------- include/urweb/urweb_cpp.h | 6 ++-- src/c/urweb.c | 78 +++++++++++++++++++++++------------------------ src/lru_cache.sml | 12 ++++---- src/option_key_fn.sml | 11 +++++++ src/sources | 3 +- src/sqlcache.sml | 30 +----------------- src/triple_key_fn.sml | 15 +++++++++ 8 files changed, 91 insertions(+), 92 deletions(-) create mode 100644 src/option_key_fn.sml create mode 100644 src/triple_key_fn.sml (limited to 'src/lru_cache.sml') diff --git a/include/urweb/types_cpp.h b/include/urweb/types_cpp.h index 2f154e1f..7b9a90a4 100644 --- a/include/urweb/types_cpp.h +++ b/include/urweb/types_cpp.h @@ -123,31 +123,31 @@ typedef struct { #include "uthash.h" -typedef struct CacheValue { +typedef struct uw_sqlcache_CacheValue { char *result; char *output; -} CacheValue; +} uw_sqlcache_CacheValue; -typedef struct CacheEntry { +typedef struct uw_sqlcache_CacheEntry { char *key; void *value; time_t timeValid; - struct CacheEntry *prev; - struct CacheEntry *next; + struct uw_sqlcache_CacheEntry *prev; + struct uw_sqlcache_CacheEntry *next; UT_hash_handle hh; -} CacheEntry; +} uw_sqlcache_CacheEntry; -typedef struct CacheList { - CacheEntry *first; - CacheEntry *last; +typedef struct uw_sqlcache_CacheList { + uw_sqlcache_CacheEntry *first; + uw_sqlcache_CacheEntry *last; int size; -} CacheList; +} uw_sqlcache_CacheList; -typedef struct Cache { - CacheEntry *table; +typedef struct uw_sqlcache_Cache { + uw_sqlcache_CacheEntry *table; time_t timeInvalid; - CacheList *lru; + uw_sqlcache_CacheList *lru; int height; -} Cache; +} uw_sqlcache_Cache; #endif diff --git a/include/urweb/urweb_cpp.h b/include/urweb/urweb_cpp.h index 3ae5b69e..3fac7041 100644 --- a/include/urweb/urweb_cpp.h +++ b/include/urweb/urweb_cpp.h @@ -406,8 +406,8 @@ void uw_Basis_writec(struct uw_context *, char); #include "uthash.h" -CacheValue *check(Cache *, char **); -CacheValue *store(Cache *, char **, CacheValue *); -CacheValue *flush(Cache *, char **); +uw_sqlcache_CacheValue *uw_sqlcache_check(uw_sqlcache_Cache *, char **); +uw_sqlcache_CacheValue *uw_sqlcache_store(uw_sqlcache_Cache *, char **, uw_sqlcache_CacheValue *); +uw_sqlcache_CacheValue *uw_sqlcache_flush(uw_sqlcache_Cache *, char **); #endif diff --git a/src/c/urweb.c b/src/c/urweb.c index e0fd503c..3993448b 100644 --- a/src/c/urweb.c +++ b/src/c/urweb.c @@ -4500,7 +4500,7 @@ void uw_set_remoteSock(uw_context ctx, int sock) { // Sqlcache -void listDelete(CacheList *list, CacheEntry *entry) { +void uw_sqlcache_listDelete(uw_sqlcache_CacheList *list, uw_sqlcache_CacheEntry *entry) { if (list->first == entry) { list->first = entry->next; } @@ -4518,7 +4518,7 @@ void listDelete(CacheList *list, CacheEntry *entry) { --(list->size); } -void listAdd(CacheList *list, CacheEntry *entry) { +void uw_sqlcache_listAdd(uw_sqlcache_CacheList *list, uw_sqlcache_CacheEntry *entry) { if (list->last) { list->last->next = entry; entry->prev = list->last; @@ -4530,22 +4530,22 @@ void listAdd(CacheList *list, CacheEntry *entry) { ++(list->size); } -void listBump(CacheList *list, CacheEntry *entry) { - listDelete(list, entry); - listAdd(list, entry); +void uw_sqlcache_listBump(uw_sqlcache_CacheList *list, uw_sqlcache_CacheEntry *entry) { + uw_sqlcache_listDelete(list, entry); + uw_sqlcache_listAdd(list, entry); } // TODO: deal with time properly. -time_t getTimeNow() { +time_t uw_sqlcache_getTimeNow() { return time(NULL); } -time_t timeMax(time_t x, time_t y) { +time_t uw_sqlcache_timeMax(time_t x, time_t y) { return difftime(x, y) > 0 ? x : y; } -void freeCacheValue(CacheValue *value) { +void uw_sqlcache_freeuw_sqlcache_CacheValue(uw_sqlcache_CacheValue *value) { if (value) { free(value->result); free(value->output); @@ -4553,83 +4553,83 @@ void freeCacheValue(CacheValue *value) { } } -void delete(Cache *cache, CacheEntry* entry) { - //listDelete(cache->lru, entry); +void uw_sqlcache_delete(uw_sqlcache_Cache *cache, uw_sqlcache_CacheEntry* entry) { + //uw_sqlcache_listUw_Sqlcache_Delete(cache->lru, entry); HASH_DELETE(hh, cache->table, entry); - freeCacheValue(entry->value); + uw_sqlcache_freeuw_sqlcache_CacheValue(entry->value); free(entry->key); free(entry); } -CacheValue *checkHelper(Cache *cache, char **keys, int timeInvalid) { +uw_sqlcache_CacheValue *uw_sqlcache_checkHelper(uw_sqlcache_Cache *cache, char **keys, int timeInvalid) { char *key = keys[cache->height]; - CacheEntry *entry; + uw_sqlcache_CacheEntry *entry; HASH_FIND(hh, cache->table, key, strlen(key), entry); - timeInvalid = timeMax(timeInvalid, cache->timeInvalid); + timeInvalid = uw_sqlcache_timeMax(timeInvalid, cache->timeInvalid); if (entry && difftime(entry->timeValid, timeInvalid) > 0) { if (cache->height == 0) { // At height 0, entry->value is the desired value. - //listBump(cache->lru, entry); + //uw_sqlcache_listBump(cache->lru, entry); return entry->value; } else { // At height n+1, entry->value is a pointer to a cache at heignt n. - return checkHelper(entry->value, keys, timeInvalid); + return uw_sqlcache_checkHelper(entry->value, keys, timeInvalid); } } else { return NULL; } } -CacheValue *check(Cache *cache, char **keys) { - return checkHelper(cache, keys, 0); +uw_sqlcache_CacheValue *uw_sqlcache_check(uw_sqlcache_Cache *cache, char **keys) { + return uw_sqlcache_checkHelper(cache, keys, 0); } -void storeHelper(Cache *cache, char **keys, CacheValue *value, int timeNow) { - CacheEntry *entry; +void uw_sqlcache_storeHelper(uw_sqlcache_Cache *cache, char **keys, uw_sqlcache_CacheValue *value, int timeNow) { + uw_sqlcache_CacheEntry *entry; char *key = keys[cache->height]; HASH_FIND(hh, cache->table, key, strlen(key), entry); if (!entry) { - entry = malloc(sizeof(CacheEntry)); + entry = malloc(sizeof(uw_sqlcache_CacheEntry)); entry->key = strdup(key); entry->value = NULL; HASH_ADD_KEYPTR(hh, cache->table, entry->key, strlen(entry->key), entry); } entry->timeValid = timeNow; if (cache->height == 0) { - //listAdd(cache->lru, entry); - freeCacheValue(entry->value); + //uw_sqlcache_listAdd(cache->lru, entry); + uw_sqlcache_freeuw_sqlcache_CacheValue(entry->value); entry->value = value; //if (cache->lru->size > MAX_SIZE) { - //delete(cache, cache->lru->first); + //uw_sqlcache_delete(cache, cache->lru->first); // TODO: return flushed value. //} } else { if (!entry->value) { - Cache *newCache = malloc(sizeof(Cache)); - newCache->table = NULL; - newCache->timeInvalid = timeNow; - newCache->lru = cache->lru; - newCache->height = cache->height - 1; - entry->value = newCache; + uw_sqlcache_Cache *newuw_sqlcache_Cache = malloc(sizeof(uw_sqlcache_Cache)); + newuw_sqlcache_Cache->table = NULL; + newuw_sqlcache_Cache->timeInvalid = timeNow; + newuw_sqlcache_Cache->lru = cache->lru; + newuw_sqlcache_Cache->height = cache->height - 1; + entry->value = newuw_sqlcache_Cache; } - storeHelper(entry->value, keys, value, timeNow); + uw_sqlcache_storeHelper(entry->value, keys, value, timeNow); } } -void store(Cache *cache, char **keys, CacheValue *value) { - storeHelper(cache, keys, value, getTimeNow()); +void uw_sqlcache_store(uw_sqlcache_Cache *cache, char **keys, uw_sqlcache_CacheValue *value) { + uw_sqlcache_storeHelper(cache, keys, value, uw_sqlcache_getTimeNow()); } -void flushHelper(Cache *cache, char **keys, int timeNow) { - CacheEntry *entry; +void uw_sqlcache_flushHelper(uw_sqlcache_Cache *cache, char **keys, int timeNow) { + uw_sqlcache_CacheEntry *entry; char *key = keys[cache->height]; if (key) { HASH_FIND(hh, cache->table, key, strlen(key), entry); if (entry) { if (cache->height == 0) { - delete(cache, entry); + uw_sqlcache_delete(cache, entry); } else { - flushHelper(entry->value, keys, timeNow); + uw_sqlcache_flushHelper(entry->value, keys, timeNow); } } } else { @@ -4638,6 +4638,6 @@ void flushHelper(Cache *cache, char **keys, int timeNow) { } } -void flush(Cache *cache, char **keys) { - flushHelper(cache, keys, getTimeNow()); +void uw_sqlcache_flush(uw_sqlcache_Cache *cache, char **keys) { + uw_sqlcache_flushHelper(cache, keys, uw_sqlcache_getTimeNow()); } diff --git a/src/lru_cache.sml b/src/lru_cache.sml index 87e939fa..26590312 100644 --- a/src/lru_cache.sml +++ b/src/lru_cache.sml @@ -64,7 +64,7 @@ fun setupQuery {index, params} = in Print.box - [string ("static Cache cacheStruct" ^ i ^ " = {"), + [string ("static uw_sqlcache_Cache cacheStruct" ^ i ^ " = {"), newline, string " .table = NULL,", newline, @@ -74,7 +74,7 @@ fun setupQuery {index, params} = newline, string (" .height = " ^ Int.toString (params - 1) ^ "};"), newline, - string ("static Cache *cache" ^ i ^ " = &cacheStruct" ^ i ^ ";"), + string ("static uw_sqlcache_Cache *cache" ^ i ^ " = &cacheStruct" ^ i ^ ";"), newline, newline, @@ -83,7 +83,7 @@ fun setupQuery {index, params} = newline, string (" char *ks[] = {" ^ revArgs ^ "};"), newline, - string (" CacheValue *v = check(cache" ^ i ^ ", ks);"), + string (" uw_sqlcache_CacheValue *v = uw_sqlcache_check(cache" ^ i ^ ", ks);"), newline, string " if (v) {", newline, @@ -112,7 +112,7 @@ fun setupQuery {index, params} = newline, string (" char *ks[] = {" ^ revArgs ^ "};"), newline, - string (" CacheValue *v = malloc(sizeof(CacheValue));"), + string (" uw_sqlcache_CacheValue *v = malloc(sizeof(uw_sqlcache_CacheValue));"), newline, string " v->result = strdup(s);", newline, @@ -120,7 +120,7 @@ fun setupQuery {index, params} = newline, string (" puts(\"SQLCACHE: stored " ^ i ^ ".\");"), newline, - string (" store(cache" ^ i ^ ", ks, v);"), + string (" uw_sqlcache_store(cache" ^ i ^ ", ks, v);"), newline, string " return uw_unit_v;", newline, @@ -133,7 +133,7 @@ fun setupQuery {index, params} = newline, string (" char *ks[] = {" ^ revArgs ^ "};"), newline, - string (" flush(cache" ^ i ^ ", ks);"), + string (" uw_sqlcache_flush(cache" ^ i ^ ", ks);"), newline, string " return uw_unit_v;", newline, diff --git a/src/option_key_fn.sml b/src/option_key_fn.sml new file mode 100644 index 00000000..ba636d4e --- /dev/null +++ b/src/option_key_fn.sml @@ -0,0 +1,11 @@ +functor OptionKeyFn(K : ORD_KEY) : ORD_KEY = struct + +type ord_key = K.ord_key option + +val compare = + fn (NONE, NONE) => EQUAL + | (NONE, _) => LESS + | (_, NONE) => GREATER + | (SOME x, SOME y) => K.compare (x, y) + +end diff --git a/src/sources b/src/sources index 0608d710..f0914bdf 100644 --- a/src/sources +++ b/src/sources @@ -172,8 +172,9 @@ $(SRC)/sql.sig $(SRC)/sql.sml $(SRC)/union_find_fn.sml - $(SRC)/multimap_fn.sml +$(SRC)/option_key_fn.sml +$(SRC)/triple_key_fn.sml $(SRC)/cache.sml $(SRC)/toy_cache.sml diff --git a/src/sqlcache.sml b/src/sqlcache.sml index 5f737ac5..ff58ef77 100644 --- a/src/sqlcache.sml +++ b/src/sqlcache.sml @@ -207,7 +207,7 @@ fun mapFormula mf = (* SQL analysis. *) -structure CmpKey : ORD_KEY = struct +structure CmpKey = struct type ord_key = Sql.cmp @@ -247,34 +247,6 @@ functor ListKeyFn (K : ORD_KEY) : ORD_KEY = struct end *) -functor OptionKeyFn (K : ORD_KEY) : ORD_KEY = struct - - type ord_key = K.ord_key option - - val compare = - fn (NONE, NONE) => EQUAL - | (NONE, _) => LESS - | (_, NONE) => GREATER - | (SOME x, SOME y) => K.compare (x, y) - -end - -functor TripleKeyFn (structure I : ORD_KEY - structure J : ORD_KEY - structure K : ORD_KEY) - : ORD_KEY where type ord_key = I.ord_key * J.ord_key * K.ord_key = struct - - type ord_key = I.ord_key * J.ord_key * K.ord_key - - fun compare ((i1, j1, k1), (i2, j2, k2)) = - case I.compare (i1, i2) of - EQUAL => (case J.compare (j1, j2) of - EQUAL => K.compare (k1, k2) - | ord => ord) - | ord => ord - -end - val rec chooseTwos : 'a list -> ('a * 'a) list = fn [] => [] | x :: ys => map (fn y => (x, y)) ys @ chooseTwos ys diff --git a/src/triple_key_fn.sml b/src/triple_key_fn.sml new file mode 100644 index 00000000..ba77c60b --- /dev/null +++ b/src/triple_key_fn.sml @@ -0,0 +1,15 @@ +functor TripleKeyFn (structure I : ORD_KEY + structure J : ORD_KEY + structure K : ORD_KEY) + : ORD_KEY where type ord_key = I.ord_key * J.ord_key * K.ord_key = struct + +type ord_key = I.ord_key * J.ord_key * K.ord_key + +fun compare ((i1, j1, k1), (i2, j2, k2)) = + case I.compare (i1, i2) of + EQUAL => (case J.compare (j1, j2) of + EQUAL => K.compare (k1, k2) + | ord => ord) + | ord => ord + +end -- cgit v1.2.3 From 46fe4e62ddefd8f79f4a29f7a273f585436d3c85 Mon Sep 17 00:00:00 2001 From: Ziv Scully Date: Sun, 13 Sep 2015 16:02:45 -0400 Subject: Start work on pure expression caching. --- include/urweb/types_cpp.h | 28 ++++---- include/urweb/urweb_cpp.h | 6 +- src/c/openssl.c | 4 +- src/c/urweb.c | 78 ++++++++++----------- src/lru_cache.sml | 12 ++-- src/sqlcache.sml | 174 +++++++++++++++++++++++++++++++++++++++++----- 6 files changed, 221 insertions(+), 81 deletions(-) (limited to 'src/lru_cache.sml') diff --git a/include/urweb/types_cpp.h b/include/urweb/types_cpp.h index 7b9a90a4..84423105 100644 --- a/include/urweb/types_cpp.h +++ b/include/urweb/types_cpp.h @@ -123,31 +123,31 @@ typedef struct { #include "uthash.h" -typedef struct uw_sqlcache_CacheValue { +typedef struct uw_Sqlcache_CacheValue { char *result; char *output; -} uw_sqlcache_CacheValue; +} uw_Sqlcache_CacheValue; -typedef struct uw_sqlcache_CacheEntry { +typedef struct uw_Sqlcache_CacheEntry { char *key; void *value; time_t timeValid; - struct uw_sqlcache_CacheEntry *prev; - struct uw_sqlcache_CacheEntry *next; + struct uw_Sqlcache_CacheEntry *prev; + struct uw_Sqlcache_CacheEntry *next; UT_hash_handle hh; -} uw_sqlcache_CacheEntry; +} uw_Sqlcache_CacheEntry; -typedef struct uw_sqlcache_CacheList { - uw_sqlcache_CacheEntry *first; - uw_sqlcache_CacheEntry *last; +typedef struct uw_Sqlcache_CacheList { + uw_Sqlcache_CacheEntry *first; + uw_Sqlcache_CacheEntry *last; int size; -} uw_sqlcache_CacheList; +} uw_Sqlcache_CacheList; -typedef struct uw_sqlcache_Cache { - uw_sqlcache_CacheEntry *table; +typedef struct uw_Sqlcache_Cache { + uw_Sqlcache_CacheEntry *table; time_t timeInvalid; - uw_sqlcache_CacheList *lru; + uw_Sqlcache_CacheList *lru; int height; -} uw_sqlcache_Cache; +} uw_Sqlcache_Cache; #endif diff --git a/include/urweb/urweb_cpp.h b/include/urweb/urweb_cpp.h index 3fac7041..05e3e4a0 100644 --- a/include/urweb/urweb_cpp.h +++ b/include/urweb/urweb_cpp.h @@ -406,8 +406,8 @@ void uw_Basis_writec(struct uw_context *, char); #include "uthash.h" -uw_sqlcache_CacheValue *uw_sqlcache_check(uw_sqlcache_Cache *, char **); -uw_sqlcache_CacheValue *uw_sqlcache_store(uw_sqlcache_Cache *, char **, uw_sqlcache_CacheValue *); -uw_sqlcache_CacheValue *uw_sqlcache_flush(uw_sqlcache_Cache *, char **); +uw_Sqlcache_CacheValue *uw_Sqlcache_check(uw_Sqlcache_Cache *, char **); +uw_Sqlcache_CacheValue *uw_Sqlcache_store(uw_Sqlcache_Cache *, char **, uw_Sqlcache_CacheValue *); +uw_Sqlcache_CacheValue *uw_Sqlcache_flush(uw_Sqlcache_Cache *, char **); #endif diff --git a/src/c/openssl.c b/src/c/openssl.c index 6d018707..533c3e21 100644 --- a/src/c/openssl.c +++ b/src/c/openssl.c @@ -35,7 +35,7 @@ static void random_password() { // OpenSSL callbacks static void thread_id(CRYPTO_THREADID *const result) { - CRYPTO_THREADID_set_numeric(result, pthread_self()); + CRYPTO_THREADID_set_numeric(result, (unsigned long)pthread_self()); } static void lock_or_unlock(const int mode, const int type, const char *file, const int line) { @@ -73,7 +73,7 @@ void uw_init_crypto() { if (access(uw_sig_file, F_OK)) { random_password(); - + if ((fd = open(uw_sig_file, O_WRONLY | O_CREAT, 0700)) < 0) { fprintf(stderr, "Can't open signature file %s\n", uw_sig_file); perror("open"); diff --git a/src/c/urweb.c b/src/c/urweb.c index 66fedfa2..61742693 100644 --- a/src/c/urweb.c +++ b/src/c/urweb.c @@ -4498,7 +4498,7 @@ void uw_set_remoteSock(uw_context ctx, int sock) { // Sqlcache -void uw_sqlcache_listDelete(uw_sqlcache_CacheList *list, uw_sqlcache_CacheEntry *entry) { +void uw_Sqlcache_listDelete(uw_Sqlcache_CacheList *list, uw_Sqlcache_CacheEntry *entry) { if (list->first == entry) { list->first = entry->next; } @@ -4516,7 +4516,7 @@ void uw_sqlcache_listDelete(uw_sqlcache_CacheList *list, uw_sqlcache_CacheEntry --(list->size); } -void uw_sqlcache_listAdd(uw_sqlcache_CacheList *list, uw_sqlcache_CacheEntry *entry) { +void uw_Sqlcache_listAdd(uw_Sqlcache_CacheList *list, uw_Sqlcache_CacheEntry *entry) { if (list->last) { list->last->next = entry; entry->prev = list->last; @@ -4528,22 +4528,22 @@ void uw_sqlcache_listAdd(uw_sqlcache_CacheList *list, uw_sqlcache_CacheEntry *en ++(list->size); } -void uw_sqlcache_listBump(uw_sqlcache_CacheList *list, uw_sqlcache_CacheEntry *entry) { - uw_sqlcache_listDelete(list, entry); - uw_sqlcache_listAdd(list, entry); +void uw_Sqlcache_listBump(uw_Sqlcache_CacheList *list, uw_Sqlcache_CacheEntry *entry) { + uw_Sqlcache_listDelete(list, entry); + uw_Sqlcache_listAdd(list, entry); } // TODO: deal with time properly. -time_t uw_sqlcache_getTimeNow() { +time_t uw_Sqlcache_getTimeNow() { return time(NULL); } -time_t uw_sqlcache_timeMax(time_t x, time_t y) { +time_t uw_Sqlcache_timeMax(time_t x, time_t y) { return difftime(x, y) > 0 ? x : y; } -void uw_sqlcache_freeuw_sqlcache_CacheValue(uw_sqlcache_CacheValue *value) { +void uw_Sqlcache_freeuw_Sqlcache_CacheValue(uw_Sqlcache_CacheValue *value) { if (value) { free(value->result); free(value->output); @@ -4551,83 +4551,83 @@ void uw_sqlcache_freeuw_sqlcache_CacheValue(uw_sqlcache_CacheValue *value) { } } -void uw_sqlcache_delete(uw_sqlcache_Cache *cache, uw_sqlcache_CacheEntry* entry) { - //uw_sqlcache_listUw_Sqlcache_Delete(cache->lru, entry); +void uw_Sqlcache_delete(uw_Sqlcache_Cache *cache, uw_Sqlcache_CacheEntry* entry) { + //uw_Sqlcache_listUw_Sqlcache_Delete(cache->lru, entry); HASH_DELETE(hh, cache->table, entry); - uw_sqlcache_freeuw_sqlcache_CacheValue(entry->value); + uw_Sqlcache_freeuw_Sqlcache_CacheValue(entry->value); free(entry->key); free(entry); } -uw_sqlcache_CacheValue *uw_sqlcache_checkHelper(uw_sqlcache_Cache *cache, char **keys, int timeInvalid) { +uw_Sqlcache_CacheValue *uw_Sqlcache_checkHelper(uw_Sqlcache_Cache *cache, char **keys, int timeInvalid) { char *key = keys[cache->height]; - uw_sqlcache_CacheEntry *entry; + uw_Sqlcache_CacheEntry *entry; HASH_FIND(hh, cache->table, key, strlen(key), entry); - timeInvalid = uw_sqlcache_timeMax(timeInvalid, cache->timeInvalid); + timeInvalid = uw_Sqlcache_timeMax(timeInvalid, cache->timeInvalid); if (entry && difftime(entry->timeValid, timeInvalid) > 0) { if (cache->height == 0) { // At height 0, entry->value is the desired value. - //uw_sqlcache_listBump(cache->lru, entry); + //uw_Sqlcache_listBump(cache->lru, entry); return entry->value; } else { // At height n+1, entry->value is a pointer to a cache at heignt n. - return uw_sqlcache_checkHelper(entry->value, keys, timeInvalid); + return uw_Sqlcache_checkHelper(entry->value, keys, timeInvalid); } } else { return NULL; } } -uw_sqlcache_CacheValue *uw_sqlcache_check(uw_sqlcache_Cache *cache, char **keys) { - return uw_sqlcache_checkHelper(cache, keys, 0); +uw_Sqlcache_CacheValue *uw_Sqlcache_check(uw_Sqlcache_Cache *cache, char **keys) { + return uw_Sqlcache_checkHelper(cache, keys, 0); } -void uw_sqlcache_storeHelper(uw_sqlcache_Cache *cache, char **keys, uw_sqlcache_CacheValue *value, int timeNow) { - uw_sqlcache_CacheEntry *entry; +void uw_Sqlcache_storeHelper(uw_Sqlcache_Cache *cache, char **keys, uw_Sqlcache_CacheValue *value, int timeNow) { + uw_Sqlcache_CacheEntry *entry; char *key = keys[cache->height]; HASH_FIND(hh, cache->table, key, strlen(key), entry); if (!entry) { - entry = malloc(sizeof(uw_sqlcache_CacheEntry)); + entry = malloc(sizeof(uw_Sqlcache_CacheEntry)); entry->key = strdup(key); entry->value = NULL; HASH_ADD_KEYPTR(hh, cache->table, entry->key, strlen(entry->key), entry); } entry->timeValid = timeNow; if (cache->height == 0) { - //uw_sqlcache_listAdd(cache->lru, entry); - uw_sqlcache_freeuw_sqlcache_CacheValue(entry->value); + //uw_Sqlcache_listAdd(cache->lru, entry); + uw_Sqlcache_freeuw_Sqlcache_CacheValue(entry->value); entry->value = value; //if (cache->lru->size > MAX_SIZE) { - //uw_sqlcache_delete(cache, cache->lru->first); + //uw_Sqlcache_delete(cache, cache->lru->first); // TODO: return flushed value. //} } else { if (!entry->value) { - uw_sqlcache_Cache *newuw_sqlcache_Cache = malloc(sizeof(uw_sqlcache_Cache)); - newuw_sqlcache_Cache->table = NULL; - newuw_sqlcache_Cache->timeInvalid = timeNow; - newuw_sqlcache_Cache->lru = cache->lru; - newuw_sqlcache_Cache->height = cache->height - 1; - entry->value = newuw_sqlcache_Cache; + uw_Sqlcache_Cache *newuw_Sqlcache_Cache = malloc(sizeof(uw_Sqlcache_Cache)); + newuw_Sqlcache_Cache->table = NULL; + newuw_Sqlcache_Cache->timeInvalid = timeNow; + newuw_Sqlcache_Cache->lru = cache->lru; + newuw_Sqlcache_Cache->height = cache->height - 1; + entry->value = newuw_Sqlcache_Cache; } - uw_sqlcache_storeHelper(entry->value, keys, value, timeNow); + uw_Sqlcache_storeHelper(entry->value, keys, value, timeNow); } } -void uw_sqlcache_store(uw_sqlcache_Cache *cache, char **keys, uw_sqlcache_CacheValue *value) { - uw_sqlcache_storeHelper(cache, keys, value, uw_sqlcache_getTimeNow()); +void uw_Sqlcache_store(uw_Sqlcache_Cache *cache, char **keys, uw_Sqlcache_CacheValue *value) { + uw_Sqlcache_storeHelper(cache, keys, value, uw_Sqlcache_getTimeNow()); } -void uw_sqlcache_flushHelper(uw_sqlcache_Cache *cache, char **keys, int timeNow) { - uw_sqlcache_CacheEntry *entry; +void uw_Sqlcache_flushHelper(uw_Sqlcache_Cache *cache, char **keys, int timeNow) { + uw_Sqlcache_CacheEntry *entry; char *key = keys[cache->height]; if (key) { HASH_FIND(hh, cache->table, key, strlen(key), entry); if (entry) { if (cache->height == 0) { - uw_sqlcache_delete(cache, entry); + uw_Sqlcache_delete(cache, entry); } else { - uw_sqlcache_flushHelper(entry->value, keys, timeNow); + uw_Sqlcache_flushHelper(entry->value, keys, timeNow); } } } else { @@ -4636,6 +4636,6 @@ void uw_sqlcache_flushHelper(uw_sqlcache_Cache *cache, char **keys, int timeNow) } } -void uw_sqlcache_flush(uw_sqlcache_Cache *cache, char **keys) { - uw_sqlcache_flushHelper(cache, keys, uw_sqlcache_getTimeNow()); +void uw_Sqlcache_flush(uw_Sqlcache_Cache *cache, char **keys) { + uw_Sqlcache_flushHelper(cache, keys, uw_Sqlcache_getTimeNow()); } diff --git a/src/lru_cache.sml b/src/lru_cache.sml index 26590312..0030777f 100644 --- a/src/lru_cache.sml +++ b/src/lru_cache.sml @@ -64,7 +64,7 @@ fun setupQuery {index, params} = in Print.box - [string ("static uw_sqlcache_Cache cacheStruct" ^ i ^ " = {"), + [string ("static uw_Sqlcache_Cache cacheStruct" ^ i ^ " = {"), newline, string " .table = NULL,", newline, @@ -74,7 +74,7 @@ fun setupQuery {index, params} = newline, string (" .height = " ^ Int.toString (params - 1) ^ "};"), newline, - string ("static uw_sqlcache_Cache *cache" ^ i ^ " = &cacheStruct" ^ i ^ ";"), + string ("static uw_Sqlcache_Cache *cache" ^ i ^ " = &cacheStruct" ^ i ^ ";"), newline, newline, @@ -83,7 +83,7 @@ fun setupQuery {index, params} = newline, string (" char *ks[] = {" ^ revArgs ^ "};"), newline, - string (" uw_sqlcache_CacheValue *v = uw_sqlcache_check(cache" ^ i ^ ", ks);"), + string (" uw_Sqlcache_CacheValue *v = uw_Sqlcache_check(cache" ^ i ^ ", ks);"), newline, string " if (v) {", newline, @@ -112,7 +112,7 @@ fun setupQuery {index, params} = newline, string (" char *ks[] = {" ^ revArgs ^ "};"), newline, - string (" uw_sqlcache_CacheValue *v = malloc(sizeof(uw_sqlcache_CacheValue));"), + string (" uw_Sqlcache_CacheValue *v = malloc(sizeof(uw_Sqlcache_CacheValue));"), newline, string " v->result = strdup(s);", newline, @@ -120,7 +120,7 @@ fun setupQuery {index, params} = newline, string (" puts(\"SQLCACHE: stored " ^ i ^ ".\");"), newline, - string (" uw_sqlcache_store(cache" ^ i ^ ", ks, v);"), + string (" uw_Sqlcache_store(cache" ^ i ^ ", ks, v);"), newline, string " return uw_unit_v;", newline, @@ -133,7 +133,7 @@ fun setupQuery {index, params} = newline, string (" char *ks[] = {" ^ revArgs ^ "};"), newline, - string (" uw_sqlcache_flush(cache" ^ i ^ ", ks);"), + string (" uw_Sqlcache_flush(cache" ^ i ^ ", ks);"), newline, string " return uw_unit_v;", newline, diff --git a/src/sqlcache.sml b/src/sqlcache.sml index 8fae15eb..8efe999c 100644 --- a/src/sqlcache.sml +++ b/src/sqlcache.sml @@ -1,4 +1,4 @@ -structure Sqlcache (* DEBUG: add back :> SQLCACHE. *) = struct +structure Sqlcache :> SQLCACHE = struct open Mono @@ -9,6 +9,12 @@ structure SS = BinarySetFn(SK) structure SM = BinaryMapFn(SK) structure SIMM = MultimapFn(structure KeyMap = SM structure ValSet = IS) +fun iterate f n x = if n < 0 + then raise Fail "Can't iterate function negative number of times." + else if n = 0 + then x + else iterate f (n-1) (f x) + (* Filled in by [cacheWrap] during [Sqlcache]. *) val ffiInfo : {index : int, params : int} list ref = ref [] @@ -36,7 +42,7 @@ val ffiEffectful = "urlifyChannel_w"] in fn (m, f) => Settings.isEffectful (m, f) - andalso not (m = "Basis" andalso SS.member (fs, f)) + orelse not (m = "Basis" andalso SS.member (fs, f)) end val cache = ref LruCache.cache @@ -45,8 +51,8 @@ fun getCache () = !cache (* Used to have type context for local variables in MonoUtil functions. *) val doBind = - fn (ctx, MonoUtil.Exp.RelE (_, t)) => t :: ctx - | (ctx, _) => ctx + fn (env, MonoUtil.Exp.RelE (s, t)) => MonoEnv.pushERel env s t NONE + | (env, _) => env (*******************) @@ -59,12 +65,12 @@ fun effectful (effs : IS.set) = val isFunction = fn (TFun _, _) => true | _ => false - fun doExp (ctx, e) = + fun doExp (env, e) = case e of EPrim _ => false (* For now: variables of function type might be effectful, but others are fully evaluated and are therefore not effectful. *) - | ERel n => isFunction (List.nth (ctx, n)) + | ERel n => isFunction (#2 (MonoEnv.lookupERel env n)) | ENamed n => IS.member (effs, n) | EFfi (m, f) => ffiEffectful (m, f) | EFfiApp (m, f, _) => ffiEffectful (m, f) @@ -84,9 +90,8 @@ fun effectful (effs : IS.set) = | EWrite _ => false | ESeq _ => false | ELet _ => false - (* ASK: what should we do about closures? *) - | EClosure _ => false | EUnurlify _ => false + (* ASK: what should we do about closures? *) (* Everything else is some sort of effect. We could flip this and explicitly list bits of Mono that are effectful, but this is conservatively robust to future changes (however unlikely). *) @@ -99,7 +104,7 @@ fun effectful (effs : IS.set) = fun effectfulDecls (decls, _) = let fun doVal ((_, name, _, e, _), effs) = - if effectful effs [] e + if effectful effs MonoEnv.empty e then IS.add (effs, name) else effs val doDecl = @@ -362,9 +367,9 @@ structure ConflictMaps = struct val markDml : (Sql.cmp * Sql.sqexp * Sql.sqexp) formula -> (Sql.cmp * atomExp option * atomExp option) formula = mapFormula (toAtomExps DmlRel) + (* No eqs should have key conflicts because no variable is in two equivalence classes, so the [#1] could be [#2]. *) - val mergeEqs : (atomExp IntBinaryMap.map option list -> atomExp IntBinaryMap.map option) = List.foldr (fn (SOME eqs, SOME acc) => SOME (IM.unionWith #1 (eqs, acc)) | _ => NONE) @@ -511,10 +516,10 @@ fun cacheWrap (query, i, urlifiedRel0, resultTyp, args) = fun fileMapfold doExp file start = case MonoUtil.File.mapfoldB {typ = Search.return2, - exp = fn ctx => fn e' => fn s => Search.Continue (doExp ctx e' s), + exp = fn env => fn e' => fn s => Search.Continue (doExp env e' s), decl = fn _ => Search.return2, bind = doBind} - [] file start of + MonoEnv.empty file start of Search.Continue x => x | Search.Return _ => raise Match @@ -556,8 +561,9 @@ fun factorOutNontrivial text = fun addChecking file = let - fun doExp ctx (queryInfo as (tableToIndices, indexToQueryNumArgs, index)) = + fun doExp env (queryInfo as (tableToIndices, indexToQueryNumArgs, index)) = fn e' as EQuery {query = origQueryText, + (* ASK: could this get messed up by inlining? *) sqlcacheInfo = urlifiedRel0, state = resultTyp, initial, body, tables, exps} => @@ -581,10 +587,14 @@ fun addChecking file = fun guard b x = if b then x else NONE val effs = effectfulDecls file (* We use dummyTyp here. I think this is okay because databases - don't store (effectful) functions, but there could be some - corner case I missed. *) + don't store (effectful) functions, but perhaps there's some + pathalogical corner case missing.... *) fun safe bound = - not o effectful effs (List.tabulate (bound, fn _ => dummyTyp) @ ctx) + not + o effectful effs + (iterate (fn env => MonoEnv.pushERel env "_" dummyTyp NONE) + bound + env) val attempt = (* Ziv misses Haskell's do notation.... *) guard (safe 0 queryText andalso safe 0 initial andalso safe 2 body) ( @@ -602,7 +612,7 @@ fun addChecking file = end | e' => (e', queryInfo) in - fileMapfold (fn ctx => fn exp => fn state => doExp ctx state exp) + fileMapfold (fn env => fn exp => fn state => doExp env state exp) file (SIMM.empty, IM.empty, 0) end @@ -716,4 +726,134 @@ fun go file = file' end + +(**********************) +(* Mono Type Checking *) +(**********************) + +val typOfPrim = + fn Prim.Int _ => TFfi ("Basis", "int") + | Prim.Float _ => TFfi ("Basis", "int") + +fun typOfExp' (env : MonoEnv.env) : exp' -> typ option = + fn EPrim p => SOME (TFfi ("Basis", case p of + Prim.Int _ => "int" + | Prim.Float _ => "double" + | Prim.String _ => "string" + | Prim.Char _ => "char"), + dummyLoc) + | ERel n => SOME (#2 (MonoEnv.lookupERel env n)) + | ENamed n => SOME (#2 (MonoEnv.lookupENamed env n)) + (* ASK: okay to make a new [ref] each time? *) + | ECon (dk, PConVar nCon, _) => + let + val (_, _, nData) = MonoEnv.lookupConstructor env nCon + val (_, cs) = MonoEnv.lookupDatatype env nData + in + SOME (TDatatype (nData, ref (dk, cs)), dummyLoc) + end + | ECon (_, PConFfi {mod = s, datatyp, ...}, _) => SOME (TFfi (s, datatyp), dummyLoc) + | ENone t => SOME (TOption t, dummyLoc) + | ESome (t, _) => SOME (TOption t, dummyLoc) + | EFfi _ => NONE + | EFfiApp _ => NONE + | EApp (e1, e2) => (case typOfExp env e1 of + SOME (TFun (_, t), _) => SOME t + | _ => NONE) + | EAbs (_, t1, t2, _) => SOME (TFun (t1, t2), dummyLoc) + (* ASK: is this right? *) + | EUnop (unop, e) => (case unop of + "!" => SOME (TFfi ("Basis", "bool"), dummyLoc) + | "-" => typOfExp env e + | _ => NONE) + (* ASK: how should this (and other "=> NONE" cases) work? *) + | EBinop _ => NONE + | ERecord fields => SOME (TRecord (map (fn (s, _, t) => (s, t)) fields), dummyLoc) + | EField (e, s) => (case typOfExp env e of + SOME (TRecord fields, _) => + (case List.find (fn (s', _) => s = s') fields of + SOME (_, t) => SOME t + | _ => NONE) + | _ => NONE) + | ECase (_, _, {result, ...}) => SOME result + | EStrcat _ => SOME (TFfi ("Basis", "string"), dummyLoc) + | EWrite _ => SOME (TRecord [], dummyLoc) + | ESeq (_, e) => typOfExp env e + | ELet (s, t, e1, e2) => typOfExp (MonoEnv.pushERel env s t (SOME e1)) e2 + | EClosure _ => NONE + | EUnurlify (_, t, _) => SOME t + +and typOfExp env (e', loc) = typOfExp' env e' + + +(*******************************) +(* Caching Pure Subexpressions *) +(*******************************) + +datatype subexp = Pure of unit -> exp | Impure of exp + +val isImpure = + fn Pure _ => false + | Impure _ => true + +val expOfSubexp = + fn Pure f => f () + | Impure e => e + +val makeCache : MonoEnv.env -> exp' -> exp' = fn _ => fn _ => raise Fail "TODO" + +fun pureCache (effs : IS.set) (env : MonoEnv.env) (exp as (exp', loc)) : subexp = + let + fun wrapBindN f (args : (MonoEnv.env * exp) list) = + let + val subexps = map (fn (env, exp) => pureCache effs env exp) args + in + if List.exists isImpure subexps + then Impure (f (map expOfSubexp subexps), loc) + else Pure (fn () => (makeCache env (f (map #2 args)), loc)) + end + fun wrapBind1 f arg = + wrapBindN (fn [arg] => f arg | _ => raise Match) [arg] + fun wrapBind2 f (arg1, arg2) = + wrapBindN (fn [arg1, arg2] => f (arg1, arg2) | _ => raise Match) [arg1, arg2] + fun wrapN f es = wrapBindN f (map (fn e => (env, e)) es) + fun wrap1 f e = wrapBind1 f (env, e) + fun wrap2 f (e1, e2) = wrapBind2 f ((env, e1), (env, e2)) + in + case exp' of + ECon (dk, pc, SOME e) => wrap1 (fn e => ECon (dk, pc, SOME e)) e + | ESome (t, e) => wrap1 (fn e => ESome (t, e)) e + | EFfiApp (s1, s2, args) => + wrapN (fn es => EFfiApp (s1, s2, ListPair.map (fn (e, (_, t)) => (e, t)) (es, args))) + (map #1 args) + | EApp (e1, e2) => wrap2 EApp (e1, e2) + | EAbs (s, t1, t2, e) => + wrapBind1 (fn e => EAbs (s, t1, t2, e)) + (MonoEnv.pushERel env s t1 NONE, e) + | EUnop (s, e) => wrap1 (fn e => EUnop (s, e)) e + | EBinop (bi, s, e1, e2) => wrap2 (fn (e1, e2) => EBinop (bi, s, e1, e2)) (e1, e2) + | ERecord fields => + wrapN (fn es => ERecord (ListPair.map (fn (e, (s, _, t)) => (s, e, t)) (es, fields))) + (map #2 fields) + | EField (e, s) => wrap1 (fn e => EField (e, s)) e + | ECase (e, cases, {disc, result}) => + wrapBindN (fn (e::es) => + ECase (e, + (ListPair.map (fn (e, (p, _)) => (p, e)) (es, cases)), + {disc = disc, result = result})) + ((env, e) :: map (fn (p, e) => (MonoEnv.patBinds env p, e)) cases) + | EStrcat (e1, e2) => wrap2 EStrcat (e1, e2) + (* We record page writes, so they're cachable. *) + | EWrite e => wrap1 EWrite e + | ESeq (e1, e2) => wrap2 ESeq (e1, e2) + | ELet (s, t, e1, e2) => + wrapBind2 (fn (e1, e2) => ELet (s, t, e1, e2)) + ((env, e1), (MonoEnv.pushERel env s t (SOME e1), e2)) + (* ASK: | EClosure (n, es) => ? *) + | EUnurlify (e, t, b) => wrap1 (fn e => EUnurlify (e, t, b)) e + | _ => if effectful effs env exp + then Impure exp + else Pure (fn () => (makeCache env exp', loc)) + end + end -- cgit v1.2.3 From 150e1a3cdc0cfae2f583f7d0185b90d5ee82a018 Mon Sep 17 00:00:00 2001 From: Ziv Scully Date: Sun, 27 Sep 2015 17:02:14 -0400 Subject: Fix bug where pure caching didn't treat FFI applications as effectful. --- src/lru_cache.sml | 8 ++++++- src/sqlcache.sml | 68 ++++++++++++++++++++++++++++++------------------------- 2 files changed, 44 insertions(+), 32 deletions(-) (limited to 'src/lru_cache.sml') diff --git a/src/lru_cache.sml b/src/lru_cache.sml index 0030777f..b8dfde5e 100644 --- a/src/lru_cache.sml +++ b/src/lru_cache.sml @@ -13,7 +13,13 @@ val optionStringTyp = (TOption stringTyp, dummyLoc) fun withTyp typ = map (fn exp => (exp, typ)) fun ffiAppCache' (func, index, argTyps) = - EFfiApp ("Sqlcache", func ^ Int.toString index, argTyps) + let + val m = "Sqlcache" + val f = func ^ Int.toString index + in + Settings.addEffectful (m, f); + EFfiApp (m, f, argTyps) + end fun check (index, keys) = ffiAppCache' ("check", index, withTyp stringTyp keys) diff --git a/src/sqlcache.sml b/src/sqlcache.sml index fa4a0d22..e2cc01d7 100644 --- a/src/sqlcache.sml +++ b/src/sqlcache.sml @@ -26,23 +26,23 @@ fun getFfiInfo () = !ffiInfo val ffiEffectful = (* ASK: how can this be less hard-coded? *) let - val fs = SS.fromList ["htmlifyInt_w", - "htmlifyFloat_w", - "htmlifyString_w", - "htmlifyBool_w", - "htmlifyTime_w", - "attrifyInt_w", - "attrifyFloat_w", - "attrifyString_w", - "attrifyChar_w", - "urlifyInt_w", - "urlifyFloat_w", - "urlifyString_w", - "urlifyBool_w", - "urlifyChannel_w"] + val okayWrites = SS.fromList ["htmlifyInt_w", + "htmlifyFloat_w", + "htmlifyString_w", + "htmlifyBool_w", + "htmlifyTime_w", + "attrifyInt_w", + "attrifyFloat_w", + "attrifyString_w", + "attrifyChar_w", + "urlifyInt_w", + "urlifyFloat_w", + "urlifyString_w", + "urlifyBool_w", + "urlifyChannel_w"] in fn (m, f) => Settings.isEffectful (m, f) - orelse not (m = "Basis" andalso SS.member (fs, f)) + andalso not (m = "Basis" andalso SS.member (okayWrites, f)) end val cache = ref LruCache.cache @@ -548,7 +548,7 @@ fun factorOutNontrivial text = let val n = length newVars in - (* This is the (n + 1)th new variable, so there are + (* This is the (n+1)th new variable, so there are already n new variables bound, so we increment indices by n. *) (strcat ((ERel (~(n+1)), loc), qText), incRels n e :: newVars) @@ -586,7 +586,7 @@ fun addChecking file = dummyLoc) val (EQuery {query = queryText, ...}, _) = queryExp (* DEBUG *) - val () = Print.preface ("sqlcache> ", (MonoPrint.p_exp MonoEnv.empty queryText)) + (* val () = Print.preface ("sqlcache> ", MonoPrint.p_exp MonoEnv.empty queryText) *) val args = List.tabulate (numArgs, fn n => (ERel n, dummyLoc)) fun bind x f = Option.mapPartial f x fun guard b x = if b then x else NONE @@ -682,7 +682,7 @@ fun addFlushing ((file, (tableToIndices, indexToQueryNumArgs, index)), effs) = val dmlText = incRels numArgs newDmlText val dmlExp = EDml (dmlText, failureMode) (* DEBUG *) - val () = Print.preface ("sqlcache> ", (MonoPrint.p_exp MonoEnv.empty dmlText)) + (* val () = Print.preface ("sqlcache> ", (MonoPrint.p_exp MonoEnv.empty dmlText)) *) val invs = case Sql.parse Sql.dml dmlText of SOME dmlParsed => @@ -795,6 +795,8 @@ val freeVars = 0 IS.empty +val expSize = MonoUtil.Exp.fold {typ = #2, exp = fn (_, n) => n+1} 0 + datatype subexp = Pure of unit -> exp | Impure of exp val isImpure = @@ -810,16 +812,18 @@ fun makeCache (env, exp', index) = NONE => NONE | SOME (TFun _, _) => NONE | SOME typ => - case List.foldr (fn ((_, _), NONE) => NONE - | ((n, typ), SOME args) => - case MonoFooify.urlify env ((ERel n, dummyLoc), typ) of - NONE => NONE - | SOME arg => SOME (arg :: args)) - (SOME []) - (map (fn n => (n, #2 (MonoEnv.lookupERel env n))) - (freeVars (exp', dummyLoc))) of - NONE => NONE - | SOME args => cacheWrap (env, (exp', dummyLoc), typ, args, index) + if expSize (exp', dummyLoc) < 5 (* TODO: pick a number. *) + then NONE + else case List.foldr (fn ((_, _), NONE) => NONE + | ((n, typ), SOME args) => + case MonoFooify.urlify env ((ERel n, dummyLoc), typ) of + NONE => NONE + | SOME arg => SOME (arg :: args)) + (SOME []) + (map (fn n => (n, #2 (MonoEnv.lookupERel env n))) + (freeVars (exp', dummyLoc))) of + NONE => NONE + | SOME args => cacheWrap (env, (exp', dummyLoc), typ, args, index) fun pureCache (effs : IS.set) ((env, exp as (exp', loc)), index) : subexp * int = let @@ -848,8 +852,11 @@ fun pureCache (effs : IS.set) ((env, exp as (exp', loc)), index) : subexp * int ECon (dk, pc, SOME e) => wrap1 (fn e => ECon (dk, pc, SOME e)) e | ESome (t, e) => wrap1 (fn e => ESome (t, e)) e | EFfiApp (s1, s2, args) => - wrapN (fn es => EFfiApp (s1, s2, ListPair.map (fn (e, (_, t)) => (e, t)) (es, args))) - (map #1 args) + if ffiEffectful (s1, s2) + then (Impure exp, index) + else wrapN (fn es => + EFfiApp (s1, s2, ListPair.map (fn (e, (_, t)) => (e, t)) (es, args))) + (map #1 args) | EApp (e1, e2) => wrap2 EApp (e1, e2) | EAbs (s, t1, t2, e) => wrapBind1 (fn e => EAbs (s, t1, t2, e)) @@ -918,7 +925,6 @@ fun addPure ((decls, sideInfo), index, effs) = (* Important that this happens after the MonoFooify.urlify calls! *) val fmDecls = MonoFooify.getNewFmDecls () in - print (Int.toString (length fmDecls)); (* ASK: fmDecls before or after? *) (fmDecls @ decls, sideInfo) end -- cgit v1.2.3 From 013ea39e9f187efbb0e3a613264a1c7adfebe692 Mon Sep 17 00:00:00 2001 From: Ziv Scully Date: Wed, 7 Oct 2015 08:58:08 -0400 Subject: Fix recording bugs to do with nesting and buffer reallocation. Stop MonoFooify printing spurious errors. --- src/c/urweb.c | 26 +++++++++---- src/lru_cache.sml | 3 +- src/mono_fooify.sml | 75 +++++++++++++++++++++--------------- src/sqlcache.sml | 107 +++++++++++++++++++++++++++++++--------------------- src/toy_cache.sml | 16 ++++++-- 5 files changed, 141 insertions(+), 86 deletions(-) (limited to 'src/lru_cache.sml') diff --git a/src/c/urweb.c b/src/c/urweb.c index 61742693..957f158c 100644 --- a/src/c/urweb.c +++ b/src/c/urweb.c @@ -72,6 +72,9 @@ void uw_buffer_free(uw_buffer *b) { void uw_buffer_reset(uw_buffer *b) { b->front = b->start; + if (b->front != b->back) { + *b->front = 0; + } } int uw_buffer_check(uw_buffer *b, size_t extra) { @@ -486,7 +489,8 @@ struct uw_context { size_t output_buffer_size; // For caching. - char *recording; + int numRecording; + int recordingOffset; int remoteSock; }; @@ -572,7 +576,8 @@ uw_context uw_init(int id, uw_loggers *lg) { ctx->output_buffer = malloc(1); ctx->output_buffer_size = 1; - ctx->recording = 0; + ctx->numRecording = 0; + ctx->recordingOffset = 0; ctx->remoteSock = -1; @@ -1689,11 +1694,18 @@ void uw_write(uw_context ctx, const char* s) { } void uw_recordingStart(uw_context ctx) { - ctx->recording = ctx->page.front; + if (ctx->numRecording++ == 0) { + ctx->recordingOffset = ctx->page.front - ctx->page.start; + } } char *uw_recordingRead(uw_context ctx) { - return strdup(ctx->recording); + // Only the outermost recorder can read unless the recording is empty. + char *recording = ctx->page.start + ctx->recordingOffset; + if (--ctx->numRecording > 0 && recording != ctx->page.front) { + return NULL; + } + return strdup(recording); } char *uw_Basis_attrifyInt(uw_context ctx, uw_Basis_int n) { @@ -4543,7 +4555,7 @@ time_t uw_Sqlcache_timeMax(time_t x, time_t y) { return difftime(x, y) > 0 ? x : y; } -void uw_Sqlcache_freeuw_Sqlcache_CacheValue(uw_Sqlcache_CacheValue *value) { +void uw_Sqlcache_free(uw_Sqlcache_CacheValue *value) { if (value) { free(value->result); free(value->output); @@ -4554,7 +4566,7 @@ void uw_Sqlcache_freeuw_Sqlcache_CacheValue(uw_Sqlcache_CacheValue *value) { void uw_Sqlcache_delete(uw_Sqlcache_Cache *cache, uw_Sqlcache_CacheEntry* entry) { //uw_Sqlcache_listUw_Sqlcache_Delete(cache->lru, entry); HASH_DELETE(hh, cache->table, entry); - uw_Sqlcache_freeuw_Sqlcache_CacheValue(entry->value); + uw_Sqlcache_free(entry->value); free(entry->key); free(entry); } @@ -4595,7 +4607,7 @@ void uw_Sqlcache_storeHelper(uw_Sqlcache_Cache *cache, char **keys, uw_Sqlcache_ entry->timeValid = timeNow; if (cache->height == 0) { //uw_Sqlcache_listAdd(cache->lru, entry); - uw_Sqlcache_freeuw_Sqlcache_CacheValue(entry->value); + uw_Sqlcache_free(entry->value); entry->value = value; //if (cache->lru->size > MAX_SIZE) { //uw_Sqlcache_delete(cache, cache->lru->first); diff --git a/src/lru_cache.sml b/src/lru_cache.sml index b8dfde5e..275c3061 100644 --- a/src/lru_cache.sml +++ b/src/lru_cache.sml @@ -91,7 +91,8 @@ fun setupQuery {index, params} = newline, string (" uw_Sqlcache_CacheValue *v = uw_Sqlcache_check(cache" ^ i ^ ", ks);"), newline, - string " if (v) {", + (* If the output is null, it means we had too much recursion, so it's a miss. *) + string " if (v && v->output != NULL) {", newline, string (" puts(\"SQLCACHE: hit " ^ i ^ ".\");"), newline, diff --git a/src/mono_fooify.sml b/src/mono_fooify.sml index b7d0b6c6..bbd34b15 100644 --- a/src/mono_fooify.sml +++ b/src/mono_fooify.sml @@ -127,9 +127,13 @@ fun capitalize s = structure E = ErrorMsg +exception TypeMismatch of Fm.t * E.span +exception CantPass of Fm.t * typ +exception DontKnow of Fm.t * typ + val dummyExp = (EPrim (Prim.Int 0), E.dummySpan) -fun fooifyExp fk lookupENamed lookupDatatype = +fun fooifyExpWithExceptions fk lookupENamed lookupDatatype = let fun fooify fm (e, tAll as (t, loc)) = case #1 e of @@ -155,8 +159,7 @@ fun fooifyExp fk lookupENamed lookupDatatype = arg'), loc)), loc), fm) end - | _ => (E.errorAt loc "Type mismatch encoding attribute"; - (e, fm)) + | _ => raise TypeMismatch (fm, loc) in attrify (args, ft, (EPrim (Prim.String (Prim.Normal, Settings.getUrlPrefix () ^ s)), loc), fm) end @@ -165,10 +168,8 @@ fun fooifyExp fk lookupENamed lookupDatatype = TFfi ("Basis", "unit") => ((EPrim (Prim.String (Prim.Normal, "")), loc), fm) | TFfi (m, x) => (if Settings.mayClientToServer (m, x) (* TODO: better error message. (Then again, user should never see this.) *) - then () - else (E.errorAt loc "MonoFooify: can't pass type from client to server"; - Print.eprefaces' [("Type", MonoPrint.p_typ MonoEnv.empty tAll)]); - ((EFfiApp (m, fk2s fk ^ "ify" ^ capitalize x, [(e, tAll)]), loc), fm)) + then ((EFfiApp (m, fk2s fk ^ "ify" ^ capitalize x, [(e, tAll)]), loc), fm) + else raise CantPass (fm, tAll)) | TRecord [] => ((EPrim (Prim.String (Prim.Normal, "")), loc), fm) | TRecord ((x, t) :: xts) => @@ -291,38 +292,50 @@ fun fooifyExp fk lookupENamed lookupDatatype = ((EApp ((ENamed n, loc), e), loc), fm) end - | _ => (E.errorAt loc "Don't know how to encode attribute/URL type"; - Print.eprefaces' [("Type", MonoPrint.p_typ MonoEnv.empty tAll)]; - (dummyExp, fm)) + | _ => raise DontKnow (fm, tAll) in fooify end +fun fooifyExp fk lookupENamed lookupDatatype fm exp = + fooifyExpWithExceptions fk lookupENamed lookupDatatype fm exp + handle TypeMismatch (fm, loc) => + (E.errorAt loc "Type mismatch encoding attribute"; + (dummyExp, fm)) + | CantPass (fm, typ as (_, loc)) => + (E.errorAt loc "MonoFooify: can't pass type from client to server"; + Print.eprefaces' [("Type", MonoPrint.p_typ MonoEnv.empty typ)]; + (dummyExp, fm)) + | DontKnow (fm, typ as (_, loc)) => + (E.errorAt loc "Don't know how to encode attribute/URL type"; + Print.eprefaces' [("Type", MonoPrint.p_typ MonoEnv.empty typ)]; + (dummyExp, fm)) + + (* Has to be set at the end of [Monoize]. *) val canonicalFm = ref (Fm.empty 0 : Fm.t) fun urlify env expTyp = - if ErrorMsg.anyErrors () - then ((* DEBUG *) print "already error"; NONE) - else - let - val (exp, fm) = - fooifyExp - Url - (fn n => - let - val (_, t, _, s) = MonoEnv.lookupENamed env n - in - (t, s) - end) - (fn n => MonoEnv.lookupDatatype env n) - (!canonicalFm) - expTyp - in - if ErrorMsg.anyErrors () - then ((* DEBUG *) print "why"; (ErrorMsg.resetErrors (); NONE)) - else (canonicalFm := fm; SOME exp) - end + let + val (exp, fm) = + fooifyExpWithExceptions + Url + (fn n => + let + val (_, t, _, s) = MonoEnv.lookupENamed env n + in + (t, s) + end) + (fn n => MonoEnv.lookupDatatype env n) + (!canonicalFm) + expTyp + in + canonicalFm := fm; + SOME exp + end + handle TypeMismatch _ => NONE + | CantPass _ => NONE + | DontKnow _ => NONE fun getNewFmDecls () = let diff --git a/src/sqlcache.sml b/src/sqlcache.sml index 4d4c7d36..dd851787 100644 --- a/src/sqlcache.sml +++ b/src/sqlcache.sml @@ -53,8 +53,9 @@ fun getCache () = !cache (* Used to have type context for local variables in MonoUtil functions. *) val doBind = - fn (env, MonoUtil.Exp.RelE (s, t)) => MonoEnv.pushERel env s t NONE - | (env, _) => env + fn (env, MonoUtil.Exp.RelE (x, t)) => MonoEnv.pushERel env x t NONE + | (env, MonoUtil.Exp.NamedE (x, n, t, eo, s)) => MonoEnv.pushENamed env x n t eo s + | (env, MonoUtil.Exp.Datatype (x, n, cs)) => MonoEnv.pushDatatype env x n cs (*******************) @@ -499,8 +500,6 @@ fun cacheWrap (env, exp, resultTyp, args, i) = let val loc = dummyLoc val rel0 = (ERel 0, loc) - (* DEBUG *) - val () = print (Int.toString i ^ "\n") in case MonoFooify.urlify env (rel0, resultTyp) of NONE => NONE @@ -524,7 +523,42 @@ fun cacheWrap (env, exp, resultTyp, args, i) = end end -fun fileMapfoldB doExp file start = +fun fileTopLevelMapfoldB doTopLevelExp (decls, sideInfo) state = + let + fun doVal env ((x, n, t, exp, s), state) = + let + val (exp, state) = doTopLevelExp env exp state + in + ((x, n, t, exp, s), state) + end + fun doDecl' env (decl', state) = + case decl' of + DVal v => + let + val (v, state) = doVal env (v, state) + in + (DVal v, state) + end + | DValRec vs => + let + val (vs, state) = ListUtil.foldlMap (doVal env) state vs + in + (DValRec vs, state) + end + | _ => (decl', state) + fun doDecl (decl as (decl', loc), (env, state)) = + let + val env = MonoEnv.declBinds env decl + val (decl', state) = doDecl' env (decl', state) + in + ((decl', loc), (env, state)) + end + val (decls, (_, state)) = (ListUtil.foldlMap doDecl (MonoEnv.empty, state) decls) + in + ((decls, sideInfo), state) + end + +fun fileAllMapfoldB doExp file start = case MonoUtil.File.mapfoldB {typ = Search.return2, exp = fn env => fn e' => fn s => Search.Continue (doExp env e' s), @@ -534,7 +568,7 @@ fun fileMapfoldB doExp file start = Search.Continue x => x | Search.Return _ => raise Match -fun fileMap doExp file = #1 (fileMapfoldB (fn _ => fn e => fn _ => (doExp e, ())) file ()) +fun fileMap doExp file = #1 (fileAllMapfoldB (fn _ => fn e => fn _ => (doExp e, ())) file ()) fun factorOutNontrivial text = let @@ -623,7 +657,7 @@ fun addChecking file = end | e' => (e', queryInfo) in - (fileMapfoldB (fn env => fn exp => fn state => doExp env state exp) + (fileAllMapfoldB (fn env => fn exp => fn state => doExp env state exp) file (SIMM.empty, IM.empty, 0), effs) @@ -675,8 +709,8 @@ end val invalidations = Invalidations.invalidations (* DEBUG *) -val gunk : ((Sql.query * int) * Sql.dml) list ref = ref [] -val gunk' : exp list ref = ref [] +(* val gunk : ((Sql.query * int) * Sql.dml) list ref = ref [] *) +(* val gunk' : exp list ref = ref [] *) fun addFlushing ((file, (tableToIndices, indexToQueryNumArgs, index)), effs) = let @@ -686,19 +720,19 @@ fun addFlushing ((file, (tableToIndices, indexToQueryNumArgs, index)), effs) = fn EDml (origDmlText, failureMode) => let (* DEBUG *) - val () = gunk' := origDmlText :: !gunk' + (* val () = gunk' := origDmlText :: !gunk' *) val (newDmlText, wrapLets, numArgs) = factorOutNontrivial origDmlText val dmlText = incRels numArgs newDmlText val dmlExp = EDml (dmlText, failureMode) (* DEBUG *) - val () = Print.preface ("sqlcache> ", (MonoPrint.p_exp MonoEnv.empty origDmlText)) + (* val () = Print.preface ("SQLCACHE: ", (MonoPrint.p_exp MonoEnv.empty origDmlText)) *) val inval = case Sql.parse Sql.dml dmlText of SOME dmlParsed => SOME (map (fn i => (case IM.find (indexToQueryNumArgs, i) of SOME queryNumArgs => (* DEBUG *) - (gunk := (queryNumArgs, dmlParsed) :: !gunk; + ((* gunk := (queryNumArgs, dmlParsed) :: !gunk; *) (i, invalidations (queryNumArgs, dmlParsed))) (* TODO: fail more gracefully. *) | NONE => raise Match)) @@ -713,7 +747,7 @@ fun addFlushing ((file, (tableToIndices, indexToQueryNumArgs, index)), effs) = | e' => e' in (* DEBUG *) - gunk := []; + (* gunk := []; *) (fileMap doExp file, index, effs) end @@ -957,52 +991,37 @@ fun pureCache (effs : IS.set) ((env, exp as (exp', loc)), index) : subexp * int index + 1) end -fun addPure ((decls, sideInfo), indexStart, effs) = +fun addPure (file, indexStart, effs) = let - fun doVal env ((x, n, t, exp, s), index) = + fun doTopLevelExp env exp index = let val (subexp, index) = pureCache effs ((env, exp), index) in - ((x, n, t, expOfSubexp subexp, s), index) - end - fun doDecl' env (decl', index) = - case decl' of - DVal v => - let - val (v, index) = doVal env (v, index) - in - (DVal v, index) - end - | DValRec vs => - let - val (vs, index) = ListUtil.foldlMap (doVal env) index vs - in - (DValRec vs, index) - end - | _ => (decl', index) - fun doDecl (decl as (decl', loc), (revDecls, env, index)) = - let - val env = MonoEnv.declBinds env decl - val (decl', index) = doDecl' env (decl', index) - (* Important that this happens after [MonoFooify.urlify] calls! *) - val fmDecls = MonoFooify.getNewFmDecls () - in - ((decl', loc) :: (fmDecls @ revDecls), env, index) + (expOfSubexp subexp, index) end in - (rev (#1 (List.foldl doDecl ([], MonoEnv.empty, indexStart) decls)), sideInfo) + #1 (fileTopLevelMapfoldB doTopLevelExp file indexStart) + end + +fun insertAfterDatatypes ((decls, sideInfo), newDecls) = + let + val (datatypes, others) = List.partition (fn (DDatatype _, _) => true | _ => false) decls + in + (datatypes @ newDecls @ others, sideInfo) end -val go' = addPure o addFlushing o addChecking (* DEBUG: add back [o inlineSql]. *) +val go' = addPure o addFlushing o addChecking o inlineSql fun go file = let (* TODO: do something nicer than [Sql] being in one of two modes. *) val () = (resetFfiInfo (); Sql.sqlcacheMode := true) - val file' = go' file + val file = go' file + (* Important that this happens after [MonoFooify.urlify] calls! *) + val fmDecls = MonoFooify.getNewFmDecls () val () = Sql.sqlcacheMode := false in - file' + insertAfterDatatypes (file, rev fmDecls) end end diff --git a/src/toy_cache.sml b/src/toy_cache.sml index 34a7a26f..cfde027b 100644 --- a/src/toy_cache.sml +++ b/src/toy_cache.sml @@ -95,7 +95,7 @@ fun setupQuery {index, params} = string args, string ") {", newline, - string "if (cacheQuery", + string "if (cacheWrite", string i, (* ASK: is returning the pointer okay? Should we duplicate? *) string " == NULL", @@ -116,9 +116,11 @@ fun setupQuery {index, params} = string i, string ".\");", newline, - string "uw_write(ctx, cacheWrite", + string " if (cacheWrite", string i, - string ");", + string " != NULL) { uw_write(ctx, cacheWrite", + string i, + string "); }", newline, string "return cacheQuery", string i, @@ -176,6 +178,14 @@ fun setupQuery {index, params} = string i, string " = NULL;", newline, + string "free(cacheWrite", + string i, + string ");", + newline, + string "cacheWrite", + string i, + string " = NULL;", + newline, string "puts(\"SQLCACHE: flush ", string i, string ".\");}", -- cgit v1.2.3 From e86ed0717e35bea1ad6127d193e5979aec4841b9 Mon Sep 17 00:00:00 2001 From: Ziv Scully Date: Wed, 14 Oct 2015 00:07:00 -0400 Subject: Hard-code Sqlcache module (in Ur/Web) as effectful and reorder sqlcache.sml. --- src/lru_cache.sml | 8 +- src/mono_fooify.sml | 2 - src/settings.sml | 3 +- src/sqlcache.sml | 478 +++++++++++++++++++++++++++------------------------- src/toy_cache.sml | 8 +- 5 files changed, 250 insertions(+), 249 deletions(-) (limited to 'src/lru_cache.sml') diff --git a/src/lru_cache.sml b/src/lru_cache.sml index 275c3061..e69624d8 100644 --- a/src/lru_cache.sml +++ b/src/lru_cache.sml @@ -13,13 +13,7 @@ val optionStringTyp = (TOption stringTyp, dummyLoc) fun withTyp typ = map (fn exp => (exp, typ)) fun ffiAppCache' (func, index, argTyps) = - let - val m = "Sqlcache" - val f = func ^ Int.toString index - in - Settings.addEffectful (m, f); - EFfiApp (m, f, argTyps) - end + EFfiApp ("Sqlcache", func ^ Int.toString index, argTyps) fun check (index, keys) = ffiAppCache' ("check", index, withTyp stringTyp keys) diff --git a/src/mono_fooify.sml b/src/mono_fooify.sml index bbd34b15..e64207cd 100644 --- a/src/mono_fooify.sml +++ b/src/mono_fooify.sml @@ -167,7 +167,6 @@ fun fooifyExpWithExceptions fk lookupENamed lookupDatatype = case t of TFfi ("Basis", "unit") => ((EPrim (Prim.String (Prim.Normal, "")), loc), fm) | TFfi (m, x) => (if Settings.mayClientToServer (m, x) - (* TODO: better error message. (Then again, user should never see this.) *) then ((EFfiApp (m, fk2s fk ^ "ify" ^ capitalize x, [(e, tAll)]), loc), fm) else raise CantPass (fm, tAll)) @@ -311,7 +310,6 @@ fun fooifyExp fk lookupENamed lookupDatatype fm exp = Print.eprefaces' [("Type", MonoPrint.p_typ MonoEnv.empty typ)]; (dummyExp, fm)) - (* Has to be set at the end of [Monoize]. *) val canonicalFm = ref (Fm.empty 0 : Fm.t) diff --git a/src/settings.sml b/src/settings.sml index ff99bf13..ecf353cd 100644 --- a/src/settings.sml +++ b/src/settings.sml @@ -151,7 +151,8 @@ val effectfulBase = basis ["dml", val effectful = ref effectfulBase fun setEffectful ls = effectful := S.addList (effectfulBase, ls) -fun isEffectful x = S.member (!effectful, x) +fun isEffectful ("Sqlcache", _) = true + | isEffectful x = S.member (!effectful, x) fun addEffectful x = effectful := S.add (!effectful, x) val benignBase = basis ["get_cookie", diff --git a/src/sqlcache.sml b/src/sqlcache.sml index f3db5795..1a4d4e97 100644 --- a/src/sqlcache.sml +++ b/src/sqlcache.sml @@ -15,7 +15,7 @@ fun iterate f n x = if n < 0 then x else iterate f (n-1) (f x) -(* Filled in by [cacheWrap] during [Sqlcache]. *) +(* Filled in by [cacheWrap]. *) val ffiInfo : {index : int, params : int} list ref = ref [] fun resetFfiInfo () = ffiInfo := [] @@ -41,8 +41,7 @@ val ffiEffectful = "urlifyBool_w", "urlifyChannel_w"] in - (* ASK: nicer way than using [Settings.addEffectful] for each Sqlcache - function? Right now they're all always effectful. *) + (* ASK: is it okay to hardcode Sqlcache functions as effectful? *) fn (m, f) => Settings.isEffectful (m, f) andalso not (m = "Basis" andalso SS.member (okayWrites, f)) end @@ -456,9 +455,9 @@ val tableDml = | Sql.Update (tab, _, _) => tab -(***************************) -(* Program Instrumentation *) -(***************************) +(*************************************) +(* Program Instrumentation Utilities *) +(*************************************) val varName = let @@ -496,33 +495,6 @@ fun incRels inc = bind = fn (bound, MonoUtil.Exp.RelE _) => bound + 1 | (bound, _) => bound} 0 -fun cacheWrap (env, exp, resultTyp, args, i) = - let - val loc = dummyLoc - val rel0 = (ERel 0, loc) - in - case MonoFooify.urlify env (rel0, resultTyp) of - NONE => NONE - | SOME urlified => - let - val () = ffiInfo := {index = i, params = length args} :: !ffiInfo - (* We ensure before this step that all arguments aren't effectful. - by turning them into local variables as needed. *) - val argsInc = map (incRels 1) args - val check = (check (i, args), loc) - val store = (store (i, argsInc, urlified), loc) - in - SOME (ECase - (check, - [((PNone stringTyp, loc), - (ELet (varName "q", resultTyp, exp, (ESeq (store, rel0), loc)), loc)), - ((PSome (stringTyp, (PVar (varName "hit", stringTyp), loc)), loc), - (* Boolean is false because we're not unurlifying from a cookie. *) - (EUnurlify (rel0, resultTyp, false), loc))], - {disc = (TOption stringTyp, loc), result = resultTyp})) - end - end - fun fileTopLevelMapfoldB doTopLevelExp (decls, sideInfo) state = let fun doVal env ((x, n, t, exp, s), state) = @@ -570,205 +542,6 @@ fun fileAllMapfoldB doExp file start = fun fileMap doExp file = #1 (fileAllMapfoldB (fn _ => fn e => fn _ => (doExp e, ())) file ()) -fun factorOutNontrivial text = - let - val loc = dummyLoc - fun strcat (e1, e2) = (EStrcat (e1, e2), loc) - val chunks = Sql.chunkify text - val (newText, newVariables) = - (* Important that this is foldr (to oppose foldl below). *) - List.foldr - (fn (chunk, (qText, newVars)) => - (* Variable bound to the head of newBs will have the lowest index. *) - case chunk of - Sql.Exp (e as (EPrim _, _)) => (strcat (e, qText), newVars) - | Sql.Exp e => - let - val n = length newVars - in - (* This is the (n+1)th new variable, so there are - already n new variables bound, so we increment - indices by n. *) - (strcat ((ERel (~(n+1)), loc), qText), incRels n e :: newVars) - end - | Sql.String s => (strcat (stringExp s, qText), newVars)) - (stringExp "", []) - chunks - fun wrapLets e' = - (* Important that this is foldl (to oppose foldr above). *) - List.foldl (fn (v, e') => ELet (varName "sqlArg", stringTyp, v, (e', loc))) - e' - newVariables - val numArgs = length newVariables - in - (newText, wrapLets, numArgs) - end - -fun cacheQuery effs env (queryInfo as (tableToIndices, indexToQueryNumArgs, index)) = - fn e' as EQuery {query = origQueryText, - state = resultTyp, - initial, body, tables, exps} => - let - val (newQueryText, wrapLets, numArgs) = factorOutNontrivial origQueryText - (* Increment once for each new variable just made. *) - val queryExp = incRels numArgs - (EQuery {query = newQueryText, - state = resultTyp, - initial = initial, - body = body, - tables = tables, - exps = exps}, - dummyLoc) - (* DEBUG *) - (* val () = Print.preface ("sqlcache> ", MonoPrint.p_exp MonoEnv.empty queryText) *) - val args = List.tabulate (numArgs, fn n => (ERel n, dummyLoc)) - fun bind x f = Option.mapPartial f x - fun guard b x = if b then x else NONE - (* We use dummyTyp here. I think this is okay because databases don't - store (effectful) functions, but perhaps there's some pathalogical - corner case missing.... *) - fun safe bound = - not - o effectful effs - (iterate (fn env => MonoEnv.pushERel env "_" dummyTyp NONE) - bound - env) - val textOfQuery = fn (EQuery {query, ...}, _) => SOME query | _ => NONE - val attempt = - (* Ziv misses Haskell's do notation.... *) - bind (textOfQuery queryExp) (fn queryText => - guard (safe 0 queryText andalso safe 0 initial andalso safe 2 body) ( - bind (Sql.parse Sql.query queryText) (fn queryParsed => - bind (cacheWrap (env, queryExp, resultTyp, args, index)) (fn cachedExp => - SOME (wrapLets cachedExp, - (SS.foldr (fn (tab, qi) => SIMM.insert (qi, tab, index)) - tableToIndices - (tablesQuery queryParsed), - IM.insert (indexToQueryNumArgs, index, (queryParsed, numArgs)), - index + 1)))))) - in - case attempt of - SOME pair => pair - (* We have to increment index conservatively. *) - (* TODO: just use a reference for current index.... *) - | NONE => (e', (tableToIndices, indexToQueryNumArgs, index + 1)) - end - | e' => (e', queryInfo) - -fun addChecking file = - let - val effs = effectfulDecls file - in - (fileAllMapfoldB (fn env => fn exp => fn state => cacheQuery effs env state exp) - file - (SIMM.empty, IM.empty, 0), - effs) - end - -structure Invalidations = struct - - val loc = dummyLoc - - val optionAtomExpToExp = - fn NONE => (ENone stringTyp, loc) - | SOME e => (ESome (stringTyp, - (case e of - DmlRel n => ERel n - | Prim p => EPrim p - (* TODO: make new type containing only these two. *) - | _ => raise Match, - loc)), - loc) - - fun eqsToInvalidation numArgs eqs = - let - fun inv n = if n < 0 then [] else IM.find (eqs, n) :: inv (n - 1) - in - inv (numArgs - 1) - end - - (* Tests if [ys] makes [xs] a redundant cache invalidation. [NONE] here - represents unknown, which means a wider invalidation. *) - val rec madeRedundantBy : atomExp option list * atomExp option list -> bool = - fn ([], []) => true - | (_ :: xs, NONE :: ys) => madeRedundantBy (xs, ys) - | (SOME x :: xs, SOME y :: ys) => (case AtomExpKey.compare (x, y) of - EQUAL => madeRedundantBy (xs, ys) - | _ => false) - | _ => false - - fun eqss (query, dml) = conflictMaps (queryToFormula query, dmlToFormula dml) - - fun invalidations ((query, numArgs), dml) = - (map (map optionAtomExpToExp) - o removeRedundant madeRedundantBy - o map (eqsToInvalidation numArgs) - o eqss) - (query, dml) - -end - -val invalidations = Invalidations.invalidations - -(* DEBUG *) -(* val gunk : ((Sql.query * int) * Sql.dml) list ref = ref [] *) -(* val gunk' : exp list ref = ref [] *) - -fun addFlushing ((file, (tableToIndices, indexToQueryNumArgs, index)), effs) = - let - val flushes = List.concat - o map (fn (i, argss) => map (fn args => flush (i, args)) argss) - val doExp = - fn EDml (origDmlText, failureMode) => - let - (* DEBUG *) - (* val () = gunk' := origDmlText :: !gunk' *) - val (newDmlText, wrapLets, numArgs) = factorOutNontrivial origDmlText - val dmlText = incRels numArgs newDmlText - val dmlExp = EDml (dmlText, failureMode) - (* DEBUG *) - val () = Print.preface ("SQLCACHE: ", (MonoPrint.p_exp MonoEnv.empty origDmlText)) - val inval = - case Sql.parse Sql.dml dmlText of - SOME dmlParsed => - SOME (map (fn i => (case IM.find (indexToQueryNumArgs, i) of - SOME queryNumArgs => - (* DEBUG *) - ((* gunk := (queryNumArgs, dmlParsed) :: !gunk; *) - (i, invalidations (queryNumArgs, dmlParsed))) - (* TODO: fail more gracefully. *) - | NONE => raise Match)) - (SIMM.findList (tableToIndices, tableDml dmlParsed))) - | NONE => NONE - in - case inval of - (* TODO: fail more gracefully. *) - NONE => raise Match - | SOME invs => wrapLets (sequence (flushes invs @ [dmlExp])) - end - | e' => e' - in - (* DEBUG *) - (* gunk := []; *) - (fileMap doExp file, index, effs) - end - -val inlineSql = - let - val doExp = - (* TODO: EQuery, too? *) - (* ASK: should this live in [MonoOpt]? *) - fn EDml ((ECase (disc, cases, {disc = dTyp, ...}), loc), failureMode) => - let - val newCases = map (fn (p, e) => (p, (EDml (e, failureMode), loc))) cases - in - ECase (disc, newCases, {disc = dTyp, result = (TRecord [], loc)}) - end - | e => e - in - fileMap doExp - end - (**********************) (* Mono Type Checking *) @@ -830,6 +603,33 @@ and typOfExp env (e', loc) = typOfExp' env e' (* Caching Pure Subexpressions *) (*******************************) +fun cacheWrap (env, exp, resultTyp, args, i) = + let + val loc = dummyLoc + val rel0 = (ERel 0, loc) + in + case MonoFooify.urlify env (rel0, resultTyp) of + NONE => NONE + | SOME urlified => + let + val () = ffiInfo := {index = i, params = length args} :: !ffiInfo + (* We ensure before this step that all arguments aren't effectful. + by turning them into local variables as needed. *) + val argsInc = map (incRels 1) args + val check = (check (i, args), loc) + val store = (store (i, argsInc, urlified), loc) + in + SOME (ECase + (check, + [((PNone stringTyp, loc), + (ELet (varName "q", resultTyp, exp, (ESeq (store, rel0), loc)), loc)), + ((PSome (stringTyp, (PVar (varName "hit", stringTyp), loc)), loc), + (* Boolean is false because we're not unurlifying from a cookie. *) + (EUnurlify (rel0, resultTyp, false), loc))], + {disc = (TOption stringTyp, loc), result = resultTyp})) + end + end + val freeVars = IS.listItems o MonoUtil.Exp.foldB @@ -1005,6 +805,220 @@ fun addPure (file, indexStart, effs) = #1 (fileTopLevelMapfoldB doTopLevelExp file indexStart) end + +(***********************) +(* Caching SQL Queries *) +(***********************) + +fun factorOutNontrivial text = + let + val loc = dummyLoc + fun strcat (e1, e2) = (EStrcat (e1, e2), loc) + val chunks = Sql.chunkify text + val (newText, newVariables) = + (* Important that this is foldr (to oppose foldl below). *) + List.foldr + (fn (chunk, (qText, newVars)) => + (* Variable bound to the head of newBs will have the lowest index. *) + case chunk of + Sql.Exp (e as (EPrim _, _)) => (strcat (e, qText), newVars) + | Sql.Exp e => + let + val n = length newVars + in + (* This is the (n+1)th new variable, so there are + already n new variables bound, so we increment + indices by n. *) + (strcat ((ERel (~(n+1)), loc), qText), incRels n e :: newVars) + end + | Sql.String s => (strcat (stringExp s, qText), newVars)) + (stringExp "", []) + chunks + fun wrapLets e' = + (* Important that this is foldl (to oppose foldr above). *) + List.foldl (fn (v, e') => ELet (varName "sqlArg", stringTyp, v, (e', loc))) + e' + newVariables + val numArgs = length newVariables + in + (newText, wrapLets, numArgs) + end + +fun cacheQuery effs env (queryInfo as (tableToIndices, indexToQueryNumArgs, index)) = + fn e' as EQuery {query = origQueryText, + state = resultTyp, + initial, body, tables, exps} => + let + val (newQueryText, wrapLets, numArgs) = factorOutNontrivial origQueryText + (* Increment once for each new variable just made. *) + val queryExp = incRels numArgs + (EQuery {query = newQueryText, + state = resultTyp, + initial = initial, + body = body, + tables = tables, + exps = exps}, + dummyLoc) + (* DEBUG *) + (* val () = Print.preface ("sqlcache> ", MonoPrint.p_exp MonoEnv.empty queryText) *) + val args = List.tabulate (numArgs, fn n => (ERel n, dummyLoc)) + fun bind x f = Option.mapPartial f x + fun guard b x = if b then x else NONE + (* We use dummyTyp here. I think this is okay because databases don't + store (effectful) functions, but perhaps there's some pathalogical + corner case missing.... *) + fun safe bound = + not + o effectful effs + (iterate (fn env => MonoEnv.pushERel env "_" dummyTyp NONE) + bound + env) + val textOfQuery = fn (EQuery {query, ...}, _) => SOME query | _ => NONE + val attempt = + (* Ziv misses Haskell's do notation.... *) + bind (textOfQuery queryExp) (fn queryText => + guard (safe 0 queryText andalso safe 0 initial andalso safe 2 body) ( + bind (Sql.parse Sql.query queryText) (fn queryParsed => + bind (cacheWrap (env, queryExp, resultTyp, args, index)) (fn cachedExp => + SOME (wrapLets cachedExp, + (SS.foldr (fn (tab, qi) => SIMM.insert (qi, tab, index)) + tableToIndices + (tablesQuery queryParsed), + IM.insert (indexToQueryNumArgs, index, (queryParsed, numArgs)), + index + 1)))))) + in + case attempt of + SOME pair => pair + (* We have to increment index conservatively. *) + (* TODO: just use a reference for current index.... *) + | NONE => (e', (tableToIndices, indexToQueryNumArgs, index + 1)) + end + | e' => (e', queryInfo) + +fun addChecking file = + let + val effs = effectfulDecls file + in + (fileAllMapfoldB (fn env => fn exp => fn state => cacheQuery effs env state exp) + file + (SIMM.empty, IM.empty, 0), + effs) + end + + +(************) +(* Flushing *) +(************) + +structure Invalidations = struct + + val loc = dummyLoc + + val optionAtomExpToExp = + fn NONE => (ENone stringTyp, loc) + | SOME e => (ESome (stringTyp, + (case e of + DmlRel n => ERel n + | Prim p => EPrim p + (* TODO: make new type containing only these two. *) + | _ => raise Match, + loc)), + loc) + + fun eqsToInvalidation numArgs eqs = + let + fun inv n = if n < 0 then [] else IM.find (eqs, n) :: inv (n - 1) + in + inv (numArgs - 1) + end + + (* Tests if [ys] makes [xs] a redundant cache invalidation. [NONE] here + represents unknown, which means a wider invalidation. *) + val rec madeRedundantBy : atomExp option list * atomExp option list -> bool = + fn ([], []) => true + | (_ :: xs, NONE :: ys) => madeRedundantBy (xs, ys) + | (SOME x :: xs, SOME y :: ys) => (case AtomExpKey.compare (x, y) of + EQUAL => madeRedundantBy (xs, ys) + | _ => false) + | _ => false + + fun eqss (query, dml) = conflictMaps (queryToFormula query, dmlToFormula dml) + + fun invalidations ((query, numArgs), dml) = + (map (map optionAtomExpToExp) + o removeRedundant madeRedundantBy + o map (eqsToInvalidation numArgs) + o eqss) + (query, dml) + +end + +val invalidations = Invalidations.invalidations + +(* DEBUG *) +(* val gunk : ((Sql.query * int) * Sql.dml) list ref = ref [] *) +(* val gunk' : exp list ref = ref [] *) + +fun addFlushing ((file, (tableToIndices, indexToQueryNumArgs, index)), effs) = + let + val flushes = List.concat + o map (fn (i, argss) => map (fn args => flush (i, args)) argss) + val doExp = + fn EDml (origDmlText, failureMode) => + let + (* DEBUG *) + (* val () = gunk' := origDmlText :: !gunk' *) + val (newDmlText, wrapLets, numArgs) = factorOutNontrivial origDmlText + val dmlText = incRels numArgs newDmlText + val dmlExp = EDml (dmlText, failureMode) + (* DEBUG *) + (* val () = Print.preface ("SQLCACHE: ", (MonoPrint.p_exp MonoEnv.empty origDmlText)) *) + val inval = + case Sql.parse Sql.dml dmlText of + SOME dmlParsed => + SOME (map (fn i => (case IM.find (indexToQueryNumArgs, i) of + SOME queryNumArgs => + (* DEBUG *) + ((* gunk := (queryNumArgs, dmlParsed) :: !gunk; *) + (i, invalidations (queryNumArgs, dmlParsed))) + (* TODO: fail more gracefully. *) + | NONE => raise Match)) + (SIMM.findList (tableToIndices, tableDml dmlParsed))) + | NONE => NONE + in + case inval of + (* TODO: fail more gracefully. *) + NONE => raise Match + | SOME invs => wrapLets (sequence (flushes invs @ [dmlExp])) + end + | e' => e' + in + (* DEBUG *) + (* gunk := []; *) + (fileMap doExp file, index, effs) + end + + +(***************) +(* Entry point *) +(***************) + +val inlineSql = + let + val doExp = + (* TODO: EQuery, too? *) + (* ASK: should this live in [MonoOpt]? *) + fn EDml ((ECase (disc, cases, {disc = dTyp, ...}), loc), failureMode) => + let + val newCases = map (fn (p, e) => (p, (EDml (e, failureMode), loc))) cases + in + ECase (disc, newCases, {disc = dTyp, result = (TRecord [], loc)}) + end + | e => e + in + fileMap doExp + end + fun insertAfterDatatypes ((decls, sideInfo), newDecls) = let val (datatypes, others) = List.partition (fn (DDatatype _, _) => true | _ => false) decls diff --git a/src/toy_cache.sml b/src/toy_cache.sml index cfde027b..377cae01 100644 --- a/src/toy_cache.sml +++ b/src/toy_cache.sml @@ -13,13 +13,7 @@ val optionStringTyp = (TOption stringTyp, dummyLoc) fun withTyp typ = map (fn exp => (exp, typ)) fun ffiAppCache' (func, index, argTyps) = - let - val m = "Sqlcache" - val f = func ^ Int.toString index - in - Settings.addEffectful (m, f); - EFfiApp (m, f, argTyps) - end + EFfiApp ("Sqlcache", func ^ Int.toString index, argTyps) fun check (index, keys) = ffiAppCache' ("check", index, withTyp stringTyp keys) -- cgit v1.2.3 From 7b14b2f01fd0218c0bbe0a5c4071fff190c91ce1 Mon Sep 17 00:00:00 2001 From: Ziv Scully Date: Wed, 11 Nov 2015 20:01:48 -0500 Subject: Rewrite LRU cache. Now uses one big hash table and is less buggy. --- include/urweb/types_cpp.h | 29 +++--- include/urweb/urweb_cpp.h | 8 +- src/c/urweb.c | 240 ++++++++++++++++++++++++---------------------- src/lru_cache.sml | 15 +-- 4 files changed, 147 insertions(+), 145 deletions(-) (limited to 'src/lru_cache.sml') diff --git a/include/urweb/types_cpp.h b/include/urweb/types_cpp.h index 84423105..4847a3fd 100644 --- a/include/urweb/types_cpp.h +++ b/include/urweb/types_cpp.h @@ -123,31 +123,24 @@ typedef struct { #include "uthash.h" -typedef struct uw_Sqlcache_CacheValue { +typedef struct uw_Sqlcache_Value { char *result; char *output; -} uw_Sqlcache_CacheValue; + unsigned long timeValid; +} uw_Sqlcache_Value; -typedef struct uw_Sqlcache_CacheEntry { +typedef struct uw_Sqlcache_Entry { char *key; - void *value; - time_t timeValid; - struct uw_Sqlcache_CacheEntry *prev; - struct uw_Sqlcache_CacheEntry *next; + uw_Sqlcache_Value *value; + unsigned long timeInvalid; UT_hash_handle hh; -} uw_Sqlcache_CacheEntry; - -typedef struct uw_Sqlcache_CacheList { - uw_Sqlcache_CacheEntry *first; - uw_Sqlcache_CacheEntry *last; - int size; -} uw_Sqlcache_CacheList; +} uw_Sqlcache_Entry; typedef struct uw_Sqlcache_Cache { - uw_Sqlcache_CacheEntry *table; - time_t timeInvalid; - uw_Sqlcache_CacheList *lru; - int height; + struct uw_Sqlcache_Entry *table; + unsigned long timeInvalid; + unsigned long timeNow; + UT_hash_handle hh; } uw_Sqlcache_Cache; #endif diff --git a/include/urweb/urweb_cpp.h b/include/urweb/urweb_cpp.h index ab2a91c1..52e54372 100644 --- a/include/urweb/urweb_cpp.h +++ b/include/urweb/urweb_cpp.h @@ -405,10 +405,8 @@ void uw_Basis_writec(struct uw_context *, char); // Sqlcache. -#include "uthash.h" - -uw_Sqlcache_CacheValue *uw_Sqlcache_check(uw_Sqlcache_Cache *, char **); -uw_Sqlcache_CacheValue *uw_Sqlcache_store(uw_Sqlcache_Cache *, char **, uw_Sqlcache_CacheValue *); -uw_Sqlcache_CacheValue *uw_Sqlcache_flush(uw_Sqlcache_Cache *, char **); +uw_Sqlcache_Value *uw_Sqlcache_check(uw_Sqlcache_Cache *, char **, int); +void *uw_Sqlcache_store(uw_Sqlcache_Cache *, char **, int, uw_Sqlcache_Value *); +void *uw_Sqlcache_flush(uw_Sqlcache_Cache *, char **, int); #endif diff --git a/src/c/urweb.c b/src/c/urweb.c index ef7eb9bb..09d04f1c 100644 --- a/src/c/urweb.c +++ b/src/c/urweb.c @@ -4532,144 +4532,154 @@ void uw_set_remoteSock(uw_context ctx, int sock) { // Sqlcache -void uw_Sqlcache_listDelete(uw_Sqlcache_CacheList *list, uw_Sqlcache_CacheEntry *entry) { - if (list->first == entry) { - list->first = entry->next; - } - if (list->last == entry) { - list->last = entry->prev; - } - if (entry->prev) { - entry->prev->next = entry->next; - } - if (entry->next) { - entry->next->prev = entry->prev; +void uw_Sqlcache_freeValue(uw_Sqlcache_Value *value) { + if (value) { + free(value->result); + free(value->output); + free(value); } - entry->prev = NULL; - entry->next = NULL; - --(list->size); } -void uw_Sqlcache_listAdd(uw_Sqlcache_CacheList *list, uw_Sqlcache_CacheEntry *entry) { - if (list->last) { - list->last->next = entry; - entry->prev = list->last; - list->last = entry; - } else { - list->first = entry; - list->last = entry; +void uw_Sqlcache_freeEntry(uw_Sqlcache_Entry* entry) { + if (entry) { + free(entry->key); + uw_Sqlcache_freeValue(entry->value); + free(entry); } - ++(list->size); -} - -void uw_Sqlcache_listBump(uw_Sqlcache_CacheList *list, uw_Sqlcache_CacheEntry *entry) { - uw_Sqlcache_listDelete(list, entry); - uw_Sqlcache_listAdd(list, entry); } -// TODO: deal with time properly. +// TODO: pick a number. +unsigned int uw_Sqlcache_maxSize = 1234567890; -time_t uw_Sqlcache_getTimeNow() { - return time(NULL); +void uw_Sqlcache_delete(uw_Sqlcache_Cache *cache, uw_Sqlcache_Entry *entry) { + HASH_DEL(cache->table, entry); + uw_Sqlcache_freeEntry(entry); } -time_t uw_Sqlcache_timeMax(time_t x, time_t y) { - return difftime(x, y) > 0 ? x : y; +uw_Sqlcache_Entry *uw_Sqlcache_find(uw_Sqlcache_Cache *cache, char *key, size_t len, int bump) { + uw_Sqlcache_Entry *entry = NULL; + HASH_FIND(hh, cache->table, key, len, entry); + if (entry && bump) { + // Bump for LRU purposes. + HASH_DEL(cache->table, entry); + // Important that we use [entry->key], because [key] might be ephemeral. + HASH_ADD_KEYPTR(hh, cache->table, entry->key, len, entry); + } + return entry; } -void uw_Sqlcache_free(uw_Sqlcache_CacheValue *value) { - if (value) { - free(value->result); - free(value->output); - free(value); +void uw_Sqlcache_add(uw_Sqlcache_Cache *cache, uw_Sqlcache_Entry *entry, size_t len) { + HASH_ADD_KEYPTR(hh, cache->table, entry->key, len, entry); + if (HASH_COUNT(cache->table) > uw_Sqlcache_maxSize) { + // Deletes the first element of the cache. + uw_Sqlcache_delete(cache, cache->table); } } -void uw_Sqlcache_delete(uw_Sqlcache_Cache *cache, uw_Sqlcache_CacheEntry* entry) { - //uw_Sqlcache_listUw_Sqlcache_Delete(cache->lru, entry); - HASH_DELETE(hh, cache->table, entry); - uw_Sqlcache_free(entry->value); - free(entry->key); - free(entry); -} - -uw_Sqlcache_CacheValue *uw_Sqlcache_checkHelper(uw_Sqlcache_Cache *cache, char **keys, int timeInvalid) { - char *key = keys[cache->height]; - uw_Sqlcache_CacheEntry *entry; - HASH_FIND(hh, cache->table, key, strlen(key), entry); - timeInvalid = uw_Sqlcache_timeMax(timeInvalid, cache->timeInvalid); - if (entry && difftime(entry->timeValid, timeInvalid) > 0) { - if (cache->height == 0) { - // At height 0, entry->value is the desired value. - //uw_Sqlcache_listBump(cache->lru, entry); - return entry->value; - } else { - // At height n+1, entry->value is a pointer to a cache at heignt n. - return uw_Sqlcache_checkHelper(entry->value, keys, timeInvalid); - } - } else { - return NULL; - } +unsigned long uw_Sqlcache_getTimeNow(uw_Sqlcache_Cache *cache) { + return ++cache->timeNow; } -uw_Sqlcache_CacheValue *uw_Sqlcache_check(uw_Sqlcache_Cache *cache, char **keys) { - return uw_Sqlcache_checkHelper(cache, keys, 0); +unsigned long uw_Sqlcache_timeMax(unsigned long x, unsigned long y) { + return x > y ? x : y; } -void uw_Sqlcache_storeHelper(uw_Sqlcache_Cache *cache, char **keys, uw_Sqlcache_CacheValue *value, int timeNow) { - uw_Sqlcache_CacheEntry *entry; - char *key = keys[cache->height]; - HASH_FIND(hh, cache->table, key, strlen(key), entry); - if (!entry) { - entry = malloc(sizeof(uw_Sqlcache_CacheEntry)); - entry->key = strdup(key); - entry->value = NULL; - HASH_ADD_KEYPTR(hh, cache->table, entry->key, strlen(entry->key), entry); +char uw_Sqlcache_keySep = '_'; + +char *uw_Sqlcache_allocKeyBuffer(char **keys, int numKeys) { + size_t len = 0; + while (numKeys-- > 0) { + char* k = keys[numKeys]; + if (!k) { + // Can only happen when flushihg, in which case we don't need anything past the null key. + break; + } + // Leave room for separator. + len += 1 + strlen(k); } - entry->timeValid = timeNow; - if (cache->height == 0) { - //uw_Sqlcache_listAdd(cache->lru, entry); - uw_Sqlcache_free(entry->value); - entry->value = value; - //if (cache->lru->size > MAX_SIZE) { - //uw_Sqlcache_delete(cache, cache->lru->first); - // TODO: return flushed value. - //} - } else { - if (!entry->value) { - uw_Sqlcache_Cache *newuw_Sqlcache_Cache = malloc(sizeof(uw_Sqlcache_Cache)); - newuw_Sqlcache_Cache->table = NULL; - newuw_Sqlcache_Cache->timeInvalid = timeNow; - newuw_Sqlcache_Cache->lru = cache->lru; - newuw_Sqlcache_Cache->height = cache->height - 1; - entry->value = newuw_Sqlcache_Cache; + char *buf = malloc(len+1); + // If nothing is copied into the buffer, it should look like it has length 0. + buf[0] = 0; + return buf; +} + +char *uw_Sqlcache_keyCopy(char *buf, char *key) { + *buf++ = uw_Sqlcache_keySep; + return stpcpy(buf, key); +} + +// The NUL-terminated prefix of [key] below always looks something like "_k1_k2_k3..._kn". +// TODO: strlen(key) = buf - key? + +uw_Sqlcache_Value *uw_Sqlcache_check(uw_Sqlcache_Cache *cache, char **keys, int numKeys) { + char *key = uw_Sqlcache_allocKeyBuffer(keys, numKeys); + char *buf = key; + time_t timeInvalid = cache->timeInvalid; + uw_Sqlcache_Entry *entry; + while (numKeys-- > 0) { + buf = uw_Sqlcache_keyCopy(buf, keys[numKeys]); + size_t len = buf - key; + entry = uw_Sqlcache_find(cache, key, len, 1); + if (!entry) { + free(key); + return NULL; } - uw_Sqlcache_storeHelper(entry->value, keys, value, timeNow); + timeInvalid = uw_Sqlcache_timeMax(timeInvalid, entry->timeInvalid); } -} - -void uw_Sqlcache_store(uw_Sqlcache_Cache *cache, char **keys, uw_Sqlcache_CacheValue *value) { - uw_Sqlcache_storeHelper(cache, keys, value, uw_Sqlcache_getTimeNow()); -} - -void uw_Sqlcache_flushHelper(uw_Sqlcache_Cache *cache, char **keys, int timeNow) { - uw_Sqlcache_CacheEntry *entry; - char *key = keys[cache->height]; - if (key) { - HASH_FIND(hh, cache->table, key, strlen(key), entry); - if (entry) { - if (cache->height == 0) { - uw_Sqlcache_delete(cache, entry); + free(key); + uw_Sqlcache_Value *value = entry->value; + return value && value->timeValid > timeInvalid ? value : NULL; +} + +void uw_Sqlcache_store(uw_Sqlcache_Cache *cache, char **keys, int numKeys, uw_Sqlcache_Value *value) { + char *key = uw_Sqlcache_allocKeyBuffer(keys, numKeys); + char *buf = key; + time_t timeNow = uw_Sqlcache_getTimeNow(cache); + uw_Sqlcache_Entry *entry; + while (numKeys-- > 0) { + buf = uw_Sqlcache_keyCopy(buf, keys[numKeys]); + size_t len = buf - key; + entry = uw_Sqlcache_find(cache, key, len, 1); + if (!entry) { + entry = malloc(sizeof(uw_Sqlcache_Entry)); + entry->key = strdup(key); + entry->value = NULL; + entry->timeInvalid = 0; // ASK: is this okay? + uw_Sqlcache_add(cache, entry, len); + } + } + free(key); + uw_Sqlcache_freeValue(entry->value); + entry->value = value; + entry->value->timeValid = timeNow; +} + +void uw_Sqlcache_flush(uw_Sqlcache_Cache *cache, char **keys, int numKeys) { + char *key = uw_Sqlcache_allocKeyBuffer(keys, numKeys); + char *buf = key; + time_t timeNow = uw_Sqlcache_getTimeNow(cache); + uw_Sqlcache_Entry *entry; + while (numKeys-- > 0) { + char *k = keys[numKeys]; + if (!k) { + if (entry) { + entry->timeInvalid = timeNow; } else { - uw_Sqlcache_flushHelper(entry->value, keys, timeNow); + // Haven't found an entry yet, so the first key was null. + cache->timeInvalid = timeNow; } + free(key); + return; + } + buf = uw_Sqlcache_keyCopy(buf, k); + size_t len = buf - key; + entry = uw_Sqlcache_find(cache, key, len, 0); + if (!entry) { + free(key); + return; } - } else { - // Null key means invalidate the entire subtree. - cache->timeInvalid = timeNow; } -} - -void uw_Sqlcache_flush(uw_Sqlcache_Cache *cache, char **keys) { - uw_Sqlcache_flushHelper(cache, keys, uw_Sqlcache_getTimeNow()); + free(key); + // All the keys were non-null and the relevant entry is present, so we delete it. + uw_Sqlcache_delete(cache, entry); } diff --git a/src/lru_cache.sml b/src/lru_cache.sml index e69624d8..6fcfdc55 100644 --- a/src/lru_cache.sml +++ b/src/lru_cache.sml @@ -62,6 +62,8 @@ fun setupQuery {index, params} = val revArgs = paramRepeatRev (fn p => "p" ^ p) ", " + val numArgs = Int.toString params + in Print.box [string ("static uw_Sqlcache_Cache cacheStruct" ^ i ^ " = {"), @@ -70,9 +72,7 @@ fun setupQuery {index, params} = newline, string " .timeInvalid = 0,", newline, - string " .lru = NULL,", - newline, - string (" .height = " ^ Int.toString (params - 1) ^ "};"), + string " .timeNow = 0};", newline, string ("static uw_Sqlcache_Cache *cache" ^ i ^ " = &cacheStruct" ^ i ^ ";"), newline, @@ -83,7 +83,8 @@ fun setupQuery {index, params} = newline, string (" char *ks[] = {" ^ revArgs ^ "};"), newline, - string (" uw_Sqlcache_CacheValue *v = uw_Sqlcache_check(cache" ^ i ^ ", ks);"), + string " uw_Sqlcache_Value *v = ", + string ("uw_Sqlcache_check(cache" ^ i ^ ", ks, " ^ numArgs ^ ");"), newline, (* If the output is null, it means we had too much recursion, so it's a miss. *) string " if (v && v->output != NULL) {", @@ -113,7 +114,7 @@ fun setupQuery {index, params} = newline, string (" char *ks[] = {" ^ revArgs ^ "};"), newline, - string (" uw_Sqlcache_CacheValue *v = malloc(sizeof(uw_Sqlcache_CacheValue));"), + string (" uw_Sqlcache_Value *v = malloc(sizeof(uw_Sqlcache_Value));"), newline, string " v->result = strdup(s);", newline, @@ -121,7 +122,7 @@ fun setupQuery {index, params} = newline, string (" puts(\"SQLCACHE: stored " ^ i ^ ".\");"), newline, - string (" uw_Sqlcache_store(cache" ^ i ^ ", ks, v);"), + string (" uw_Sqlcache_store(cache" ^ i ^ ", ks, " ^ numArgs ^ ", v);"), newline, string " return uw_unit_v;", newline, @@ -134,7 +135,7 @@ fun setupQuery {index, params} = newline, string (" char *ks[] = {" ^ revArgs ^ "};"), newline, - string (" uw_Sqlcache_flush(cache" ^ i ^ ", ks);"), + string (" uw_Sqlcache_flush(cache" ^ i ^ ", ks, " ^ numArgs ^ ");"), newline, string " return uw_unit_v;", newline, -- cgit v1.2.3 From 011b7148c87f8b0d90abee2f454ef7689493e1f9 Mon Sep 17 00:00:00 2001 From: Ziv Scully Date: Thu, 12 Nov 2015 09:15:50 -0500 Subject: Simplify C interface. --- include/urweb/types_cpp.h | 1 + include/urweb/urweb_cpp.h | 6 +++--- src/c/urweb.c | 11 +++++++---- src/lru_cache.sml | 11 +++++------ 4 files changed, 16 insertions(+), 13 deletions(-) (limited to 'src/lru_cache.sml') diff --git a/include/urweb/types_cpp.h b/include/urweb/types_cpp.h index 4847a3fd..3955dcc8 100644 --- a/include/urweb/types_cpp.h +++ b/include/urweb/types_cpp.h @@ -140,6 +140,7 @@ typedef struct uw_Sqlcache_Cache { struct uw_Sqlcache_Entry *table; unsigned long timeInvalid; unsigned long timeNow; + size_t numKeys; UT_hash_handle hh; } uw_Sqlcache_Cache; diff --git a/include/urweb/urweb_cpp.h b/include/urweb/urweb_cpp.h index f89c432c..15bfffac 100644 --- a/include/urweb/urweb_cpp.h +++ b/include/urweb/urweb_cpp.h @@ -406,8 +406,8 @@ void uw_Basis_writec(struct uw_context *, char); // Sqlcache. -uw_Sqlcache_Value *uw_Sqlcache_check(uw_Sqlcache_Cache *, char **, int); -void *uw_Sqlcache_store(uw_Sqlcache_Cache *, char **, int, uw_Sqlcache_Value *); -void *uw_Sqlcache_flush(uw_Sqlcache_Cache *, char **, int); +uw_Sqlcache_Value *uw_Sqlcache_check(uw_Sqlcache_Cache *, char **); +void *uw_Sqlcache_store(uw_Sqlcache_Cache *, char **, uw_Sqlcache_Value *); +void *uw_Sqlcache_flush(uw_Sqlcache_Cache *, char **); #endif diff --git a/src/c/urweb.c b/src/c/urweb.c index 30619314..71130cc7 100644 --- a/src/c/urweb.c +++ b/src/c/urweb.c @@ -488,7 +488,7 @@ struct uw_context { char *output_buffer; size_t output_buffer_size; - // For caching. + // Sqlcache. int numRecording; int recordingOffset; @@ -4616,7 +4616,8 @@ char *uw_Sqlcache_keyCopy(char *buf, char *key) { // The NUL-terminated prefix of [key] below always looks something like "_k1_k2_k3..._kn". // TODO: strlen(key) = buf - key? -uw_Sqlcache_Value *uw_Sqlcache_check(uw_Sqlcache_Cache *cache, char **keys, int numKeys) { +uw_Sqlcache_Value *uw_Sqlcache_check(uw_Sqlcache_Cache *cache, char **keys) { + size_t numKeys = cache->numKeys; char *key = uw_Sqlcache_allocKeyBuffer(keys, numKeys); char *buf = key; time_t timeInvalid = cache->timeInvalid; @@ -4636,7 +4637,8 @@ uw_Sqlcache_Value *uw_Sqlcache_check(uw_Sqlcache_Cache *cache, char **keys, int return value && value->timeValid > timeInvalid ? value : NULL; } -void uw_Sqlcache_store(uw_Sqlcache_Cache *cache, char **keys, int numKeys, uw_Sqlcache_Value *value) { +void uw_Sqlcache_store(uw_Sqlcache_Cache *cache, char **keys, uw_Sqlcache_Value *value) { + size_t numKeys = cache->numKeys; char *key = uw_Sqlcache_allocKeyBuffer(keys, numKeys); char *buf = key; time_t timeNow = uw_Sqlcache_getTimeNow(cache); @@ -4659,7 +4661,8 @@ void uw_Sqlcache_store(uw_Sqlcache_Cache *cache, char **keys, int numKeys, uw_Sq entry->value->timeValid = timeNow; } -void uw_Sqlcache_flush(uw_Sqlcache_Cache *cache, char **keys, int numKeys) { +void uw_Sqlcache_flush(uw_Sqlcache_Cache *cache, char **keys) { + size_t numKeys = cache->numKeys; char *key = uw_Sqlcache_allocKeyBuffer(keys, numKeys); char *buf = key; time_t timeNow = uw_Sqlcache_getTimeNow(cache); diff --git a/src/lru_cache.sml b/src/lru_cache.sml index 6fcfdc55..d4da2849 100644 --- a/src/lru_cache.sml +++ b/src/lru_cache.sml @@ -62,14 +62,14 @@ fun setupQuery {index, params} = val revArgs = paramRepeatRev (fn p => "p" ^ p) ", " - val numArgs = Int.toString params - in Print.box [string ("static uw_Sqlcache_Cache cacheStruct" ^ i ^ " = {"), newline, string " .table = NULL,", newline, + string (" .numKeys = " ^ Int.toString params ^ ","), + newline, string " .timeInvalid = 0,", newline, string " .timeNow = 0};", @@ -83,8 +83,7 @@ fun setupQuery {index, params} = newline, string (" char *ks[] = {" ^ revArgs ^ "};"), newline, - string " uw_Sqlcache_Value *v = ", - string ("uw_Sqlcache_check(cache" ^ i ^ ", ks, " ^ numArgs ^ ");"), + string (" uw_Sqlcache_Value *v = uw_Sqlcache_check(cache" ^ i ^ ", ks);"), newline, (* If the output is null, it means we had too much recursion, so it's a miss. *) string " if (v && v->output != NULL) {", @@ -122,7 +121,7 @@ fun setupQuery {index, params} = newline, string (" puts(\"SQLCACHE: stored " ^ i ^ ".\");"), newline, - string (" uw_Sqlcache_store(cache" ^ i ^ ", ks, " ^ numArgs ^ ", v);"), + string (" uw_Sqlcache_store(cache" ^ i ^ ", ks, v);"), newline, string " return uw_unit_v;", newline, @@ -135,7 +134,7 @@ fun setupQuery {index, params} = newline, string (" char *ks[] = {" ^ revArgs ^ "};"), newline, - string (" uw_Sqlcache_flush(cache" ^ i ^ ", ks, " ^ numArgs ^ ");"), + string (" uw_Sqlcache_flush(cache" ^ i ^ ", ks);"), newline, string " return uw_unit_v;", newline, -- cgit v1.2.3 From fd7375f584790047731686345c8ce6fedee71435 Mon Sep 17 00:00:00 2001 From: Ziv Scully Date: Thu, 12 Nov 2015 11:44:21 -0500 Subject: Actually use transactional machinery for flushes this time. --- include/urweb/types_cpp.h | 10 +++------- include/urweb/urweb_cpp.h | 2 +- src/c/urweb.c | 28 ++++++++++++++++++++++++---- src/lru_cache.sml | 4 +++- 4 files changed, 31 insertions(+), 13 deletions(-) (limited to 'src/lru_cache.sml') diff --git a/include/urweb/types_cpp.h b/include/urweb/types_cpp.h index 3955dcc8..c4af2866 100644 --- a/include/urweb/types_cpp.h +++ b/include/urweb/types_cpp.h @@ -129,15 +129,11 @@ typedef struct uw_Sqlcache_Value { unsigned long timeValid; } uw_Sqlcache_Value; -typedef struct uw_Sqlcache_Entry { - char *key; - uw_Sqlcache_Value *value; - unsigned long timeInvalid; - UT_hash_handle hh; -} uw_Sqlcache_Entry; +typedef struct uw_Sqlcache_Entry uw_Sqlcache_Entry; typedef struct uw_Sqlcache_Cache { - struct uw_Sqlcache_Entry *table; + //pthread_rwlock_t *lock; + uw_Sqlcache_Entry *table; unsigned long timeInvalid; unsigned long timeNow; size_t numKeys; diff --git a/include/urweb/urweb_cpp.h b/include/urweb/urweb_cpp.h index 15bfffac..3e70b4ac 100644 --- a/include/urweb/urweb_cpp.h +++ b/include/urweb/urweb_cpp.h @@ -408,6 +408,6 @@ void uw_Basis_writec(struct uw_context *, char); uw_Sqlcache_Value *uw_Sqlcache_check(uw_Sqlcache_Cache *, char **); void *uw_Sqlcache_store(uw_Sqlcache_Cache *, char **, uw_Sqlcache_Value *); -void *uw_Sqlcache_flush(uw_Sqlcache_Cache *, char **); +void *uw_Sqlcache_flush(struct uw_context *, uw_Sqlcache_Cache *, char **); #endif diff --git a/src/c/urweb.c b/src/c/urweb.c index 55d89fd5..4db019fe 100644 --- a/src/c/urweb.c +++ b/src/c/urweb.c @@ -4545,6 +4545,13 @@ void uw_set_remoteSock(uw_context ctx, int sock) { // Sqlcache +typedef struct uw_Sqlcache_Entry { + char *key; + uw_Sqlcache_Value *value; + unsigned long timeInvalid; + UT_hash_handle hh; +} uw_Sqlcache_Entry; + void uw_Sqlcache_freeValue(uw_Sqlcache_Value *value) { if (value) { free(value->result); @@ -4599,7 +4606,7 @@ unsigned long uw_Sqlcache_timeMax(unsigned long x, unsigned long y) { char uw_Sqlcache_keySep = '_'; -char *uw_Sqlcache_allocKeyBuffer(char **keys, int numKeys) { +char *uw_Sqlcache_allocKeyBuffer(char **keys, size_t numKeys) { size_t len = 0; while (numKeys-- > 0) { char* k = keys[numKeys]; @@ -4625,6 +4632,7 @@ char *uw_Sqlcache_keyCopy(char *buf, char *key) { // TODO: strlen(key) = buf - key? uw_Sqlcache_Value *uw_Sqlcache_check(uw_Sqlcache_Cache *cache, char **keys) { + //pthread_rwlock_rdlock(cache->lock); size_t numKeys = cache->numKeys; char *key = uw_Sqlcache_allocKeyBuffer(keys, numKeys); char *buf = key; @@ -4642,10 +4650,12 @@ uw_Sqlcache_Value *uw_Sqlcache_check(uw_Sqlcache_Cache *cache, char **keys) { } free(key); uw_Sqlcache_Value *value = entry->value; + //pthread_rwlock_unlock(cache->lock); return value && value->timeValid > timeInvalid ? value : NULL; } void uw_Sqlcache_store(uw_Sqlcache_Cache *cache, char **keys, uw_Sqlcache_Value *value) { + //pthread_rwlock_wrlock(cache->lock); size_t numKeys = cache->numKeys; char *key = uw_Sqlcache_allocKeyBuffer(keys, numKeys); char *buf = key; @@ -4667,6 +4677,7 @@ void uw_Sqlcache_store(uw_Sqlcache_Cache *cache, char **keys, uw_Sqlcache_Value uw_Sqlcache_freeValue(entry->value); entry->value = value; entry->value->timeValid = timeNow; + //pthread_rwlock_unlock(cache->lock); } void uw_Sqlcache_flushCommitOne(uw_Sqlcache_Cache *cache, char **keys) { @@ -4717,20 +4728,28 @@ void uw_Sqlcache_flushFree(void *data, int dontCare) { void uw_Sqlcache_flushCommit(void *data) { uw_Sqlcache_Inval *inval = (uw_Sqlcache_Inval *)data; - uw_Sqlcache_Inval *invalFirst = inval; while (inval) { uw_Sqlcache_Cache *cache = inval->cache; char **keys = inval->keys; uw_Sqlcache_flushCommitOne(cache, keys); inval = inval->next; } - uw_Sqlcache_flushFree(invalFirst, 0); +} + +char **uw_Sqlcache_copyKeys(char **keys, size_t numKeys) { + char **copy = malloc(sizeof(char *) * numKeys); + while (numKeys-- > 0) { + char * k = keys[numKeys]; + copy[numKeys] = k ? strdup(k) : NULL; + } + return copy; } void uw_Sqlcache_flush(uw_context ctx, uw_Sqlcache_Cache *cache, char **keys) { + //pthread_rwlock_wrlock(cache->lock); uw_Sqlcache_Inval *inval = malloc(sizeof(uw_Sqlcache_Inval)); inval->cache = cache; - inval->keys = keys; + inval->keys = uw_Sqlcache_copyKeys(keys, cache->numKeys); inval->next = NULL; if (ctx->inval) { // An invalidation is already registered, so just extend it. @@ -4740,4 +4759,5 @@ void uw_Sqlcache_flush(uw_context ctx, uw_Sqlcache_Cache *cache, char **keys) { } // [ctx->inval] should always point to the last invalidation. ctx->inval = inval; + //pthread_rwlock_unlock(cache->lock); } diff --git a/src/lru_cache.sml b/src/lru_cache.sml index d4da2849..9d65420b 100644 --- a/src/lru_cache.sml +++ b/src/lru_cache.sml @@ -65,6 +65,8 @@ fun setupQuery {index, params} = in Print.box [string ("static uw_Sqlcache_Cache cacheStruct" ^ i ^ " = {"), + (* newline, *) + (* string " .lock = PTHREAD_RWLOCK_INITIALIZER,", *) newline, string " .table = NULL,", newline, @@ -134,7 +136,7 @@ fun setupQuery {index, params} = newline, string (" char *ks[] = {" ^ revArgs ^ "};"), newline, - string (" uw_Sqlcache_flush(cache" ^ i ^ ", ks);"), + string (" uw_Sqlcache_flush(ctx, cache" ^ i ^ ", ks);"), newline, string " return uw_unit_v;", newline, -- cgit v1.2.3 From 06464bd07cb1efbc9df4ca650978c14f4c20390a Mon Sep 17 00:00:00 2001 From: Ziv Scully Date: Thu, 12 Nov 2015 16:36:35 -0500 Subject: Fix committing multiple stores/flushes. Locking is WIP. --- include/urweb/types_cpp.h | 3 +- include/urweb/urweb_cpp.h | 4 +- src/c/urweb.c | 108 +++++++++++++++++++++++++++------------------- src/lru_cache.sml | 8 ++-- 4 files changed, 72 insertions(+), 51 deletions(-) (limited to 'src/lru_cache.sml') diff --git a/include/urweb/types_cpp.h b/include/urweb/types_cpp.h index c4af2866..82f8d30a 100644 --- a/include/urweb/types_cpp.h +++ b/include/urweb/types_cpp.h @@ -121,6 +121,7 @@ typedef struct { // Caching +#include #include "uthash.h" typedef struct uw_Sqlcache_Value { @@ -132,7 +133,7 @@ typedef struct uw_Sqlcache_Value { typedef struct uw_Sqlcache_Entry uw_Sqlcache_Entry; typedef struct uw_Sqlcache_Cache { - //pthread_rwlock_t *lock; + pthread_rwlock_t lock; uw_Sqlcache_Entry *table; unsigned long timeInvalid; unsigned long timeNow; diff --git a/include/urweb/urweb_cpp.h b/include/urweb/urweb_cpp.h index 3e70b4ac..2c032e7b 100644 --- a/include/urweb/urweb_cpp.h +++ b/include/urweb/urweb_cpp.h @@ -406,8 +406,8 @@ void uw_Basis_writec(struct uw_context *, char); // Sqlcache. -uw_Sqlcache_Value *uw_Sqlcache_check(uw_Sqlcache_Cache *, char **); -void *uw_Sqlcache_store(uw_Sqlcache_Cache *, char **, uw_Sqlcache_Value *); +uw_Sqlcache_Value *uw_Sqlcache_check(struct uw_context *, uw_Sqlcache_Cache *, char **); +void *uw_Sqlcache_store(struct uw_context *, uw_Sqlcache_Cache *, char **, uw_Sqlcache_Value *); void *uw_Sqlcache_flush(struct uw_context *, uw_Sqlcache_Cache *, char **); #endif diff --git a/src/c/urweb.c b/src/c/urweb.c index 4db019fe..4afc7297 100644 --- a/src/c/urweb.c +++ b/src/c/urweb.c @@ -424,11 +424,12 @@ typedef struct { void (*free)(void*); } global; -typedef struct uw_Sqlcache_Inval { +typedef struct uw_Sqlcache_Update { uw_Sqlcache_Cache *cache; char **keys; - struct uw_Sqlcache_Inval *next; -} uw_Sqlcache_Inval; + uw_Sqlcache_Value *value; + struct uw_Sqlcache_Update *next; +} uw_Sqlcache_Update; struct uw_context { uw_app *app; @@ -497,7 +498,8 @@ struct uw_context { // Sqlcache. int numRecording; int recordingOffset; - uw_Sqlcache_Inval *inval; + uw_Sqlcache_Update *cacheUpdate; + uw_Sqlcache_Update *cacheUpdateTail; int remoteSock; }; @@ -508,6 +510,7 @@ size_t uw_heap_max = SIZE_MAX; size_t uw_script_max = SIZE_MAX; uw_context uw_init(int id, uw_loggers *lg) { + puts("Initializing"); uw_context ctx = malloc(sizeof(struct uw_context)); ctx->app = NULL; @@ -585,7 +588,8 @@ uw_context uw_init(int id, uw_loggers *lg) { ctx->numRecording = 0; ctx->recordingOffset = 0; - ctx->inval = NULL; + ctx->cacheUpdate = NULL; + ctx->cacheUpdateTail = NULL; ctx->remoteSock = -1; @@ -4629,10 +4633,9 @@ char *uw_Sqlcache_keyCopy(char *buf, char *key) { } // The NUL-terminated prefix of [key] below always looks something like "_k1_k2_k3..._kn". -// TODO: strlen(key) = buf - key? -uw_Sqlcache_Value *uw_Sqlcache_check(uw_Sqlcache_Cache *cache, char **keys) { - //pthread_rwlock_rdlock(cache->lock); +uw_Sqlcache_Value *uw_Sqlcache_check(uw_context ctx, uw_Sqlcache_Cache *cache, char **keys) { + pthread_rwlock_rdlock(&cache->lock); size_t numKeys = cache->numKeys; char *key = uw_Sqlcache_allocKeyBuffer(keys, numKeys); char *buf = key; @@ -4644,18 +4647,20 @@ uw_Sqlcache_Value *uw_Sqlcache_check(uw_Sqlcache_Cache *cache, char **keys) { entry = uw_Sqlcache_find(cache, key, len, 1); if (!entry) { free(key); + pthread_rwlock_unlock(&cache->lock); return NULL; } timeInvalid = uw_Sqlcache_timeMax(timeInvalid, entry->timeInvalid); } free(key); + // TODO: pass back copy of value and free it in the generated code... or use uw_malloc? uw_Sqlcache_Value *value = entry->value; - //pthread_rwlock_unlock(cache->lock); + pthread_rwlock_unlock(&cache->lock); return value && value->timeValid > timeInvalid ? value : NULL; } -void uw_Sqlcache_store(uw_Sqlcache_Cache *cache, char **keys, uw_Sqlcache_Value *value) { - //pthread_rwlock_wrlock(cache->lock); +void uw_Sqlcache_storeCommitOne(uw_Sqlcache_Cache *cache, char **keys, uw_Sqlcache_Value *value) { + pthread_rwlock_wrlock(&cache->lock); size_t numKeys = cache->numKeys; char *key = uw_Sqlcache_allocKeyBuffer(keys, numKeys); char *buf = key; @@ -4669,7 +4674,7 @@ void uw_Sqlcache_store(uw_Sqlcache_Cache *cache, char **keys, uw_Sqlcache_Value entry = malloc(sizeof(uw_Sqlcache_Entry)); entry->key = strdup(key); entry->value = NULL; - entry->timeInvalid = 0; // ASK: is this okay? + entry->timeInvalid = 0; uw_Sqlcache_add(cache, entry, len); } } @@ -4677,10 +4682,11 @@ void uw_Sqlcache_store(uw_Sqlcache_Cache *cache, char **keys, uw_Sqlcache_Value uw_Sqlcache_freeValue(entry->value); entry->value = value; entry->value->timeValid = timeNow; - //pthread_rwlock_unlock(cache->lock); + pthread_rwlock_unlock(&cache->lock); } void uw_Sqlcache_flushCommitOne(uw_Sqlcache_Cache *cache, char **keys) { + pthread_rwlock_wrlock(&cache->lock); size_t numKeys = cache->numKeys; char *key = uw_Sqlcache_allocKeyBuffer(keys, numKeys); char *buf = key; @@ -4709,55 +4715,69 @@ void uw_Sqlcache_flushCommitOne(uw_Sqlcache_Cache *cache, char **keys) { free(key); // All the keys were non-null and the relevant entry is present, so we delete it. uw_Sqlcache_delete(cache, entry); + pthread_rwlock_unlock(&cache->lock); } -void uw_Sqlcache_flushFree(void *data, int dontCare) { - uw_Sqlcache_Inval *inval = (uw_Sqlcache_Inval *)data; - while (inval) { - char** keys = inval->keys; - size_t numKeys = inval->cache->numKeys; +void uw_Sqlcache_freeUpdate(void *data, int dontCare) { + uw_context ctx = (uw_context)data; + uw_Sqlcache_Update *update = ctx->cacheUpdate; + while (update) { + char** keys = update->keys; + size_t numKeys = update->cache->numKeys; while (numKeys-- > 0) { free(keys[numKeys]); } free(keys); - uw_Sqlcache_Inval *nextInval = inval->next; - free(inval); - inval = nextInval; + // Don't free [update->value]: it's in the cache now! + uw_Sqlcache_Update *nextUpdate = update->next; + free(update); + update = nextUpdate; } -} - -void uw_Sqlcache_flushCommit(void *data) { - uw_Sqlcache_Inval *inval = (uw_Sqlcache_Inval *)data; - while (inval) { - uw_Sqlcache_Cache *cache = inval->cache; - char **keys = inval->keys; - uw_Sqlcache_flushCommitOne(cache, keys); - inval = inval->next; + ctx->cacheUpdate = NULL; + ctx->cacheUpdateTail = NULL; +} + +void uw_Sqlcache_commitUpdate(void *data) { + uw_context ctx = (uw_context)data; + uw_Sqlcache_Update *update = ctx->cacheUpdate; + while (update) { + uw_Sqlcache_Cache *cache = update->cache; + char **keys = update->keys; + if (update->value) { + uw_Sqlcache_storeCommitOne(cache, keys, update->value); + } else { + uw_Sqlcache_flushCommitOne(cache, keys); + } + update = update->next; } } char **uw_Sqlcache_copyKeys(char **keys, size_t numKeys) { char **copy = malloc(sizeof(char *) * numKeys); while (numKeys-- > 0) { - char * k = keys[numKeys]; + char *k = keys[numKeys]; copy[numKeys] = k ? strdup(k) : NULL; } return copy; } -void uw_Sqlcache_flush(uw_context ctx, uw_Sqlcache_Cache *cache, char **keys) { - //pthread_rwlock_wrlock(cache->lock); - uw_Sqlcache_Inval *inval = malloc(sizeof(uw_Sqlcache_Inval)); - inval->cache = cache; - inval->keys = uw_Sqlcache_copyKeys(keys, cache->numKeys); - inval->next = NULL; - if (ctx->inval) { - // An invalidation is already registered, so just extend it. - ctx->inval->next = inval; +void uw_Sqlcache_store(uw_context ctx, uw_Sqlcache_Cache *cache, char **keys, uw_Sqlcache_Value *value) { + uw_Sqlcache_Update *update = malloc(sizeof(uw_Sqlcache_Update)); + update->cache = cache; + update->keys = uw_Sqlcache_copyKeys(keys, cache->numKeys); + update->value = value; + update->next = NULL; + if (ctx->cacheUpdateTail) { + // An update is already registered, so just extend it. + ctx->cacheUpdateTail->next = update; } else { - uw_register_transactional(ctx, inval, uw_Sqlcache_flushCommit, NULL, uw_Sqlcache_flushFree); + ctx->cacheUpdate = update; + uw_register_transactional(ctx, ctx, uw_Sqlcache_commitUpdate, NULL, uw_Sqlcache_freeUpdate); } - // [ctx->inval] should always point to the last invalidation. - ctx->inval = inval; - //pthread_rwlock_unlock(cache->lock); + ctx->cacheUpdateTail = update; +} + +void uw_Sqlcache_flush(uw_context ctx, uw_Sqlcache_Cache *cache, char **keys) { + // A flush is represented in the queue as storing NULL. + uw_Sqlcache_store(ctx, cache, keys, NULL); } diff --git a/src/lru_cache.sml b/src/lru_cache.sml index 9d65420b..b6ffe700 100644 --- a/src/lru_cache.sml +++ b/src/lru_cache.sml @@ -65,8 +65,8 @@ fun setupQuery {index, params} = in Print.box [string ("static uw_Sqlcache_Cache cacheStruct" ^ i ^ " = {"), - (* newline, *) - (* string " .lock = PTHREAD_RWLOCK_INITIALIZER,", *) + newline, + string " .lock = PTHREAD_RWLOCK_INITIALIZER,", newline, string " .table = NULL,", newline, @@ -85,7 +85,7 @@ fun setupQuery {index, params} = newline, string (" char *ks[] = {" ^ revArgs ^ "};"), newline, - string (" uw_Sqlcache_Value *v = uw_Sqlcache_check(cache" ^ i ^ ", ks);"), + string (" uw_Sqlcache_Value *v = uw_Sqlcache_check(ctx, cache" ^ i ^ ", ks);"), newline, (* If the output is null, it means we had too much recursion, so it's a miss. *) string " if (v && v->output != NULL) {", @@ -123,7 +123,7 @@ fun setupQuery {index, params} = newline, string (" puts(\"SQLCACHE: stored " ^ i ^ ".\");"), newline, - string (" uw_Sqlcache_store(cache" ^ i ^ ", ks, v);"), + string (" uw_Sqlcache_store(ctx, cache" ^ i ^ ", ks, v);"), newline, string " return uw_unit_v;", newline, -- cgit v1.2.3 From c38edb9bd5c21bcc1d21979d40ec8e9d638b6e9c Mon Sep 17 00:00:00 2001 From: Ziv Scully Date: Fri, 13 Nov 2015 01:04:32 -0500 Subject: Fix issue with one-element caches. Locking still WIP. --- src/c/urweb.c | 95 ++++++++++++++++++++++------------ src/cache.sml | 9 ++-- src/lru_cache.sml | 29 ++++++----- src/sqlcache.sml | 149 ++++++++++++++++++++++++++++++++++++------------------ src/toy_cache.sml | 5 +- 5 files changed, 189 insertions(+), 98 deletions(-) (limited to 'src/lru_cache.sml') diff --git a/src/c/urweb.c b/src/c/urweb.c index 4afc7297..02e17a0b 100644 --- a/src/c/urweb.c +++ b/src/c/urweb.c @@ -4641,18 +4641,27 @@ uw_Sqlcache_Value *uw_Sqlcache_check(uw_context ctx, uw_Sqlcache_Cache *cache, c char *buf = key; time_t timeInvalid = cache->timeInvalid; uw_Sqlcache_Entry *entry; - while (numKeys-- > 0) { - buf = uw_Sqlcache_keyCopy(buf, keys[numKeys]); - size_t len = buf - key; - entry = uw_Sqlcache_find(cache, key, len, 1); + if (numKeys == 0) { + entry = cache->table; if (!entry) { free(key); pthread_rwlock_unlock(&cache->lock); return NULL; } - timeInvalid = uw_Sqlcache_timeMax(timeInvalid, entry->timeInvalid); + } else { + while (numKeys-- > 0) { + buf = uw_Sqlcache_keyCopy(buf, keys[numKeys]); + size_t len = buf - key; + entry = uw_Sqlcache_find(cache, key, len, 1); + if (!entry) { + free(key); + pthread_rwlock_unlock(&cache->lock); + return NULL; + } + timeInvalid = uw_Sqlcache_timeMax(timeInvalid, entry->timeInvalid); + } + free(key); } - free(key); // TODO: pass back copy of value and free it in the generated code... or use uw_malloc? uw_Sqlcache_Value *value = entry->value; pthread_rwlock_unlock(&cache->lock); @@ -4666,19 +4675,30 @@ void uw_Sqlcache_storeCommitOne(uw_Sqlcache_Cache *cache, char **keys, uw_Sqlcac char *buf = key; time_t timeNow = uw_Sqlcache_getTimeNow(cache); uw_Sqlcache_Entry *entry; - while (numKeys-- > 0) { - buf = uw_Sqlcache_keyCopy(buf, keys[numKeys]); - size_t len = buf - key; - entry = uw_Sqlcache_find(cache, key, len, 1); + if (numKeys == 0) { + entry = cache->table; if (!entry) { entry = malloc(sizeof(uw_Sqlcache_Entry)); entry->key = strdup(key); entry->value = NULL; entry->timeInvalid = 0; - uw_Sqlcache_add(cache, entry, len); + cache->table = entry; } + } else { + while (numKeys-- > 0) { + buf = uw_Sqlcache_keyCopy(buf, keys[numKeys]); + size_t len = buf - key; + entry = uw_Sqlcache_find(cache, key, len, 1); + if (!entry) { + entry = malloc(sizeof(uw_Sqlcache_Entry)); + entry->key = strdup(key); + entry->value = NULL; + entry->timeInvalid = 0; + uw_Sqlcache_add(cache, entry, len); + } + } + free(key); } - free(key); uw_Sqlcache_freeValue(entry->value); entry->value = value; entry->value->timeValid = timeNow; @@ -4692,29 +4712,40 @@ void uw_Sqlcache_flushCommitOne(uw_Sqlcache_Cache *cache, char **keys) { char *buf = key; time_t timeNow = uw_Sqlcache_getTimeNow(cache); uw_Sqlcache_Entry *entry; - while (numKeys-- > 0) { - char *k = keys[numKeys]; - if (!k) { - if (entry) { - entry->timeInvalid = timeNow; - } else { - // Haven't found an entry yet, so the first key was null. - cache->timeInvalid = timeNow; - } - free(key); - return; + if (numKeys == 0) { + puts("flush cache of height 0"); + entry = cache->table; + if (entry) { + uw_Sqlcache_freeValue(entry->value); + entry->value = NULL; } - buf = uw_Sqlcache_keyCopy(buf, k); - size_t len = buf - key; - entry = uw_Sqlcache_find(cache, key, len, 0); - if (!entry) { - free(key); - return; + } else { + while (numKeys-- > 0) { + char *k = keys[numKeys]; + if (!k) { + if (entry) { + entry->timeInvalid = timeNow; + } else { + // Haven't found an entry yet, so the first key was null. + cache->timeInvalid = timeNow; + } + free(key); + pthread_rwlock_unlock(&cache->lock); + return; + } + buf = uw_Sqlcache_keyCopy(buf, k); + size_t len = buf - key; + entry = uw_Sqlcache_find(cache, key, len, 0); + if (!entry) { + free(key); + pthread_rwlock_unlock(&cache->lock); + return; + } } + free(key); + // All the keys were non-null and the relevant entry is present, so we delete it. + uw_Sqlcache_delete(cache, entry); } - free(key); - // All the keys were non-null and the relevant entry is present, so we delete it. - uw_Sqlcache_delete(cache, entry); pthread_rwlock_unlock(&cache->lock); } diff --git a/src/cache.sml b/src/cache.sml index 8de22e0d..015c3ff1 100644 --- a/src/cache.sml +++ b/src/cache.sml @@ -2,13 +2,14 @@ structure Cache = struct type cache = {(* Takes a query ID and parameters (and, for store, the value to - store) and gives an FFI call that checks, stores, or flushes the - relevant entry. The parameters are strings for check and store and - optional strings for flush because some parameters might not be - fixed. *) + store) and gives an FFI call that checks, stores, or flushes the + relevant entry. The parameters are strings for check and store and + optional strings for flush because some parameters might not be + fixed. *) check : int * Mono.exp list -> Mono.exp', store : int * Mono.exp list * Mono.exp -> Mono.exp', flush : int * Mono.exp list -> Mono.exp', + lock : int * bool (* true = write, false = read *) -> Mono.exp', (* Generates C needed for FFI calls in check, store, and flush. *) setupGlobal : Print.PD.pp_desc, setupQuery : {index : int, params : int} -> Print.PD.pp_desc} diff --git a/src/lru_cache.sml b/src/lru_cache.sml index b6ffe700..b66becb7 100644 --- a/src/lru_cache.sml +++ b/src/lru_cache.sml @@ -24,6 +24,9 @@ fun store (index, keys, value) = fun flush (index, keys) = ffiAppCache' ("flush", index, withTyp optionStringTyp keys) +fun lock (index, write) = + ffiAppCache' ((if write then "w" else "r") ^ "lock", index, []) + (* Cjr *) @@ -157,18 +160,18 @@ fun toyIfNoKeys numKeys implLru implToy args = else implLru args val cache = - let - val {check = toyCheck, - store = toyStore, - flush = toyFlush, - setupQuery = toySetupQuery, - ...} = ToyCache.cache - in - {check = toyIfNoKeys (length o #2) check toyCheck, - store = toyIfNoKeys (length o #2) store toyStore, - flush = toyIfNoKeys (length o #2) flush toyFlush, - setupQuery = toyIfNoKeys #params setupQuery toySetupQuery, - setupGlobal = setupGlobal} - end + (* let *) + (* val {check = toyCheck, *) + (* store = toyStore, *) + (* flush = toyFlush, *) + (* setupQuery = toySetupQuery, *) + (* ...} = ToyCache.cache *) + (* in *) + (* {check = toyIfNoKeys (length o #2) check toyCheck, *) + (* store = toyIfNoKeys (length o #2) store toyStore, *) + (* flush = toyIfNoKeys (length o #2) flush toyFlush, *) + {check = check, store = store, flush = flush, lock = lock, + setupQuery = setupQuery, setupGlobal = setupGlobal} + (* end *) end diff --git a/src/sqlcache.sml b/src/sqlcache.sml index 5a748496..2b3b80ae 100644 --- a/src/sqlcache.sml +++ b/src/sqlcache.sml @@ -1,6 +1,9 @@ structure Sqlcache :> SQLCACHE = struct -open Mono + +(*********************) +(* General Utilities *) +(*********************) structure IK = struct type ord_key = int val compare = Int.compare end structure IS = IntBinarySet @@ -8,10 +11,9 @@ structure IM = IntBinaryMap structure SK = struct type ord_key = string val compare = String.compare end structure SS = BinarySetFn(SK) structure SM = BinaryMapFn(SK) +structure IIMM = MultimapFn(structure KeyMap = IM structure ValSet = IS) structure SIMM = MultimapFn(structure KeyMap = SM structure ValSet = IS) -(* ASK: how do we deal with heap reallocation? *) - fun id x = x fun iterate f n x = if n < 0 @@ -20,6 +22,35 @@ fun iterate f n x = if n < 0 then x else iterate f (n-1) (f x) +(* From the MLton wiki. *) +infix 3 <\ fun x <\ f = fn y => f (x, y) (* Left section *) +infix 3 \> fun f \> y = f y (* Left application *) + +fun mapFst f (x, y) = (f x, y) + +(* Option monad. *) +fun obind (x, f) = Option.mapPartial f x +fun oguard (b, x) = if b then x else NONE +fun omap f = fn SOME x => SOME (f x) | _ => NONE +fun omap2 f = fn (SOME x, SOME y) => SOME (f (x,y)) | _ => NONE +fun osequence ys = List.foldr (omap2 op::) (SOME []) ys + +fun indexOf test = + let + fun f n = + fn [] => NONE + | (x::xs) => if test x then SOME n else f (n+1) xs + in + f 0 + end + + +(************) +(* Settings *) +(************) + +open Mono + (* Filled in by [addFlushing]. *) val ffiInfoRef : {index : int, params : int} list ref = ref [] @@ -59,6 +90,11 @@ val alwaysConsolidateRef = ref true fun setAlwaysConsolidate b = alwaysConsolidateRef := b fun getAlwaysConsolidate () = !alwaysConsolidateRef + +(************************) +(* Really Useful Things *) +(************************) + (* Used to have type context for local variables in MonoUtil functions. *) val doBind = fn (env, MonoUtil.Exp.RelE (x, t)) => MonoEnv.pushERel env x t NONE @@ -79,36 +115,26 @@ fun obindDebug printer (x, f) = NONE => (printer (); NONE) | y => y -(*********************) -(* General Utilities *) -(*********************) - -(* From the MLton wiki. *) -infix 3 <\ fun x <\ f = fn y => f (x, y) (* Left section *) -infix 3 \> fun f \> y = f y (* Left application *) -fun mapFst f (x, y) = (f x, y) - -(* Option monad. *) -fun obind (x, f) = Option.mapPartial f x -fun oguard (b, x) = if b then x else NONE -fun omap f = fn SOME x => SOME (f x) | _ => NONE -fun omap2 f = fn (SOME x, SOME y) => SOME (f (x,y)) | _ => NONE -fun osequence ys = List.foldr (omap2 op::) (SOME []) ys +(*******************) +(* Effect Analysis *) +(*******************) -fun indexOf test = +(* TODO: test this. *) +fun transitiveAnalysis doVal state (decls, _) = let - fun f n = - fn [] => NONE - | (x::xs) => if test x then SOME n else f (n+1) xs + val doDecl = + fn ((DVal v, _), state) => doVal (v, state) + (* Pass over the list of values a number of times equal to its size, + making sure whatever property we're testing propagates everywhere + it should. This is analagous to the Bellman-Ford algorithm. *) + | ((DValRec vs, _), state) => + iterate (fn state => List.foldl doVal state vs) (length vs) state + | (_, state) => state in - f 0 + List.foldl doDecl state decls end -(*******************) -(* Effect Analysis *) -(*******************) - (* Makes an exception for [EWrite] (which is recorded when caching). *) fun effectful (effs : IS.set) = let @@ -151,24 +177,13 @@ fun effectful (effs : IS.set) = end (* TODO: test this. *) -fun effectfulDecls (decls, _) = - let - fun doVal ((_, name, _, e, _), effs) = - if effectful effs MonoEnv.empty e - then IS.add (effs, name) - else effs - val doDecl = - fn ((DVal v, _), effs) => doVal (v, effs) - (* Repeat the list of declarations a number of times equal to its size, - making sure effectfulness propagates everywhere it should. This is - analagous to the Bellman-Ford algorithm. *) - | ((DValRec vs, _), effs) => - List.foldl doVal effs (List.concat (List.map (fn _ => vs) vs)) - (* ASK: any other cases? *) - | (_, effs) => effs - in - List.foldl doDecl IS.empty decls - end +fun effectfulDecls file = + transitiveAnalysis (fn ((_, name, _, e, _), effs) => + if effectful effs MonoEnv.empty e + then IS.add (effs, name) + else effs) + IS.empty + file (*********************************) @@ -1080,9 +1095,7 @@ fun typOfExp' (env : MonoEnv.env) : exp' -> typ option = | ERecord fields => SOME (TRecord (map (fn (s, _, t) => (s, t)) fields), dummyLoc) | EField (e, s) => (case typOfExp env e of SOME (TRecord fields, _) => - (case List.find (fn (s', _) => s = s') fields of - SOME (_, t) => SOME t - | _ => NONE) + omap #2 (List.find (fn (s', _) => s = s') fields) | _ => NONE) | ECase (_, _, {result, ...}) => SOME result | EStrcat _ => SOME (TFfi ("Basis", "string"), dummyLoc) @@ -1414,6 +1427,46 @@ fun addFlushing ((file, {tableToIndices, indexToInvalInfo, ffiInfo, ...} : state end +(***********) +(* Locking *) +(***********) + +(* TODO: do this less evil-ly by not relying on specific FFI names, please? *) +fun locksNeeded file = + transitiveAnalysis + (fn ((_, name, _, e, _), state) => + MonoUtil.Exp.fold + {typ = #2, + exp = fn (EFfiApp ("Sqlcache", x, _), state as {store, flush}) => + (case Int.fromString (String.extract (x, 5, NONE)) of + NONE => raise Match + | SOME index => + if String.isPrefix "store" x + then {store = IIMM.insert (store, name, index), flush = flush} + else if String.isPrefix "flush" x + then {store = store, flush = IIMM.insert (flush, name, index)} + else state) + | _ => state} + state + e) + {store = IIMM.empty, flush = IIMM.empty} + file + +fun exports (decls, _) = + List.foldl (fn ((DExport (_, _, n, _, _, _), _), ns) => IS.add (ns, n) + | ((DTask _, _), _) => raise Fail "Sqlcache doesn't yet support tasks." + | (_, ns) => ns) + IS.empty + decls + +(* fun addLocking file = *) +(* let *) +(* val whichLocks = locksNeeded file *) +(* val needsLocks = exports file *) +(* in *) + +(* end *) + (************************) (* Compiler Entry Point *) (************************) diff --git a/src/toy_cache.sml b/src/toy_cache.sml index 377cae01..5c5aa459 100644 --- a/src/toy_cache.sml +++ b/src/toy_cache.sml @@ -24,6 +24,9 @@ fun store (index, keys, value) = fun flush (index, keys) = ffiAppCache' ("flush", index, withTyp optionStringTyp keys) +fun lock (index, keys) = + raise Fail "ToyCache doesn't yet implement lock" + (* Cjr *) @@ -198,7 +201,7 @@ val setupGlobal = string "/* No global setup for toy cache. */" (* Bundled up. *) -val cache = {check = check, store = store, flush = flush, +val cache = {check = check, store = store, flush = flush, lock = lock, setupQuery = setupQuery, setupGlobal = setupGlobal} end -- cgit v1.2.3 From bad52a2868ff0551ac0199fd8124f81f9623391e Mon Sep 17 00:00:00 2001 From: Ziv Scully Date: Fri, 13 Nov 2015 11:03:09 -0500 Subject: Finish locking, but it's not yet tested rigorously. --- include/urweb/types_cpp.h | 3 +- include/urweb/urweb_cpp.h | 2 + src/c/urweb.c | 143 ++++++++++++++++++++++++++++++---------------- src/lru_cache.sml | 20 ++++++- src/sqlcache.sml | 51 ++++++++++++----- 5 files changed, 154 insertions(+), 65 deletions(-) (limited to 'src/lru_cache.sml') diff --git a/include/urweb/types_cpp.h b/include/urweb/types_cpp.h index 82f8d30a..ce0f2825 100644 --- a/include/urweb/types_cpp.h +++ b/include/urweb/types_cpp.h @@ -133,7 +133,8 @@ typedef struct uw_Sqlcache_Value { typedef struct uw_Sqlcache_Entry uw_Sqlcache_Entry; typedef struct uw_Sqlcache_Cache { - pthread_rwlock_t lock; + pthread_rwlock_t lockOut; + pthread_rwlock_t lockIn; uw_Sqlcache_Entry *table; unsigned long timeInvalid; unsigned long timeNow; diff --git a/include/urweb/urweb_cpp.h b/include/urweb/urweb_cpp.h index 2c032e7b..916fbbf9 100644 --- a/include/urweb/urweb_cpp.h +++ b/include/urweb/urweb_cpp.h @@ -406,6 +406,8 @@ void uw_Basis_writec(struct uw_context *, char); // Sqlcache. +void *uw_Sqlcache_rlock(struct uw_context *, uw_Sqlcache_Cache *); +void *uw_Sqlcache_wlock(struct uw_context *, uw_Sqlcache_Cache *); uw_Sqlcache_Value *uw_Sqlcache_check(struct uw_context *, uw_Sqlcache_Cache *, char **); void *uw_Sqlcache_store(struct uw_context *, uw_Sqlcache_Cache *, char **, uw_Sqlcache_Value *); void *uw_Sqlcache_flush(struct uw_context *, uw_Sqlcache_Cache *, char **); diff --git a/src/c/urweb.c b/src/c/urweb.c index 778adacc..6a48e95e 100644 --- a/src/c/urweb.c +++ b/src/c/urweb.c @@ -366,6 +366,9 @@ void uw_global_init() { uw_global_custom(); uw_init_crypto(); + + // Fast non-cryptographic strength randomness for Sqlcache. + srandom(clock()); } void uw_app_init(uw_app *app) { @@ -431,6 +434,11 @@ typedef struct uw_Sqlcache_Update { struct uw_Sqlcache_Update *next; } uw_Sqlcache_Update; +typedef struct uw_Sqlcache_Unlock { + pthread_rwlock_t *lock; + struct uw_Sqlcache_Unlock *next; +} uw_Sqlcache_Unlock; + struct uw_context { uw_app *app; int id; @@ -500,6 +508,7 @@ struct uw_context { int recordingOffset; uw_Sqlcache_Update *cacheUpdate; uw_Sqlcache_Update *cacheUpdateTail; + uw_Sqlcache_Unlock *cacheUnlock; int remoteSock; }; @@ -4556,7 +4565,7 @@ typedef struct uw_Sqlcache_Entry { UT_hash_handle hh; } uw_Sqlcache_Entry; -void uw_Sqlcache_freeValue(uw_Sqlcache_Value *value) { +static void uw_Sqlcache_freeValue(uw_Sqlcache_Value *value) { if (value) { free(value->result); free(value->output); @@ -4564,7 +4573,7 @@ void uw_Sqlcache_freeValue(uw_Sqlcache_Value *value) { } } -void uw_Sqlcache_freeEntry(uw_Sqlcache_Entry* entry) { +static void uw_Sqlcache_freeEntry(uw_Sqlcache_Entry* entry) { if (entry) { free(entry->key); uw_Sqlcache_freeValue(entry->value); @@ -4573,14 +4582,14 @@ void uw_Sqlcache_freeEntry(uw_Sqlcache_Entry* entry) { } // TODO: pick a number. -unsigned int uw_Sqlcache_maxSize = 1234567890; +static unsigned int uw_Sqlcache_maxSize = 1234567890; -void uw_Sqlcache_delete(uw_Sqlcache_Cache *cache, uw_Sqlcache_Entry *entry) { +static void uw_Sqlcache_delete(uw_Sqlcache_Cache *cache, uw_Sqlcache_Entry *entry) { HASH_DEL(cache->table, entry); uw_Sqlcache_freeEntry(entry); } -uw_Sqlcache_Entry *uw_Sqlcache_find(uw_Sqlcache_Cache *cache, char *key, size_t len, int bump) { +static uw_Sqlcache_Entry *uw_Sqlcache_find(uw_Sqlcache_Cache *cache, char *key, size_t len, int bump) { uw_Sqlcache_Entry *entry = NULL; HASH_FIND(hh, cache->table, key, len, entry); if (entry && bump) { @@ -4592,7 +4601,7 @@ uw_Sqlcache_Entry *uw_Sqlcache_find(uw_Sqlcache_Cache *cache, char *key, size_t return entry; } -void uw_Sqlcache_add(uw_Sqlcache_Cache *cache, uw_Sqlcache_Entry *entry, size_t len) { +static void uw_Sqlcache_add(uw_Sqlcache_Cache *cache, uw_Sqlcache_Entry *entry, size_t len) { HASH_ADD_KEYPTR(hh, cache->table, entry->key, len, entry); if (HASH_COUNT(cache->table) > uw_Sqlcache_maxSize) { // Deletes the first element of the cache. @@ -4600,17 +4609,17 @@ void uw_Sqlcache_add(uw_Sqlcache_Cache *cache, uw_Sqlcache_Entry *entry, size_t } } -unsigned long uw_Sqlcache_getTimeNow(uw_Sqlcache_Cache *cache) { +static unsigned long uw_Sqlcache_getTimeNow(uw_Sqlcache_Cache *cache) { return ++cache->timeNow; } -unsigned long uw_Sqlcache_timeMax(unsigned long x, unsigned long y) { +static unsigned long uw_Sqlcache_timeMax(unsigned long x, unsigned long y) { return x > y ? x : y; } -char uw_Sqlcache_keySep = '_'; +static char uw_Sqlcache_keySep = '_'; -char *uw_Sqlcache_allocKeyBuffer(char **keys, size_t numKeys) { +static char *uw_Sqlcache_allocKeyBuffer(char **keys, size_t numKeys) { size_t len = 0; while (numKeys-- > 0) { char* k = keys[numKeys]; @@ -4627,7 +4636,7 @@ char *uw_Sqlcache_allocKeyBuffer(char **keys, size_t numKeys) { return buf; } -char *uw_Sqlcache_keyCopy(char *buf, char *key) { +static char *uw_Sqlcache_keyCopy(char *buf, char *key) { *buf++ = uw_Sqlcache_keySep; return stpcpy(buf, key); } @@ -4635,7 +4644,12 @@ char *uw_Sqlcache_keyCopy(char *buf, char *key) { // The NUL-terminated prefix of [key] below always looks something like "_k1_k2_k3..._kn". uw_Sqlcache_Value *uw_Sqlcache_check(uw_context ctx, uw_Sqlcache_Cache *cache, char **keys) { - pthread_rwlock_rdlock(&cache->lock); + int doBump = random() % 1024 == 0; + if (doBump) { + pthread_rwlock_wrlock(&cache->lockIn); + } else { + pthread_rwlock_rdlock(&cache->lockIn); + } size_t numKeys = cache->numKeys; char *key = uw_Sqlcache_allocKeyBuffer(keys, numKeys); char *buf = key; @@ -4645,46 +4659,49 @@ uw_Sqlcache_Value *uw_Sqlcache_check(uw_context ctx, uw_Sqlcache_Cache *cache, c entry = cache->table; if (!entry) { free(key); - pthread_rwlock_unlock(&cache->lock); + pthread_rwlock_unlock(&cache->lockIn); return NULL; } } else { while (numKeys-- > 0) { buf = uw_Sqlcache_keyCopy(buf, keys[numKeys]); size_t len = buf - key; - entry = uw_Sqlcache_find(cache, key, len, 1); + entry = uw_Sqlcache_find(cache, key, len, doBump); if (!entry) { free(key); - pthread_rwlock_unlock(&cache->lock); + pthread_rwlock_unlock(&cache->lockIn); return NULL; } timeInvalid = uw_Sqlcache_timeMax(timeInvalid, entry->timeInvalid); } free(key); } - // TODO: pass back copy of value and free it in the generated code... or use uw_malloc? uw_Sqlcache_Value *value = entry->value; - pthread_rwlock_unlock(&cache->lock); + pthread_rwlock_unlock(&cache->lockIn); + // ASK: though the argument isn't trivial, this is safe, right? + // Returning outside the lock is safe because updates happen at commit time. + // Those are the only times the returned value or its strings can get freed. + // Handler output is a new string, so it's safe to free this at commit time. return value && value->timeValid > timeInvalid ? value : NULL; } -void uw_Sqlcache_storeCommitOne(uw_Sqlcache_Cache *cache, char **keys, uw_Sqlcache_Value *value) { - pthread_rwlock_wrlock(&cache->lock); +static void uw_Sqlcache_storeCommitOne(uw_Sqlcache_Cache *cache, char **keys, uw_Sqlcache_Value *value) { + pthread_rwlock_wrlock(&cache->lockIn); size_t numKeys = cache->numKeys; - char *key = uw_Sqlcache_allocKeyBuffer(keys, numKeys); - char *buf = key; time_t timeNow = uw_Sqlcache_getTimeNow(cache); uw_Sqlcache_Entry *entry; if (numKeys == 0) { entry = cache->table; if (!entry) { entry = malloc(sizeof(uw_Sqlcache_Entry)); - entry->key = strdup(key); + entry->key = NULL; entry->value = NULL; entry->timeInvalid = 0; cache->table = entry; } } else { + char *key = uw_Sqlcache_allocKeyBuffer(keys, numKeys); + char *buf = key; while (numKeys-- > 0) { buf = uw_Sqlcache_keyCopy(buf, keys[numKeys]); size_t len = buf - key; @@ -4702,23 +4719,23 @@ void uw_Sqlcache_storeCommitOne(uw_Sqlcache_Cache *cache, char **keys, uw_Sqlcac uw_Sqlcache_freeValue(entry->value); entry->value = value; entry->value->timeValid = timeNow; - pthread_rwlock_unlock(&cache->lock); + pthread_rwlock_unlock(&cache->lockIn); } -void uw_Sqlcache_flushCommitOne(uw_Sqlcache_Cache *cache, char **keys) { - pthread_rwlock_wrlock(&cache->lock); +static void uw_Sqlcache_flushCommitOne(uw_Sqlcache_Cache *cache, char **keys) { + pthread_rwlock_wrlock(&cache->lockIn); size_t numKeys = cache->numKeys; - char *key = uw_Sqlcache_allocKeyBuffer(keys, numKeys); - char *buf = key; - time_t timeNow = uw_Sqlcache_getTimeNow(cache); - uw_Sqlcache_Entry *entry; if (numKeys == 0) { - entry = cache->table; + uw_Sqlcache_Entry *entry = cache->table; if (entry) { uw_Sqlcache_freeValue(entry->value); entry->value = NULL; } } else { + char *key = uw_Sqlcache_allocKeyBuffer(keys, numKeys); + char *buf = key; + time_t timeNow = uw_Sqlcache_getTimeNow(cache); + uw_Sqlcache_Entry *entry = NULL; while (numKeys-- > 0) { char *k = keys[numKeys]; if (!k) { @@ -4729,15 +4746,16 @@ void uw_Sqlcache_flushCommitOne(uw_Sqlcache_Cache *cache, char **keys) { cache->timeInvalid = timeNow; } free(key); - pthread_rwlock_unlock(&cache->lock); + pthread_rwlock_unlock(&cache->lockIn); return; } buf = uw_Sqlcache_keyCopy(buf, k); size_t len = buf - key; entry = uw_Sqlcache_find(cache, key, len, 0); if (!entry) { + // Nothing in the cache to flush. free(key); - pthread_rwlock_unlock(&cache->lock); + pthread_rwlock_unlock(&cache->lockIn); return; } } @@ -4745,10 +4763,25 @@ void uw_Sqlcache_flushCommitOne(uw_Sqlcache_Cache *cache, char **keys) { // All the keys were non-null and the relevant entry is present, so we delete it. uw_Sqlcache_delete(cache, entry); } - pthread_rwlock_unlock(&cache->lock); + pthread_rwlock_unlock(&cache->lockIn); +} + +static void uw_Sqlcache_commit(void *data) { + uw_context ctx = (uw_context)data; + uw_Sqlcache_Update *update = ctx->cacheUpdate; + while (update) { + uw_Sqlcache_Cache *cache = update->cache; + char **keys = update->keys; + if (update->value) { + uw_Sqlcache_storeCommitOne(cache, keys, update->value); + } else { + uw_Sqlcache_flushCommitOne(cache, keys); + } + update = update->next; + } } -void uw_Sqlcache_freeUpdate(void *data, int dontCare) { +static void uw_Sqlcache_free(void *data, int dontCare) { uw_context ctx = (uw_context)data; uw_Sqlcache_Update *update = ctx->cacheUpdate; while (update) { @@ -4765,24 +4798,38 @@ void uw_Sqlcache_freeUpdate(void *data, int dontCare) { } ctx->cacheUpdate = NULL; ctx->cacheUpdateTail = NULL; + uw_Sqlcache_Unlock *unlock = ctx->cacheUnlock; + while (unlock) { + pthread_rwlock_unlock(unlock->lock); + uw_Sqlcache_Unlock *nextUnlock = unlock->next; + free(unlock); + unlock = nextUnlock; + } + ctx->cacheUnlock = NULL; } -void uw_Sqlcache_commitUpdate(void *data) { - uw_context ctx = (uw_context)data; - uw_Sqlcache_Update *update = ctx->cacheUpdate; - while (update) { - uw_Sqlcache_Cache *cache = update->cache; - char **keys = update->keys; - if (update->value) { - uw_Sqlcache_storeCommitOne(cache, keys, update->value); - } else { - uw_Sqlcache_flushCommitOne(cache, keys); - } - update = update->next; +static void uw_Sqlcache_pushUnlock(uw_context ctx, pthread_rwlock_t *lock) { + if (!ctx->cacheUnlock) { + // Just need one registered commit for both updating and unlocking. + uw_register_transactional(ctx, ctx, uw_Sqlcache_commit, NULL, uw_Sqlcache_free); } + uw_Sqlcache_Unlock *unlock = malloc(sizeof(uw_Sqlcache_Unlock)); + unlock->lock = lock; + unlock->next = ctx->cacheUnlock; + ctx->cacheUnlock = unlock; +} + +void uw_Sqlcache_rlock(uw_context ctx, uw_Sqlcache_Cache *cache) { + pthread_rwlock_rdlock(&cache->lockOut); + uw_Sqlcache_pushUnlock(ctx, &cache->lockOut); +} + +void uw_Sqlcache_wlock(uw_context ctx, uw_Sqlcache_Cache *cache) { + pthread_rwlock_wrlock(&cache->lockOut); + uw_Sqlcache_pushUnlock(ctx, &cache->lockOut); } -char **uw_Sqlcache_copyKeys(char **keys, size_t numKeys) { +static char **uw_Sqlcache_copyKeys(char **keys, size_t numKeys) { char **copy = malloc(sizeof(char *) * numKeys); while (numKeys-- > 0) { char *k = keys[numKeys]; @@ -4798,11 +4845,9 @@ void uw_Sqlcache_store(uw_context ctx, uw_Sqlcache_Cache *cache, char **keys, uw update->value = value; update->next = NULL; if (ctx->cacheUpdateTail) { - // An update is already registered, so just extend it. ctx->cacheUpdateTail->next = update; } else { ctx->cacheUpdate = update; - uw_register_transactional(ctx, ctx, uw_Sqlcache_commitUpdate, NULL, uw_Sqlcache_freeUpdate); } ctx->cacheUpdateTail = update; } diff --git a/src/lru_cache.sml b/src/lru_cache.sml index b66becb7..0276de91 100644 --- a/src/lru_cache.sml +++ b/src/lru_cache.sml @@ -69,7 +69,9 @@ fun setupQuery {index, params} = Print.box [string ("static uw_Sqlcache_Cache cacheStruct" ^ i ^ " = {"), newline, - string " .lock = PTHREAD_RWLOCK_INITIALIZER,", + string " .lockIn = PTHREAD_RWLOCK_INITIALIZER,", + newline, + string " .lockOut = PTHREAD_RWLOCK_INITIALIZER,", newline, string " .table = NULL,", newline, @@ -83,6 +85,22 @@ fun setupQuery {index, params} = newline, newline, + string ("static void uw_Sqlcache_rlock" ^ i ^ "(uw_context ctx) {"), + newline, + string (" uw_Sqlcache_rlock(ctx, cache" ^ i ^ ");"), + newline, + string "}", + newline, + newline, + + string ("static void uw_Sqlcache_wlock" ^ i ^ "(uw_context ctx) {"), + newline, + string (" uw_Sqlcache_wlock(ctx, cache" ^ i ^ ");"), + newline, + string "}", + newline, + newline, + string ("static uw_Basis_string uw_Sqlcache_check" ^ i), string ("(uw_context ctx" ^ typedArgs ^ ") {"), newline, diff --git a/src/sqlcache.sml b/src/sqlcache.sml index 2b3b80ae..6583dc91 100644 --- a/src/sqlcache.sml +++ b/src/sqlcache.sml @@ -913,7 +913,7 @@ val conflictMaps = ConflictMaps.conflictMaps (* Program Instrumentation Utilities *) (*************************************) -val {check, store, flush, ...} = getCache () +val {check, store, flush, lock, ...} = getCache () val dummyTyp = (TRecord [], dummyLoc) @@ -1431,7 +1431,7 @@ fun addFlushing ((file, {tableToIndices, indexToInvalInfo, ffiInfo, ...} : state (* Locking *) (***********) -(* TODO: do this less evil-ly by not relying on specific FFI names, please? *) +(* TODO: do this less evilly by not relying on specific FFI names, please? *) fun locksNeeded file = transitiveAnalysis (fn ((_, name, _, e, _), state) => @@ -1439,14 +1439,14 @@ fun locksNeeded file = {typ = #2, exp = fn (EFfiApp ("Sqlcache", x, _), state as {store, flush}) => (case Int.fromString (String.extract (x, 5, NONE)) of - NONE => raise Match + NONE => state | SOME index => - if String.isPrefix "store" x - then {store = IIMM.insert (store, name, index), flush = flush} - else if String.isPrefix "flush" x + if String.isPrefix "flush" x then {store = store, flush = IIMM.insert (flush, name, index)} + else if String.isPrefix "store" x + then {store = IIMM.insert (store, name, index), flush = flush} else state) - | _ => state} + | (_, state) => state} state e) {store = IIMM.empty, flush = IIMM.empty} @@ -1459,13 +1459,36 @@ fun exports (decls, _) = IS.empty decls -(* fun addLocking file = *) -(* let *) -(* val whichLocks = locksNeeded file *) -(* val needsLocks = exports file *) -(* in *) +fun wrapLocks (locks, (exp', loc)) = + case exp' of + EAbs (s, t1, t2, exp) => (EAbs (s, t1, t2, wrapLocks (locks, exp)), loc) + | _ => (List.foldr (fn (l, e') => sequence [lock l, e']) exp' locks, loc) + +fun addLocking file = + let + val {store, flush} = locksNeeded file + fun locks n = + let + val wlocks = IIMM.findSet (flush, n) + val rlocks = IIMM.findSet (store, n) + val ls = map (fn i => (i, true)) (IS.listItems wlocks) + @ map (fn i => (i, false)) (IS.listItems (IS.difference (rlocks, wlocks))) + in + ListMergeSort.sort (fn ((i, _), (j, _)) => i > j) ls + end + val expts = exports file + fun doVal (v as (x, n, t, exp, s)) = + if IS.member (expts, n) + then (x, n, t, wrapLocks ((locks n), exp), s) + else v + val doDecl = + fn (DVal v, loc) => (DVal (doVal v), loc) + | (DValRec vs, loc) => (DValRec (map doVal vs), loc) + | decl => decl + in + mapFst (map doDecl) file + end -(* end *) (************************) (* Compiler Entry Point *) @@ -1494,7 +1517,7 @@ fun insertAfterDatatypes ((decls, sideInfo), newDecls) = (datatypes @ newDecls @ others, sideInfo) end -val go' = addFlushing o addCaching o simplifySql o inlineSql +val go' = addLocking o addFlushing o addCaching o simplifySql o inlineSql fun go file = let -- cgit v1.2.3 From ed7b5e6f956c5b13735cc3e5c4de01fbfc437e12 Mon Sep 17 00:00:00 2001 From: Ziv Scully Date: Sun, 15 Nov 2015 14:18:35 -0500 Subject: Fix bugs for lock calculation and SQL parsing and add support for tasks. --- caching-tests/test.urp | 2 +- src/lru_cache.sml | 12 ++--- src/sqlcache.sml | 126 +++++++++++++++++++++++++++++++------------------ 3 files changed, 87 insertions(+), 53 deletions(-) (limited to 'src/lru_cache.sml') diff --git a/caching-tests/test.urp b/caching-tests/test.urp index cea8821e..dd8cf774 100644 --- a/caching-tests/test.urp +++ b/caching-tests/test.urp @@ -1,4 +1,4 @@ -database test.db +database host=localhost dbname=ziv sql test.sql safeGet Test/flush safeGet Test/flash diff --git a/src/lru_cache.sml b/src/lru_cache.sml index 0276de91..e9ed5f73 100644 --- a/src/lru_cache.sml +++ b/src/lru_cache.sml @@ -111,16 +111,16 @@ fun setupQuery {index, params} = (* If the output is null, it means we had too much recursion, so it's a miss. *) string " if (v && v->output != NULL) {", newline, - string (" puts(\"SQLCACHE: hit " ^ i ^ ".\");"), - newline, + (* string (" puts(\"SQLCACHE: hit " ^ i ^ ".\");"), *) + (* newline, *) string " uw_write(ctx, v->output);", newline, string " return v->result;", newline, string " } else {", newline, - string (" puts(\"SQLCACHE: miss " ^ i ^ ".\");"), - newline, + (* string (" puts(\"SQLCACHE: miss " ^ i ^ ".\");"), *) + (* newline, *) string " uw_recordingStart(ctx);", newline, string " return NULL;", @@ -142,8 +142,8 @@ fun setupQuery {index, params} = newline, string " v->output = uw_recordingRead(ctx);", newline, - string (" puts(\"SQLCACHE: stored " ^ i ^ ".\");"), - newline, + (* string (" puts(\"SQLCACHE: stored " ^ i ^ ".\");"), *) + (* newline, *) string (" uw_Sqlcache_store(ctx, cache" ^ i ^ ", ks, v);"), newline, string " return uw_unit_v;", diff --git a/src/sqlcache.sml b/src/sqlcache.sml index 6583dc91..481acbeb 100644 --- a/src/sqlcache.sml +++ b/src/sqlcache.sml @@ -1,4 +1,4 @@ -structure Sqlcache :> SQLCACHE = struct +structure Sqlcache (* DEBUG :> SQLCACHE *) = struct (*********************) @@ -312,7 +312,9 @@ fun removeRedundant madeRedundantBy zs = end datatype atomExp = - QueryArg of int + True + | False + | QueryArg of int | DmlRel of int | Prim of Prim.t | Field of string * string @@ -322,7 +324,13 @@ structure AtomExpKey : ORD_KEY = struct type ord_key = atomExp val compare = - fn (QueryArg n1, QueryArg n2) => Int.compare (n1, n2) + fn (True, True) => EQUAL + | (True, _) => LESS + | (_, True) => GREATER + | (False, False) => EQUAL + | (False, _) => LESS + | (_, False) => GREATER + | (QueryArg n1, QueryArg n2) => Int.compare (n1, n2) | (QueryArg _, _) => LESS | (_, QueryArg _) => GREATER | (DmlRel n1, DmlRel n2) => Int.compare (n1, n2) @@ -531,7 +539,7 @@ end = struct project from a sqlified value (which is a string). *) | (_, sq as SOME _, [], NONE) => wrap sq - | _ => raise Match + | _ => raise Fail "Sqlcache: traverseSubst" end) (f n) in @@ -620,7 +628,7 @@ end = struct AM.find (argsMap, arg) <\obind\> (fn n' => SOME (ERel n'))) - | _ => raise Match + | _ => raise Fail "Sqlcache: query (a)" in case (map #1 qs) of (q :: qs) => @@ -629,16 +637,16 @@ end = struct val ns = IS.listItems (varsOfQuery q) val rename = fn ERel n => omap ERel (indexOf (fn n' => n' = n) ns) - | _ => raise Match + | _ => raise Fail "Sqlcache: query (b)" in case omapQuery rename q of SOME q => q (* We should never get NONE because indexOf should never fail. *) - | NONE => raise Match + | NONE => raise Fail "Sqlcache: query (c)" end (* We should never reach this case because [updateState] won't put anything in the state if there are no queries. *) - | [] => raise Match + | [] => raise Fail "Sqlcache: query (d)" end val argOfExp = @@ -700,8 +708,23 @@ val rec sqexpToFormula = | Sql.Binop (Sql.RCmp c, e1, e2) => Atom (c, e1, e2) | Sql.Binop (Sql.RLop l, p1, p2) => Combo (case l of Sql.And => Conj | Sql.Or => Disj, [sqexpToFormula p1, sqexpToFormula p2]) + | e as Sql.Field f => Atom (Sql.Eq, e, Sql.SqTrue) (* ASK: any other sqexps that can be props? *) - | _ => raise Match + | Sql.SqConst prim => + (case prim of + (Prim.String (Prim.Normal, s)) => + if s = #trueString (Settings.currentDbms ()) + then Combo (Conj, []) + else if s = #falseString (Settings.currentDbms ()) + then Combo (Disj, []) + else raise Fail "Sqlcache: sqexpToFormula (SqConst a)" + | _ => raise Fail "Sqlcache: sqexpToFormula (SqConst b)") + | Sql.Computed _ => raise Fail "Sqlcache: sqexpToFormula (Computed)" + | Sql.SqKnown _ => raise Fail "Sqlcache: sqexpToFormula (SqKnown)" + | Sql.Inj _ => raise Fail "Sqlcache: sqexpToFormula (Inj)" + | Sql.SqFunc _ => raise Fail "Sqlcache: sqexpToFormula (SqFunc)" + | Sql.Unmodeled => raise Fail "Sqlcache: sqexpToFormula (Unmodeled)" + | Sql.Null => raise Fail "Sqlcache: sqexpToFormula (Null)" fun mapSqexpFields f = fn Sql.Field (t, v) => f (t, v) @@ -799,9 +822,6 @@ structure ConflictMaps = struct fun equivClasses atoms : atomExp list list option = let val uf = List.foldl UF.union' UF.empty (List.mapPartial toKnownEquality atoms) - val ineqs = List.filter (fn (cmp, _, _) => - cmp = Sql.Ne orelse cmp = Sql.Lt orelse cmp = Sql.Gt) - atoms val contradiction = fn (cmp, SOME ae1, SOME ae2) => (cmp = Sql.Ne orelse cmp = Sql.Lt orelse cmp = Sql.Gt) andalso UF.together (uf, ae1, ae2) @@ -928,7 +948,7 @@ val sequence = in List.foldl (fn (e', seq) => ESeq ((seq, loc), (e', loc))) exp exps end - | _ => raise Match + | _ => raise Fail "Sqlcache: sequence" (* Always increments negative indices as a hack we use later. *) fun incRels inc = @@ -983,7 +1003,7 @@ fun fileAllMapfoldB doExp file start = bind = doBind} MonoEnv.empty file start of Search.Continue x => x - | Search.Return _ => raise Match + | Search.Return _ => raise Fail "Sqlcache: fileAllMapfoldB" fun fileMap doExp file = #1 (fileAllMapfoldB (fn _ => fn e => fn _ => (doExp e, ())) file ()) @@ -1029,7 +1049,7 @@ val simplifySql = val text = case exp' of EQuery {query = text, ...} => text | EDml (text, _) => text - | _ => raise Match + | _ => raise Fail "Sqlcache: simplifySql (a)" val (newText, wrapLets, numArgs) = factorOutNontrivial text val newExp' = case exp' of EQuery q => EQuery {query = newText, @@ -1039,7 +1059,7 @@ val simplifySql = body = #body q, initial = #initial q} | EDml (_, failureMode) => EDml (newText, failureMode) - | _ => raise Match + | _ => raise Fail "Sqlcache: simplifySql (b)" in (* Increment once for each new variable just made. This is where we use the negative De Bruijn indices hack. *) @@ -1128,7 +1148,7 @@ val runSubexp : subexp * state -> exp * state = val invalInfoOfSubexp = fn Cachable (invalInfo, _) => invalInfo - | Impure _ => raise Match + | Impure _ => raise Fail "Sqlcache: invalInfoOfSubexp" fun cacheWrap (env, exp, typ, args, index) = let @@ -1275,9 +1295,11 @@ fun cacheTree (effs : IS.set) ((env, exp as (exp', loc)), state) = | NONE => mapFst Impure (mkExp state) end fun wrapBind1 f arg = - wrapBindN (fn [arg] => f arg | _ => raise Match) [arg] + wrapBindN (fn [arg] => f arg + | _ => raise Fail "Sqlcache: cacheTree (a)") [arg] fun wrapBind2 f (arg1, arg2) = - wrapBindN (fn [arg1, arg2] => f (arg1, arg2) | _ => raise Match) [arg1, arg2] + wrapBindN (fn [arg1, arg2] => f (arg1, arg2) + | _ => raise Fail "Sqlcache: cacheTree (b)") [arg1, arg2] fun wrapN f es = wrapBindN f (map (fn e => ((env, e), Unknowns 0)) es) fun wrap1 f e = wrapBind1 f ((env, e), Unknowns 0) fun wrap2 f (e1, e2) = wrapBind2 f (((env, e1), Unknowns 0), ((env, e2), Unknowns 0)) @@ -1306,7 +1328,7 @@ fun cacheTree (effs : IS.set) ((env, exp as (exp', loc)), state) = ECase (e, (ListPair.map (fn (e, (p, _)) => (p, e)) (es, cases)), {disc = disc, result = result}) - | _ => raise Match) + | _ => raise Fail "Sqlcache: cacheTree (c)") (((env, e), Unknowns 0) :: map (fn (p, e) => ((MonoEnv.patBinds env p, e), Unknowns (MonoEnv.patBindsN p))) @@ -1362,7 +1384,7 @@ structure Invalidations = struct DmlRel n => ERel n | Prim p => EPrim p (* TODO: make new type containing only these two. *) - | _ => raise Match, + | _ => raise Fail "Sqlcache: optionAtomExpToExp", loc)), loc) @@ -1409,13 +1431,13 @@ fun addFlushing ((file, {tableToIndices, indexToInvalInfo, ffiInfo, ...} : state (i, invalidations (invalInfo, dmlParsed)) (* TODO: fail more gracefully. *) (* This probably means invalidating everything.... *) - | NONE => raise Match)) + | NONE => raise Fail "Sqlcache: addFlushing (a)")) (SIMM.findList (tableToIndices, tableOfDml dmlParsed))) | NONE => NONE in case inval of (* TODO: fail more gracefully. *) - NONE => raise Match + NONE => raise Fail "Sqlcache: addFlushing (b)" | SOME invs => sequence (flushes invs @ [dmlExp]) end | e' => e' @@ -1432,29 +1454,38 @@ fun addFlushing ((file, {tableToIndices, indexToInvalInfo, ffiInfo, ...} : state (***********) (* TODO: do this less evilly by not relying on specific FFI names, please? *) -fun locksNeeded file = +fun locksNeeded (lockMap : {store : IIMM.multimap, flush : IIMM.multimap}) = + MonoUtil.Exp.fold + {typ = #2, + exp = fn (EFfiApp ("Sqlcache", x, _), state as {store, flush}) => + (case Int.fromString (String.extract (x, 5, NONE)) of + NONE => state + | SOME index => + if String.isPrefix "flush" x + then {store = store, flush = IS.add (flush, index)} + else if String.isPrefix "store" x + then {store = IS.add (store, index), flush = flush} + else state) + | (ENamed n, {store, flush}) => + {store = IS.union (store, IIMM.findSet (#store lockMap, n)), + flush = IS.union (flush, IIMM.findSet (#flush lockMap, n))} + | (_, state) => state} + {store = IS.empty, flush = IS.empty} + +fun lockMapOfFile file = transitiveAnalysis (fn ((_, name, _, e, _), state) => - MonoUtil.Exp.fold - {typ = #2, - exp = fn (EFfiApp ("Sqlcache", x, _), state as {store, flush}) => - (case Int.fromString (String.extract (x, 5, NONE)) of - NONE => state - | SOME index => - if String.isPrefix "flush" x - then {store = store, flush = IIMM.insert (flush, name, index)} - else if String.isPrefix "store" x - then {store = IIMM.insert (store, name, index), flush = flush} - else state) - | (_, state) => state} - state - e) + let + val locks = locksNeeded state e + in + {store = IIMM.insertSet (#store state, name, #store locks), + flush = IIMM.insertSet (#flush state, name, #flush locks)} + end) {store = IIMM.empty, flush = IIMM.empty} file fun exports (decls, _) = List.foldl (fn ((DExport (_, _, n, _, _, _), _), ns) => IS.add (ns, n) - | ((DTask _, _), _) => raise Fail "Sqlcache doesn't yet support tasks." | (_, ns) => ns) IS.empty decls @@ -1466,24 +1497,27 @@ fun wrapLocks (locks, (exp', loc)) = fun addLocking file = let - val {store, flush} = locksNeeded file - fun locks n = + val lockMap = lockMapOfFile file + fun lockList {store, flush} = let - val wlocks = IIMM.findSet (flush, n) - val rlocks = IIMM.findSet (store, n) - val ls = map (fn i => (i, true)) (IS.listItems wlocks) - @ map (fn i => (i, false)) (IS.listItems (IS.difference (rlocks, wlocks))) + val ls = map (fn i => (i, true)) (IS.listItems flush) + @ map (fn i => (i, false)) (IS.listItems (IS.difference (store, flush))) in ListMergeSort.sort (fn ((i, _), (j, _)) => i > j) ls end + fun locksOfName n = + lockList {store = IIMM.findSet (#flush lockMap, n), + flush =IIMM.findSet (#store lockMap, n)} + val locksOfExp = lockList o locksNeeded lockMap val expts = exports file fun doVal (v as (x, n, t, exp, s)) = if IS.member (expts, n) - then (x, n, t, wrapLocks ((locks n), exp), s) + then (x, n, t, wrapLocks ((locksOfName n), exp), s) else v val doDecl = fn (DVal v, loc) => (DVal (doVal v), loc) | (DValRec vs, loc) => (DValRec (map doVal vs), loc) + | (DTask (exp1, exp2), loc) => (DTask (exp1, wrapLocks (locksOfExp exp2, exp2)), loc) | decl => decl in mapFst (map doDecl) file -- cgit v1.2.3 From 7a49a90f8b092e1c2e58d3e754578cff3bf06b18 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Thu, 19 Nov 2015 10:31:47 -0500 Subject: Fix a few C memory bugs --- src/c/urweb.c | 10 ++++++---- src/lru_cache.sml | 16 +++++++++------- 2 files changed, 15 insertions(+), 11 deletions(-) (limited to 'src/lru_cache.sml') diff --git a/src/c/urweb.c b/src/c/urweb.c index c1cfe94c..945a6890 100644 --- a/src/c/urweb.c +++ b/src/c/urweb.c @@ -602,6 +602,8 @@ uw_context uw_init(int id, uw_loggers *lg) { ctx->remoteSock = -1; + ctx->cacheUnlock = NULL; + return ctx; } @@ -3681,7 +3683,7 @@ failure_kind uw_initialize(uw_context ctx) { if (r == 0) { uw_ensure_transaction(ctx); ctx->app->initializer(ctx); - if (ctx->app->db_commit(ctx)) + if (uw_commit(ctx)) uw_error(ctx, FATAL, "Error running SQL COMMIT"); } @@ -4626,7 +4628,7 @@ static char *uw_Sqlcache_allocKeyBuffer(char **keys, size_t numKeys) { while (numKeys-- > 0) { char* k = keys[numKeys]; if (!k) { - // Can only happen when flushihg, in which case we don't need anything past the null key. + // Can only happen when flushing, in which case we don't need anything past the null key. break; } // Leave room for separator. @@ -4695,7 +4697,7 @@ static void uw_Sqlcache_storeCommitOne(uw_Sqlcache_Cache *cache, char **keys, uw if (numKeys == 0) { entry = cache->table; if (!entry) { - entry = malloc(sizeof(uw_Sqlcache_Entry)); + entry = calloc(1, sizeof(uw_Sqlcache_Entry)); entry->key = NULL; entry->value = NULL; entry->timeInvalid = 0; @@ -4709,7 +4711,7 @@ static void uw_Sqlcache_storeCommitOne(uw_Sqlcache_Cache *cache, char **keys, uw size_t len = buf - key; entry = uw_Sqlcache_find(cache, key, len, 1); if (!entry) { - entry = malloc(sizeof(uw_Sqlcache_Entry)); + entry = calloc(1, sizeof(uw_Sqlcache_Entry)); entry->key = strdup(key); entry->value = NULL; entry->timeInvalid = 0; diff --git a/src/lru_cache.sml b/src/lru_cache.sml index e9ed5f73..5c05b261 100644 --- a/src/lru_cache.sml +++ b/src/lru_cache.sml @@ -111,16 +111,16 @@ fun setupQuery {index, params} = (* If the output is null, it means we had too much recursion, so it's a miss. *) string " if (v && v->output != NULL) {", newline, - (* string (" puts(\"SQLCACHE: hit " ^ i ^ ".\");"), *) - (* newline, *) + (*string (" puts(\"SQLCACHE: hit " ^ i ^ ".\");"), + newline,*) string " uw_write(ctx, v->output);", newline, string " return v->result;", newline, string " } else {", newline, - (* string (" puts(\"SQLCACHE: miss " ^ i ^ ".\");"), *) - (* newline, *) + (*string (" puts(\"SQLCACHE: miss " ^ i ^ ".\");"), + newline,*) string " uw_recordingStart(ctx);", newline, string " return NULL;", @@ -136,14 +136,16 @@ fun setupQuery {index, params} = newline, string (" char *ks[] = {" ^ revArgs ^ "};"), newline, - string (" uw_Sqlcache_Value *v = malloc(sizeof(uw_Sqlcache_Value));"), + string (" uw_Sqlcache_Value *v = calloc(1, sizeof(uw_Sqlcache_Value));"), newline, string " v->result = strdup(s);", newline, string " v->output = uw_recordingRead(ctx);", newline, - (* string (" puts(\"SQLCACHE: stored " ^ i ^ ".\");"), *) - (* newline, *) + string " v->timeValid = 0;", + newline, + (*string (" puts(\"SQLCACHE: stored " ^ i ^ ".\");"), + newline,*) string (" uw_Sqlcache_store(ctx, cache" ^ i ^ ", ks, v);"), newline, string " return uw_unit_v;", -- cgit v1.2.3 From 30dd885d1fc3013be0e3c2a45b2e0117f684f40a Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Thu, 19 Nov 2015 13:18:58 -0500 Subject: Fix a read-after-free bug using a timestamp check --- src/c/urweb.c | 9 ++++++--- src/lru_cache.sml | 4 +--- 2 files changed, 7 insertions(+), 6 deletions(-) (limited to 'src/lru_cache.sml') diff --git a/src/c/urweb.c b/src/c/urweb.c index 945a6890..093a5294 100644 --- a/src/c/urweb.c +++ b/src/c/urweb.c @@ -4720,9 +4720,11 @@ static void uw_Sqlcache_storeCommitOne(uw_Sqlcache_Cache *cache, char **keys, uw } free(key); } - uw_Sqlcache_freeValue(entry->value); - entry->value = value; - entry->value->timeValid = timeNow; + if (entry->value && entry->value->timeValid < value->timeValid) { + uw_Sqlcache_freeValue(entry->value); + entry->value = value; + entry->value->timeValid = timeNow; + } pthread_rwlock_unlock(&cache->lockIn); } @@ -4807,6 +4809,7 @@ void uw_Sqlcache_store(uw_context ctx, uw_Sqlcache_Cache *cache, char **keys, uw update->keys = uw_Sqlcache_copyKeys(keys, cache->numKeys); update->value = value; update->next = NULL; + value->timeValid = uw_Sqlcache_getTimeNow(cache); if (ctx->cacheUpdateTail) { ctx->cacheUpdateTail->next = update; } else { diff --git a/src/lru_cache.sml b/src/lru_cache.sml index 5c05b261..851b4ccb 100644 --- a/src/lru_cache.sml +++ b/src/lru_cache.sml @@ -136,14 +136,12 @@ fun setupQuery {index, params} = newline, string (" char *ks[] = {" ^ revArgs ^ "};"), newline, - string (" uw_Sqlcache_Value *v = calloc(1, sizeof(uw_Sqlcache_Value));"), + string (" uw_Sqlcache_Value *v = malloc(sizeof(uw_Sqlcache_Value));"), newline, string " v->result = strdup(s);", newline, string " v->output = uw_recordingRead(ctx);", newline, - string " v->timeValid = 0;", - newline, (*string (" puts(\"SQLCACHE: stored " ^ i ^ ".\");"), newline,*) string (" uw_Sqlcache_store(ctx, cache" ^ i ^ ", ks, v);"), -- cgit v1.2.3 From 027ffcf5b2e3f71a42857547b17b0824d38a3f85 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Thu, 19 Nov 2015 16:02:04 -0500 Subject: Fix condition for installing new cache entries --- src/c/urweb.c | 26 +++++++++++++++----------- src/lru_cache.sml | 10 +++++++++- tests/fib.ur | 10 ++++++++++ 3 files changed, 34 insertions(+), 12 deletions(-) create mode 100644 tests/fib.ur (limited to 'src/lru_cache.sml') diff --git a/src/c/urweb.c b/src/c/urweb.c index 093a5294..54135666 100644 --- a/src/c/urweb.c +++ b/src/c/urweb.c @@ -504,8 +504,8 @@ struct uw_context { size_t output_buffer_size; // Sqlcache. - int numRecording; - int recordingOffset; + int numRecording, recordingCapacity; + int *recordingOffsets; uw_Sqlcache_Update *cacheUpdate; uw_Sqlcache_Update *cacheUpdateTail; uw_Sqlcache_Unlock *cacheUnlock; @@ -596,7 +596,8 @@ uw_context uw_init(int id, uw_loggers *lg) { ctx->output_buffer_size = 1; ctx->numRecording = 0; - ctx->recordingOffset = 0; + ctx->recordingCapacity = 0; + ctx->recordingOffsets = malloc(0); ctx->cacheUpdate = NULL; ctx->cacheUpdateTail = NULL; @@ -669,6 +670,8 @@ void uw_free(uw_context ctx) { free(ctx->output_buffer); + free(ctx->recordingOffsets); + free(ctx); } @@ -692,6 +695,7 @@ void uw_reset_keep_error_message(uw_context ctx) { ctx->usedSig = 0; ctx->needsResig = 0; ctx->remoteSock = -1; + ctx->numRecording = 0; } void uw_reset_keep_request(uw_context ctx) { @@ -1739,17 +1743,16 @@ void uw_write(uw_context ctx, const char* s) { } void uw_recordingStart(uw_context ctx) { - if (ctx->numRecording++ == 0) { - ctx->recordingOffset = ctx->page.front - ctx->page.start; + if (ctx->numRecording == ctx->recordingCapacity) { + ++ctx->recordingCapacity; + ctx->recordingOffsets = realloc(ctx->recordingOffsets, sizeof(int) * ctx->recordingCapacity); } + ctx->recordingOffsets[ctx->numRecording] = ctx->page.front - ctx->page.start; + ++ctx->numRecording; } char *uw_recordingRead(uw_context ctx) { - // Only the outermost recorder can read unless the recording is empty. - char *recording = ctx->page.start + ctx->recordingOffset; - if (--ctx->numRecording > 0 && recording != ctx->page.front) { - return NULL; - } + char *recording = ctx->page.start + ctx->recordingOffsets[--ctx->numRecording]; return strdup(recording); } @@ -4709,6 +4712,7 @@ static void uw_Sqlcache_storeCommitOne(uw_Sqlcache_Cache *cache, char **keys, uw while (numKeys-- > 0) { buf = uw_Sqlcache_keyCopy(buf, keys[numKeys]); size_t len = buf - key; + entry = uw_Sqlcache_find(cache, key, len, 1); if (!entry) { entry = calloc(1, sizeof(uw_Sqlcache_Entry)); @@ -4720,7 +4724,7 @@ static void uw_Sqlcache_storeCommitOne(uw_Sqlcache_Cache *cache, char **keys, uw } free(key); } - if (entry->value && entry->value->timeValid < value->timeValid) { + if (!entry->value || entry->value->timeValid < value->timeValid) { uw_Sqlcache_freeValue(entry->value); entry->value = value; entry->value->timeValid = timeNow; diff --git a/src/lru_cache.sml b/src/lru_cache.sml index 851b4ccb..81000458 100644 --- a/src/lru_cache.sml +++ b/src/lru_cache.sml @@ -65,6 +65,7 @@ fun setupQuery {index, params} = val revArgs = paramRepeatRev (fn p => "p" ^ p) ", " + val argNums = List.tabulate (params, fn i => "p" ^ Int.toString i) in Print.box [string ("static uw_Sqlcache_Cache cacheStruct" ^ i ^ " = {"), @@ -119,7 +120,12 @@ fun setupQuery {index, params} = newline, string " } else {", newline, - (*string (" puts(\"SQLCACHE: miss " ^ i ^ ".\");"), + (*string (" printf(\"SQLCACHE: miss " ^ i ^ " " ^ String.concatWith ", " (List.tabulate (params, fn _ => "%s")) ^ ".\\n\""), + (case argNums of + [] => Print.box [] + | _ => Print.box [string ", ", + p_list string argNums]), + string ");", newline,*) string " uw_recordingStart(ctx);", newline, @@ -159,6 +165,8 @@ fun setupQuery {index, params} = newline, string (" uw_Sqlcache_flush(ctx, cache" ^ i ^ ", ks);"), newline, + (*string (" puts(\"SQLCACHE: flushed " ^ i ^ ".\");"), + newline,*) string " return uw_unit_v;", newline, string "}", diff --git a/tests/fib.ur b/tests/fib.ur new file mode 100644 index 00000000..9d7fd340 --- /dev/null +++ b/tests/fib.ur @@ -0,0 +1,10 @@ +fun fib n = + if n = 0 then + 0 + else if n = 1 then + 1 + else + fib (n - 1) + fib (n - 2) + +fun main n : transaction page = + return {[fib n]} -- cgit v1.2.3