int dt_cache_remove(dt_cache_t *cache, const uint32_t key) { gpointer orig_key, value; gboolean res; int result; dt_cache_entry_t *entry; restart: dt_pthread_mutex_lock(&cache->lock); res = g_hash_table_lookup_extended( cache->hashtable, GINT_TO_POINTER(key), &orig_key, &value); entry = (dt_cache_entry_t *)value; if(!res) { // not found in cache, not deleting. dt_pthread_mutex_unlock(&cache->lock); return 1; } // need write lock to be able to delete: result = dt_pthread_rwlock_trywrlock(&entry->lock); if(result) { dt_pthread_mutex_unlock(&cache->lock); g_usleep(5); goto restart; } if(entry->_lock_demoting) { // oops, we are currently demoting (rw -> r) lock to this entry in some thread. do not touch! dt_pthread_rwlock_unlock(&entry->lock); dt_pthread_mutex_unlock(&cache->lock); g_usleep(5); goto restart; } gboolean removed = g_hash_table_remove(cache->hashtable, GINT_TO_POINTER(key)); (void)removed; // make non-assert compile happy assert(removed); cache->lru = g_list_delete_link(cache->lru, entry->link); if(cache->cleanup) { assert(entry->data_size); ASAN_UNPOISON_MEMORY_REGION(entry->data, entry->data_size); cache->cleanup(cache->cleanup_data, entry); } else dt_free_align(entry->data); dt_pthread_rwlock_unlock(&entry->lock); dt_pthread_rwlock_destroy(&entry->lock); cache->cost -= entry->cost; g_slice_free1(sizeof(*entry), entry); dt_pthread_mutex_unlock(&cache->lock); return 0; }
// best-effort garbage collection. never blocks, never fails. well, sometimes it just doesn't free anything. void dt_cache_gc(dt_cache_t *cache, const float fill_ratio) { GList *l = cache->lru; int cnt = 0; while(l) { cnt++; dt_cache_entry_t *entry = (dt_cache_entry_t *)l->data; assert(entry->link->data == entry); l = g_list_next(l); // we might remove this element, so walk to the next one while we still have the pointer.. if(cache->cost < cache->cost_quota * fill_ratio) break; // if still locked by anyone else give up: if(dt_pthread_rwlock_trywrlock(&entry->lock)) continue; if(entry->_lock_demoting) { // oops, we are currently demoting (rw -> r) lock to this entry in some thread. do not touch! dt_pthread_rwlock_unlock(&entry->lock); continue; } // delete! g_hash_table_remove(cache->hashtable, GINT_TO_POINTER(entry->key)); cache->lru = g_list_delete_link(cache->lru, entry->link); cache->cost -= entry->cost; if(cache->cleanup) { assert(entry->data_size); ASAN_UNPOISON_MEMORY_REGION(entry->data, entry->data_size); cache->cleanup(cache->cleanup_data, entry); } else dt_free_align(entry->data); dt_pthread_rwlock_unlock(&entry->lock); dt_pthread_rwlock_destroy(&entry->lock); g_slice_free1(sizeof(*entry), entry); } }
void dt_cache_release_with_caller(dt_cache_t *cache, dt_cache_entry_t *entry, const char *file, int line) { #if((__has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__)) && 1) // yes, this is *HIGHLY* unportable and is accessing implementation details. #ifdef _DEBUG if(entry->lock.lock.__data.__nr_readers <= 1) #else if(entry->lock.__data.__nr_readers <= 1) #endif { // only if there are no other reades we may poison. assert(entry->data_size); ASAN_POISON_MEMORY_REGION(entry->data, entry->data_size); } #endif dt_pthread_rwlock_unlock(&entry->lock); }
void dt_cache_release(dt_cache_t *cache, dt_cache_entry_t *entry) { dt_pthread_rwlock_unlock(&entry->lock); }