Files
Arpad Borsos 62c9810094 fix: Avoid assertion when hitting shutdown_timeout (#323)
This reworks the task locking and background worker shutdown logic.
Tasks are now refcounted so they can be executed concurrently while another thread removes them from the queue. In case the background worker can’t be cleanly shut down (due to slow network), the task queue is being dumped, but the background worker itself is not freed from the main thread, to avoid potential use-after-free bugs.
Also, the background worker itself is refcounted and owns its state.

The transport state is now owned by the background worker, and passed explicitly to the task send function. The task state itself was removed in favor of providing the envelope directly.
2020-07-09 12:58:22 +02:00

134 lines
3.3 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "sentry_core.h"
#include "sentry_sync.h"
#include "sentry_testsupport.h"
#ifdef SENTRY_PLATFORM_WINDOWS
# include <windows.h>
# define sleep_s(SECONDS) Sleep((SECONDS)*1000)
#else
# include <unistd.h>
# define sleep_s(SECONDS) sleep(SECONDS)
#endif
struct task_state {
int executed;
bool running;
};
static void
task_func(void *data, void *UNUSED(state))
{
struct task_state *state = data;
state->executed++;
}
static void
cleanup_func(void *data)
{
struct task_state *state = data;
state->running = false;
}
SENTRY_TEST(background_worker)
{
for (size_t i = 0; i < 100; i++) {
sentry_bgworker_t *bgw = sentry__bgworker_new(NULL, NULL);
TEST_CHECK(!!bgw);
sentry__bgworker_start(bgw);
struct task_state ts;
ts.executed = 0;
ts.running = true;
for (size_t j = 0; j < 10; j++) {
sentry__bgworker_submit(bgw, task_func, cleanup_func, &ts);
}
TEST_CHECK_INT_EQUAL(sentry__bgworker_shutdown(bgw, 5000), 0);
sentry__bgworker_decref(bgw);
TEST_CHECK_INT_EQUAL(ts.executed, 10);
TEST_CHECK(!ts.running);
}
}
static void
sleep_task(void *UNUSED(data), void *UNUSED(state))
{
sleep_s(1);
}
static void
trailing_task(void *data, void *UNUSED(state))
{
bool *executed = (bool *)data;
*executed = true;
}
static bool
drop_lessthan(void *task, void *data)
{
return (size_t)task < (size_t)data;
}
static bool
drop_greaterthan(void *task, void *data)
{
return (size_t)task > (size_t)data;
}
static bool
collect(void *task, void *data)
{
sentry_value_t *list = (sentry_value_t *)data;
sentry_value_append(*list, sentry_value_new_int32((int32_t)(size_t)task));
return true;
}
SENTRY_TEST(task_queue)
{
sentry_bgworker_t *bgw = sentry__bgworker_new(NULL, NULL);
sentry__bgworker_submit(bgw, sleep_task, NULL, NULL);
sentry__bgworker_decref(bgw);
bgw = sentry__bgworker_new(NULL, NULL);
// submit before starting
for (size_t i = 0; i < 20; i++) {
sentry__bgworker_submit(bgw, sleep_task, NULL, (void *)(i % 10));
}
sentry__bgworker_start(bgw);
size_t dropped = 0;
dropped = sentry__bgworker_foreach_matching(
bgw, sleep_task, drop_lessthan, (void *)4);
TEST_CHECK_INT_EQUAL(dropped, 8);
dropped = sentry__bgworker_foreach_matching(
bgw, sleep_task, drop_greaterthan, (void *)6);
TEST_CHECK_INT_EQUAL(dropped, 6);
int shutdown = sentry__bgworker_shutdown(bgw, 500);
TEST_CHECK_INT_EQUAL(shutdown, 1);
// submit another task to the worker which is still in shutdown
bool executed_after_shutdown = false;
sentry__bgworker_submit(bgw, trailing_task, NULL, &executed_after_shutdown);
sentry_value_t list = sentry_value_new_list();
dropped
= sentry__bgworker_foreach_matching(bgw, sleep_task, collect, &list);
TEST_CHECK_INT_EQUAL(dropped, 6);
TEST_CHECK_JSON_VALUE(list, "[4,5,6,4,5,6]");
sentry_value_decref(list);
sentry__bgworker_decref(bgw);
// the worker is still "executing" one task, so lets sleep here as well so
// we dont leak
sleep_s(1);
// the worker will still execute tasks as long as there are some, even if it
// was instructed to shut down
TEST_CHECK(executed_after_shutdown);
}