blob: c9c30b2fa7e63840bb83191b946752e72a188991 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2013 Wind River Systems, Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
* You may elect to redistribute this code under either of these licenses.
*
* Contributors:
* Wind River Systems - initial API and implementation
*******************************************************************************/
/*
* Abstract asynchronous data cache support.
*/
#include <tcf/config.h>
#include <assert.h>
#include <string.h>
#include <tcf/framework/errors.h>
#include <tcf/framework/exceptions.h>
#include <tcf/framework/myalloc.h>
#include <tcf/framework/events.h>
#include <tcf/framework/trace.h>
#include <tcf/framework/cache.h>
typedef struct WaitingCacheClient {
unsigned id;
CacheClient * client;
Channel * channel;
void * args;
size_t args_size;
int args_copy;
#ifndef NDEBUG
time_t time_stamp;
const char * file;
int line;
#endif
} WaitingCacheClient;
static WaitingCacheClient current_client = {0, 0, 0, 0, 0, 0};
static int client_exited = 0;
static int cache_miss_cnt = 0;
static WaitingCacheClient * wait_list_buf;
static unsigned wait_list_max;
static unsigned id_cnt = 0;
static LINK cache_list = TCF_LIST_INIT(cache_list);
static Channel * def_channel = NULL;
static const char * channel_lock_msg = "Cache client lock";
#define link_all2cache(x) ((AbstractCache *)((char *)(x) - offsetof(AbstractCache, link)))
#ifndef NDEBUG
/* Print cache items that are waiting too long to be filled.
* In most cases such items indicate a bug in the agent code. */
static int cache_timer_posted = 0;
static void cache_timer(void * x) {
LINK * l;
time_t time_now = time(NULL);
assert(cache_timer_posted);
cache_timer_posted = 0;
for (l = cache_list.next; l != &cache_list; l = l->next) {
unsigned i;
AbstractCache * cache = link_all2cache(l);
assert(cache->wait_list_cnt > 0);
for (i = 0; i < cache->wait_list_cnt; i++) {
WaitingCacheClient * client = cache->wait_list_buf + i;
if (time_now - client->time_stamp >= 30) {
/* Client is waiting longer than 30 sec - it might be a bug */
trace(LOG_ALWAYS, "Stalled cache at %s:%d", client->file, client->line);
}
}
}
if (!list_is_empty(&cache_list)) {
post_event_with_delay(cache_timer, NULL, 5000000);
cache_timer_posted = 1;
}
}
#endif
static void run_cache_client(void) {
Trap trap;
cache_miss_cnt = 0;
client_exited = 0;
def_channel = NULL;
if (set_trap(&trap)) {
current_client.client(current_client.args);
clear_trap(&trap);
assert(cache_miss_cnt == 0);
assert(client_exited);
}
else if (get_error_code(trap.error) != ERR_CACHE_MISS || client_exited || cache_miss_cnt == 0) {
trace(LOG_ALWAYS, "Unhandled exception in data cache client: %d %s", trap.error, errno_to_str(trap.error));
}
if (cache_miss_cnt == 0 && current_client.args_copy) loc_free(current_client.args);
memset(&current_client, 0, sizeof(current_client));
cache_miss_cnt = 0;
client_exited = 0;
def_channel = NULL;
}
void cache_enter(CacheClient * client, Channel * channel, void * args, size_t args_size) {
assert(is_dispatch_thread());
assert(client != NULL);
assert(channel == NULL || !is_channel_closed(channel));
assert(current_client.client == NULL);
current_client.id = id_cnt++;
current_client.client = client;
current_client.channel = channel;
current_client.args = args;
current_client.args_size = args_size;
current_client.args_copy = 0;
run_cache_client();
}
void cache_exit(void) {
assert(is_dispatch_thread());
assert(current_client.client != NULL);
assert(!client_exited);
if (cache_miss_cnt > 0) exception(ERR_CACHE_MISS);
client_exited = 1;
}
#ifdef NDEBUG
void cache_wait(AbstractCache * cache) {
#else
void cache_wait_dbg(const char * file, int line, AbstractCache * cache) {
#endif
assert(is_dispatch_thread());
assert(client_exited == 0);
if (current_client.client != NULL && cache_miss_cnt == 0) {
assert(current_client.channel == NULL || !is_channel_closed(current_client.channel));
if (cache->wait_list_cnt >= cache->wait_list_max) {
cache->wait_list_max += 8;
cache->wait_list_buf = (WaitingCacheClient *)loc_realloc(cache->wait_list_buf, cache->wait_list_max * sizeof(WaitingCacheClient));
}
if (current_client.args != NULL && !current_client.args_copy) {
void * mem = loc_alloc(current_client.args_size);
memcpy(mem, current_client.args, current_client.args_size);
current_client.args = mem;
current_client.args_copy = 1;
}
#ifndef NDEBUG
current_client.file = file;
current_client.line = line;
current_client.time_stamp = time(NULL);
if (!cache_timer_posted) {
post_event_with_delay(cache_timer, NULL, 5000000);
cache_timer_posted = 1;
}
#endif
if (cache->wait_list_cnt == 0) list_add_last(&cache->link, &cache_list);
if (current_client.channel != NULL) channel_lock_with_msg(current_client.channel, channel_lock_msg);
cache->wait_list_buf[cache->wait_list_cnt++] = current_client;
}
#ifndef NDEBUG
else if (current_client.client == NULL) {
trace(LOG_ALWAYS, "Illegal cache access at %s:%d", file, line);
}
#endif
cache_miss_cnt++;
exception(ERR_CACHE_MISS);
}
void cache_notify(AbstractCache * cache) {
unsigned i;
unsigned cnt = cache->wait_list_cnt;
assert(is_dispatch_thread());
if (cnt == 0) return;
list_remove(&cache->link);
cache->wait_list_cnt = 0;
if (wait_list_max < cnt) {
wait_list_max = cnt;
wait_list_buf = (WaitingCacheClient *)loc_realloc(wait_list_buf, cnt * sizeof(WaitingCacheClient));
}
memcpy(wait_list_buf, cache->wait_list_buf, cnt * sizeof(WaitingCacheClient));
for (i = 0; i < cnt; i++) {
current_client = wait_list_buf[i];
run_cache_client();
if (wait_list_buf[i].channel != NULL) channel_unlock_with_msg(wait_list_buf[i].channel, channel_lock_msg);
}
}
static void cache_notify_event(void * args) {
unsigned i;
WaitingCacheClient * buf = (WaitingCacheClient *)args;
for (i = 0; buf[i].client != NULL; i++) {
current_client = buf[i];
run_cache_client();
if (buf[i].channel != NULL) channel_unlock_with_msg(buf[i].channel, channel_lock_msg);
}
loc_free(buf);
}
void cache_notify_later(AbstractCache * cache) {
unsigned cnt = cache->wait_list_cnt;
unsigned max = cache->wait_list_max;
WaitingCacheClient * buf = cache->wait_list_buf;
assert(is_dispatch_thread());
if (cnt == 0) return;
list_remove(&cache->link);
cache->wait_list_buf = NULL;
cache->wait_list_cnt = 0;
cache->wait_list_max = 0;
if (max <= cnt) {
max = cnt + 1;
buf = (WaitingCacheClient *)loc_realloc(buf, max * sizeof(WaitingCacheClient));
}
memset(buf + cnt, 0, sizeof(WaitingCacheClient));
post_event(cache_notify_event, buf);
}
Channel * cache_channel(void) {
if (current_client.channel != NULL) return current_client.channel;
return def_channel;
}
void cache_set_def_channel(Channel * channel) {
def_channel = channel;
}
unsigned cache_transaction_id(void) {
return current_client.id;
}
void cache_dispose(AbstractCache * cache) {
assert(is_dispatch_thread());
assert(cache->wait_list_cnt == 0);
assert(list_is_empty(&cache->link));
loc_free(cache->wait_list_buf);
memset(cache, 0, sizeof(*cache));
}