diff --git a/include/discord-internal.h b/include/discord-internal.h index 17eb2a59..8459c68a 100644 --- a/include/discord-internal.h +++ b/include/discord-internal.h @@ -29,6 +29,7 @@ extern "C" { #include "queue.h" #include "priority_queue.h" #include "attributes.h" +#include "anomap.h" /** @brief Return 1 if string isn't considered empty */ #define NOT_EMPTY_STR(str) ((str) && *(str)) @@ -938,17 +939,9 @@ void discord_gateway_dispatch(struct discord_gateway *gw); struct discord_refcounter { /** `DISCORD_REFCOUNT` logging module */ struct logconf conf; - /** amount of individual user's data held for automatic cleanup */ - int length; - /** cap before increase */ - int capacity; - /** - * individual user's data held for automatic cleanup - * @note datatype declared at discord-refcount.c - */ - struct _discord_ref *refs; - /** global lock */ - pthread_mutex_t *g_lock; + + struct anomap *maps[16]; + pthread_mutex_t locks[16]; }; /** @@ -971,6 +964,7 @@ void discord_refcounter_init(struct discord_refcounter *rc, * @param should_free whether `data` cleanup should be followed by a free() */ void discord_refcounter_add_internal(struct discord_refcounter *rc, + const char * name, void *data, void (*cleanup)(void *data), bool should_free); @@ -985,6 +979,7 @@ void discord_refcounter_add_internal(struct discord_refcounter *rc, * @param should_free whether `data` cleanup should be followed by a free() */ void discord_refcounter_add_client(struct discord_refcounter *rc, + const char * name, void *data, void (*cleanup)(struct discord *client, void *data), diff --git a/src/channel.c b/src/channel.c index 000b7ff2..236ad612 100644 --- a/src/channel.c +++ b/src/channel.c @@ -84,7 +84,8 @@ discord_get_channel_at_pos(struct discord *client, && CCORD_RESOURCE_UNAVAILABLE == discord_refcounter_incr(&client->refcounter, ret->data)) { - discord_refcounter_add_client(&client->refcounter, ret->data, + discord_refcounter_add_client(&client->refcounter, + "discord_get_channel_at_pos", ret->data, ret->cleanup, false); } diff --git a/src/discord-cache.c b/src/discord-cache.c index 39aa0027..e93ec4e6 100644 --- a/src/discord-cache.c +++ b/src/discord-cache.c @@ -111,7 +111,7 @@ _on_shard_disconnected(struct discord *client, memset(guild, 0, sizeof *guild); \ discord_guild_from_json(buf, size, guild); \ discord_refcounter_add_internal( \ - &client->refcounter, guild, \ + &client->refcounter, "cache_guild", guild, \ (void (*)(void *))discord_guild_cleanup, true); \ } while (0) diff --git a/src/discord-gateway_dispatch.c b/src/discord-gateway_dispatch.c index b8df9e29..83052f27 100644 --- a/src/discord-gateway_dispatch.c +++ b/src/discord-gateway_dispatch.c @@ -120,6 +120,7 @@ discord_gateway_dispatch(struct discord_gateway *gw) == discord_refcounter_incr(&client->refcounter, event_data)) { discord_refcounter_add_internal(&client->refcounter, + "discord_gateway_dispatch", event_data, dispatch[event].cleanup, true); } diff --git a/src/discord-messagecommands.c b/src/discord-messagecommands.c index 758b97ee..c47d8d0a 100644 --- a/src/discord-messagecommands.c +++ b/src/discord-messagecommands.c @@ -169,8 +169,9 @@ discord_message_commands_try_perform(struct discord_message_commands *cmds, if (CCORD_RESOURCE_UNAVAILABLE == discord_refcounter_incr(&client->refcounter, event_data)) { - discord_refcounter_add_internal(&client->refcounter, event_data, - _discord_message_cleanup_v, false); + discord_refcounter_add_internal( + &client->refcounter, "discord_message_commands_try_perform", + event_data, _discord_message_cleanup_v, false); } callback(client, event_data); event_data->content = tmp; /* retrieve original ptr */ diff --git a/src/discord-refcount.c b/src/discord-refcount.c index 4e3dd66b..6dffb02a 100644 --- a/src/discord-refcount.c +++ b/src/discord-refcount.c @@ -5,24 +5,10 @@ #include "discord.h" #include "discord-internal.h" -#define CHASH_BUCKETS_FIELD refs -#include "chash.h" - -/* chash heap-mode (auto-increase hashtable) */ -#define REFCOUNTER_TABLE_HEAP 1 -#define REFCOUNTER_TABLE_BUCKET struct _discord_ref -#define REFCOUNTER_TABLE_FREE_KEY(_key) -#define REFCOUNTER_TABLE_HASH(_key, _hash) ((intptr_t)(_key)) -#define REFCOUNTER_TABLE_FREE_VALUE(_value) \ - _discord_refvalue_cleanup(rc, &_value) -#define REFCOUNTER_TABLE_COMPARE(_cmp_a, _cmp_b) (_cmp_a == _cmp_b) -#define REFCOUNTER_TABLE_INIT(ref, _key, _value) \ - memset(&ref, 0, sizeof(ref)); \ - chash_default_init(ref, _key, _value) +ANOMAP_DECLARE_COMPARE_FUNCTION(_cmp_ptr, void *) struct _discord_refvalue { - /** user arbitrary data to be retrieved at `done` or `fail` callbacks */ - void *data; + const char *const name; /** * cleanup for when `data` is no longer needed * @note this only has to be assigned once, it is automatically called once @@ -31,244 +17,189 @@ struct _discord_refvalue { void (*client)(struct discord *client, void *data); void (*internal)(void *data); } cleanup; + /** how many times this resource has been discord_claim() 'd */ + int claims; /** - * `data` references count - * @note if `-1` then `data` has been claimed with - * discord_refcounter_claim() and will be cleaned up once - * discord_refcount_unclaim() is called + * how many references the internal client is holding */ - int visits; + short visits; /** whether `data` cleanup should also be followed by a free() */ bool should_free; /** whether cleanup expects a client parameter */ bool expects_client; - /** how many times this resource has been discord_claim() 'd */ - int claims; -}; - -struct _discord_ref { - /** key is the user data's address */ - intptr_t key; - /** holds the user data and information for automatic cleanup */ - struct _discord_refvalue value; - /** the route state in the hashtable (see chash.h 'State enums') */ - int state; }; static void -_discord_refvalue_cleanup(struct discord_refcounter *rc, - struct _discord_refvalue *value) +_on_ref_map_changed(const struct anomap_item_changed *ev) { - if (value->cleanup.client) { - if (value->expects_client) - value->cleanup.client(CLIENT(rc, refcounter), value->data); - else - value->cleanup.internal(value->data); + struct discord_refcounter *rc = ev->data; + if (ev->op & anomap_delete) { + struct _discord_refvalue *val = ev->val.prev; + logconf_trace(&rc->conf, "deleting %s %p", val->name, + *(void **)ev->key); + if (val->cleanup.client) { + if (val->expects_client) + val->cleanup.client(CLIENT(ev->data, refcounter), + *(void **)ev->key); + else + val->cleanup.internal(*(void **)ev->key); + } + if (val->should_free) free(*(void **)ev->key); } - if (value->should_free) free(value->data); -} - -static struct _discord_refvalue * -_discord_refvalue_find(struct discord_refcounter *rc, const void *data) -{ - struct _discord_ref *ref = - chash_lookup_bucket(rc, (intptr_t)data, ref, REFCOUNTER_TABLE); - return &ref->value; -} - -static void -_discord_refvalue_init(struct discord_refcounter *rc, - void *data, - struct _discord_refvalue *init_fields) -{ - init_fields->data = data; - init_fields->visits = 1; - chash_assign(rc, (intptr_t)data, *init_fields, REFCOUNTER_TABLE); -} - -static void -_discord_refvalue_delete(struct discord_refcounter *rc, void *data) -{ - chash_delete(rc, (intptr_t)data, REFCOUNTER_TABLE); } void discord_refcounter_init(struct discord_refcounter *rc, struct logconf *conf) { logconf_branch(&rc->conf, conf, "DISCORD_REFCOUNT"); - - __chash_init(rc, REFCOUNTER_TABLE); - - rc->g_lock = malloc(sizeof *rc->g_lock); - ASSERT_S(!pthread_mutex_init(rc->g_lock, NULL), - "Couldn't initialize refcounter mutex"); + for (int i = 0; i < 16; i++) { + rc->maps[i] = + anomap_create(sizeof(void *), sizeof(struct _discord_refvalue), + _cmp_ptr, anomap_direct_access); + anomap_set_on_item_changed(rc->maps[i], _on_ref_map_changed, rc); + pthread_mutex_init(&rc->locks[i], NULL); + } } void discord_refcounter_cleanup(struct discord_refcounter *rc) { - __chash_free(rc, REFCOUNTER_TABLE); - pthread_mutex_destroy(rc->g_lock); - free(rc->g_lock); + for (int i = 0; i < 16; i++) { + anomap_destroy(rc->maps[i]); + pthread_mutex_destroy(&rc->locks[i]); + } } -static bool -_discord_refcounter_contains(struct discord_refcounter *rc, const void *data) +static size_t +_get_map_index(const void *data) { - bool ret = chash_contains(rc, (intptr_t)data, ret, REFCOUNTER_TABLE); - return ret; + return (((uintptr_t)data) >> 6) & 0xF; } static CCORDcode -_discord_refcounter_incr_no_lock(struct discord_refcounter *rc, void *data) +_discord_refcounter_mod_no_lock(struct discord_refcounter *rc, + const void *data, + short visits, + int claims) { - CCORDcode code = CCORD_RESOURCE_UNAVAILABLE; - if (_discord_refcounter_contains(rc, data)) { - struct _discord_refvalue *value = _discord_refvalue_find(rc, data); + struct anomap *map = rc->maps[_get_map_index(data)]; + size_t index; + bool found = anomap_index_of(map, &data, &index); + struct _discord_refvalue *val; + if (!found) { + logconf_trace(&rc->conf, "%p not found", data); + return CCORD_RESOURCE_UNAVAILABLE; + } - if (value->visits == INT_MAX) { - logconf_error(&rc->conf, - "Can't increment %p any further: Overflow", data); - } - else { - ++value->visits; - logconf_trace(&rc->conf, "Increment %p (%d visits)", data, - value->visits); - code = CCORD_OK; - } + val = anomap_direct_val_at_index(map, index); + static const char *operation_name[] = { "decrementing", "incrementing" }; + + if (0 != visits) + logconf_trace(&rc->conf, "visits %p %s by %i to %i", data, + operation_name[visits > 0], + visits > 0 ? visits : -visits, val->visits += visits); + + if (0 != claims) + logconf_trace(&rc->conf, "claims %p %s by %i to %i", data, + operation_name[claims > 0], + claims > 0 ? claims : -claims, val->claims += claims); + + if (0 <= val->visits && 0 <= val->claims) { + if (0 == val->visits && 0 == val->claims) + anomap_do(map, anomap_delete, &data, NULL); + return CCORD_OK; } - return code; + + if (0 > val->visits) + logconf_error(&rc->conf, "visits for %p below 0", data); + + if (0 > val->claims) + logconf_error(&rc->conf, "claims for %p below 0", data); + + return CCORD_RESOURCE_UNAVAILABLE; } static CCORDcode -_discord_refcounter_decr_no_lock(struct discord_refcounter *rc, void *data) +_discord_refcounter_mod(struct discord_refcounter *rc, + const void *data, + short visits, + int claims) { - CCORDcode code = CCORD_RESOURCE_UNAVAILABLE; - if (_discord_refcounter_contains(rc, data)) { - struct _discord_refvalue *value = _discord_refvalue_find(rc, data); - - if (value->visits < value->claims) { - logconf_error(&rc->conf, - "(Internal Error) There shouldn't be more visits " - "than claims!"); - } - else if (--value->visits > 0) { - code = CCORD_OK; - logconf_trace(&rc->conf, "Decrement %p (%d visits)", data, - value->visits); - } - else { - if (value->claims != 0) { - logconf_error(&rc->conf, "(Internal Error) Caught attempt to " - "cleanup claimed resource!"); - ++value->visits; - code = CCORD_RESOURCE_OWNERSHIP; - } - else { - _discord_refvalue_delete(rc, data); - logconf_info(&rc->conf, "Fully decremented and free'd %p", - data); - code = CCORD_OK; - } - } - } + size_t arrayi = _get_map_index(data); + pthread_mutex_lock(&rc->locks[arrayi]); + CCORDcode code = _discord_refcounter_mod_no_lock(rc, data, visits, claims); + pthread_mutex_unlock(&rc->locks[arrayi]); return code; } CCORDcode discord_refcounter_claim(struct discord_refcounter *rc, const void *data) { - CCORDcode code = CCORD_RESOURCE_UNAVAILABLE; - - pthread_mutex_lock(rc->g_lock); - if (_discord_refcounter_contains(rc, data)) { - struct _discord_refvalue *value = _discord_refvalue_find(rc, data); - - ++value->claims; - code = _discord_refcounter_incr_no_lock(rc, (void *)data); - logconf_trace(&rc->conf, "Claiming %p (claims: %d)", data, - value->claims); - } - pthread_mutex_unlock(rc->g_lock); - return code; + return _discord_refcounter_mod(rc, data, 0, 1); } CCORDcode discord_refcounter_unclaim(struct discord_refcounter *rc, void *data) { - CCORDcode code = CCORD_RESOURCE_UNAVAILABLE; - - pthread_mutex_lock(rc->g_lock); - if (_discord_refcounter_contains(rc, data)) { - struct _discord_refvalue *value = _discord_refvalue_find(rc, data); - - if (0 == value->claims) { - logconf_error(&rc->conf, "Resource hasn't been claimed before, or " - "it has already been unclaimed"); - } - else { - --value->claims; - logconf_trace(&rc->conf, "Unclaiming %p (claims: %d)", data, - value->claims); - code = _discord_refcounter_decr_no_lock(rc, data); - } - } - pthread_mutex_unlock(rc->g_lock); - - return code; + return _discord_refcounter_mod(rc, data, 0, -1); } void discord_refcounter_add_internal(struct discord_refcounter *rc, + const char *name, void *data, void (*cleanup)(void *data), bool should_free) { - pthread_mutex_lock(rc->g_lock); - _discord_refvalue_init(rc, data, - &(struct _discord_refvalue){ - .expects_client = false, - .cleanup.internal = cleanup, - .should_free = should_free, - }); - logconf_info(&rc->conf, "Adding concord's internal resource %p", data); - pthread_mutex_unlock(rc->g_lock); + size_t arrayi = _get_map_index(data); + struct anomap *map = rc->maps[arrayi]; + pthread_mutex_lock(&rc->locks[arrayi]); + struct _discord_refvalue val = { + .name = name, + .expects_client = false, + .cleanup.internal = cleanup, + .should_free = should_free, + .visits = 1, + }; + anomap_operation result = anomap_do(map, anomap_insert, &data, &val); + logconf_trace(&rc->conf, "Adding concord's internal resource %s %p %s", + name, data, + result & anomap_insert ? "successful" : "failed"); + pthread_mutex_unlock(&rc->locks[arrayi]); } void discord_refcounter_add_client(struct discord_refcounter *rc, + const char *name, void *data, void (*cleanup)(struct discord *client, void *data), bool should_free) { - pthread_mutex_lock(rc->g_lock); - _discord_refvalue_init(rc, data, - &(struct _discord_refvalue){ - .expects_client = true, - .cleanup.client = cleanup, - .should_free = should_free, - }); - logconf_info(&rc->conf, "Adding user's custom resource %p", data); - pthread_mutex_unlock(rc->g_lock); + size_t arrayi = _get_map_index(data); + struct anomap *map = rc->maps[arrayi]; + pthread_mutex_lock(&rc->locks[arrayi]); + struct _discord_refvalue val = { + .name = name, + .expects_client = true, + .cleanup.client = cleanup, + .should_free = should_free, + .visits = 1, + }; + anomap_operation result = anomap_do(map, anomap_insert, &data, &val); + logconf_trace(&rc->conf, "Adding user's custom resource %s %p %s", name, + data, result & anomap_insert ? "successful" : "failed"); + pthread_mutex_unlock(&rc->locks[arrayi]); } CCORDcode discord_refcounter_incr(struct discord_refcounter *rc, void *data) { - CCORDcode code; - pthread_mutex_lock(rc->g_lock); - code = _discord_refcounter_incr_no_lock(rc, data); - pthread_mutex_unlock(rc->g_lock); - return code; + return _discord_refcounter_mod(rc, data, 1, 0); } CCORDcode discord_refcounter_decr(struct discord_refcounter *rc, void *data) { - CCORDcode code; - pthread_mutex_lock(rc->g_lock); - code = _discord_refcounter_decr_no_lock(rc, data); - pthread_mutex_unlock(rc->g_lock); - return code; + return _discord_refcounter_mod(rc, data, -1, 0); } diff --git a/src/discord-rest_request.c b/src/discord-rest_request.c index 48850136..8dd1e65b 100644 --- a/src/discord-rest_request.c +++ b/src/discord-rest_request.c @@ -391,7 +391,8 @@ discord_requestor_info_read(struct discord_requestor *rqtor) body.start); } else if (req->dispatch.has_type - && req->dispatch.sync != DISCORD_SYNC_FLAG) { + && req->dispatch.sync != DISCORD_SYNC_FLAG) + { if (req->dispatch.sync) { req->response.data = req->dispatch.sync; } @@ -399,7 +400,8 @@ discord_requestor_info_read(struct discord_requestor *rqtor) req->response.data = calloc(1, req->response.size); discord_refcounter_add_internal( &CLIENT(rqtor, rest.requestor)->refcounter, - req->response.data, req->response.cleanup, true); + "discord_requestor_info_read", req->response.data, + req->response.cleanup, true); } /* initialize ret */ @@ -631,8 +633,9 @@ discord_request_begin(struct discord_requestor *rqtor, == discord_refcounter_incr(&client->refcounter, req->dispatch.data)) { - discord_refcounter_add_client(&client->refcounter, req->dispatch.data, - req->dispatch.cleanup, false); + discord_refcounter_add_client( + &client->refcounter, "discord_request_begin", req->dispatch.data, + req->dispatch.cleanup, false); } pthread_mutex_lock(&rqtor->qlocks->pending);