mirror of
https://github.com/libretro/libretro-common.git
synced 2026-11-07 11:06:12 -10:00
resync
This commit is contained in:
1 parent
38907d073f
commit
e4529182fa
9 files changed
+502
-21
No files matched your search
@@ -40,11 +40,16 @@
|
||||
#endif
|
||||
#if defined(__APPLE__)
|
||||
#include <sys/ucontext.h>
|
||||
#include <TargetConditionals.h>
|
||||
/* tvOS marks the mach task API unavailable, and the SDK does not ship
|
||||
* the headers for it. Nothing below needs them there. */
|
||||
#if !defined(TARGET_OS_TV) || !TARGET_OS_TV
|
||||
#include <mach/mach_init.h>
|
||||
#include <mach/mach_port.h>
|
||||
#include <mach/task.h>
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* The state, and why each piece is shaped as it is:
|
||||
*
|
||||
@@ -323,9 +328,15 @@ bool retro_faulthandler_install(retro_fault_handler_t handler)
|
||||
if (ok && sigaction(SIGSEGV, &sa, &s_old_sigsegv) != 0)
|
||||
ok = false;
|
||||
#endif
|
||||
#if defined(__APPLE__) && defined(__aarch64__)
|
||||
/* Not && !TARGET_OS_TV: on a platform that never includes
|
||||
* TargetConditionals.h the name is undefined, and an undefined name in
|
||||
* a preprocessor expression is a warning under -Wundef and a silent
|
||||
* zero otherwise. Ask whether it is defined first. */
|
||||
#if defined(__APPLE__) && defined(__aarch64__) \
|
||||
&& (!defined(TARGET_OS_TV) || !TARGET_OS_TV)
|
||||
/* Keeps a debugger out of an EXC_BAD_ACCESS loop when the fault
|
||||
* is one we are going to handle. */
|
||||
* is one we are going to handle; tvOS has neither the API nor a
|
||||
* debugger to keep out. */
|
||||
if (ok)
|
||||
task_set_exception_ports(mach_task_self(), EXC_MASK_BAD_ACCESS,
|
||||
MACH_PORT_NULL, EXCEPTION_DEFAULT, 0);
|
||||
|
||||
@@ -143,6 +143,14 @@ static bool image_texture_load_internal(
|
||||
|
||||
image_transfer_set_buffer_ptr(img, type, (uint8_t*)ptr, len);
|
||||
|
||||
/* A caller that wants the extra precision says so through
|
||||
* ->pix10; the decoders that have a 10-bit path (PNG 16-bit, and
|
||||
* the video stills) then emit XRGB2101010 and report what they
|
||||
* actually produced. A decoder or a file without one leaves the
|
||||
* flag clear and the caller gets the ordinary 8-bit image. */
|
||||
if (out_img->pix10)
|
||||
image_transfer_set_want_10bit(img, type, 1);
|
||||
|
||||
if (!image_transfer_start(img, type))
|
||||
{
|
||||
image_transfer_free(img, type);
|
||||
@@ -163,6 +171,8 @@ static bool image_texture_load_internal(
|
||||
return false;
|
||||
}
|
||||
|
||||
out_img->pix10 = image_transfer_is_10bit(img, type);
|
||||
|
||||
/* GPU-native fast path: if the loader can hand back BCn blocks for
|
||||
* direct upload, copy the source (so the mip pointers survive the
|
||||
* caller freeing its buffer) and defer any RGBA8 decode. */
|
||||
@@ -392,6 +402,7 @@ bool image_texture_load_ex(struct texture_image *out_img,
|
||||
out_img->width = 0;
|
||||
out_img->height = 0;
|
||||
out_img->compressed = NULL;
|
||||
out_img->pix10 = false;
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -145,6 +145,10 @@ enum image_type_enum image_texture_get_type(const char *path);
|
||||
bool image_texture_load_buffer(struct texture_image *img,
|
||||
enum image_type_enum type, void *s, size_t len);
|
||||
|
||||
/* ->pix10 is an ask on the way in and an answer on the way out: set
|
||||
* it before the call to have a decoder that can emit XRGB2101010 do
|
||||
* so, and read it after to find out whether it did. Clear on entry
|
||||
* means the ordinary 8-bit image, as before. */
|
||||
bool image_texture_load(struct texture_image *img, const char *path);
|
||||
|
||||
/* image_texture_load with an abort hook: both decode stages are
|
||||
|
||||
@@ -138,7 +138,25 @@ enum retro_task_flags
|
||||
* If set, the task queue will not call \c progress_cb
|
||||
* and will not display any messages from this task.
|
||||
*/
|
||||
RETRO_TASK_FLG_MUTE = (1 << 3)
|
||||
RETRO_TASK_FLG_MUTE = (1 << 3),
|
||||
/**
|
||||
* Set by the pusher to promise that \c handler touches nothing
|
||||
* but the task itself - its own state, and the task_set_* and
|
||||
* task_get_* accessors - and nothing that outlives the queue or
|
||||
* is torn down with it: no logging, no pushing other tasks, no
|
||||
* subsystem the frontend frees on the way out.
|
||||
*
|
||||
* In exchange, if \c task_queue_deinit() finds the worker still
|
||||
* inside this task's handler after its bound, it stops waiting:
|
||||
* the task is taken off the queue and the worker is left to
|
||||
* finish the handler on its own. \c callback and \c cleanup never
|
||||
* run, so whatever they would have released stays allocated; the
|
||||
* task itself is freed when the handler returns. Meant for
|
||||
* handlers that can
|
||||
* block in an OS call for arbitrarily long, such as a device
|
||||
* enumeration.
|
||||
*/
|
||||
RETRO_TASK_FLG_DETACHABLE = (1 << 4)
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1065,6 +1065,13 @@ typedef volatile size_t retro_atomic_size_t;
|
||||
#define retro_atomic_fetch_sub_int(p, v) retro_atomic_fetch_sub_int_fb((p), (v))
|
||||
#define retro_atomic_fetch_or_int(p, v) retro_atomic_fetch_or_int_fb((p), (v))
|
||||
#define retro_atomic_fetch_and_int(p, v) retro_atomic_fetch_and_int_fb((p), (v))
|
||||
/* Compare-and-swap, which this backend can only offer as a plain
|
||||
* read-compare-write: correct where the fallback itself is - one core,
|
||||
* or a platform whose only preemption is cooperative - and no worse
|
||||
* than the loads and stores beside it. Callers that need a real CAS
|
||||
* are on a backend that has one. */
|
||||
#define retro_atomic_cas_int(p, expected, desired) \
|
||||
retro_atomic_cas_int_fb((p), (expected), (desired))
|
||||
|
||||
static INLINE int retro_atomic_fetch_add_int_fb(retro_atomic_int_t *p, int v)
|
||||
{
|
||||
@@ -1094,6 +1101,15 @@ static INLINE int retro_atomic_fetch_and_int_fb(retro_atomic_int_t *p, int v)
|
||||
return old;
|
||||
}
|
||||
|
||||
static INLINE bool retro_atomic_cas_int_fb(retro_atomic_int_t *p,
|
||||
int expected, int desired)
|
||||
{
|
||||
if (*p != expected)
|
||||
return false;
|
||||
*p = desired;
|
||||
return true;
|
||||
}
|
||||
|
||||
#define retro_atomic_load_acquire_size(p) (*(p))
|
||||
#define retro_atomic_load_relaxed_size(p) (*(p))
|
||||
#define retro_atomic_store_release_size(p, v) do { *(p) = (v); } while (0)
|
||||
|
||||
@@ -73,6 +73,22 @@
|
||||
#define SSL_MBED_LEGACY_RNG 1
|
||||
#endif
|
||||
|
||||
/* Mbed TLS 3.x keeps the legacy RNG but runs TLS 1.3 (and everything
|
||||
* else once MBEDTLS_USE_PSA_CRYPTO is set) on top of PSA, and leaves
|
||||
* psa_crypto_init() to the application. Without it the client cannot
|
||||
* even build its ClientHello key share and every handshake fails with
|
||||
* MBEDTLS_ERR_SSL_INTERNAL_ERROR. 3.6 compiles TLS 1.3 in by default,
|
||||
* so a system 3.6 build hits this on every connection. */
|
||||
#if SSL_MBED_LEGACY_RNG && !defined(HAVE_BUILTINMBEDTLS) \
|
||||
&& defined(MBEDTLS_VERSION_MAJOR) && MBEDTLS_VERSION_MAJOR == 3 \
|
||||
&& defined(MBEDTLS_PSA_CRYPTO_C) \
|
||||
&& (defined(MBEDTLS_SSL_PROTO_TLS1_3) || defined(MBEDTLS_USE_PSA_CRYPTO))
|
||||
#include <psa/crypto.h>
|
||||
#define SSL_MBED_LEGACY_PSA_INIT 1
|
||||
#else
|
||||
#define SSL_MBED_LEGACY_PSA_INIT 0
|
||||
#endif
|
||||
|
||||
#if defined(VITA)
|
||||
#include <psp2/kernel/rng.h>
|
||||
#endif
|
||||
@@ -181,6 +197,11 @@ void* ssl_socket_init(int fd, const char *domain)
|
||||
#endif
|
||||
&state->entropy, (const unsigned char*)pers, strlen(pers)) != 0)
|
||||
goto error;
|
||||
#if SSL_MBED_LEGACY_PSA_INIT
|
||||
/* Idempotent - safe to call once per socket. */
|
||||
if (psa_crypto_init() != PSA_SUCCESS)
|
||||
goto error;
|
||||
#endif
|
||||
#else
|
||||
/* Idempotent - safe to call once per socket. */
|
||||
if (psa_crypto_init() != PSA_SUCCESS)
|
||||
@@ -360,6 +381,11 @@ ssize_t ssl_socket_receive_all_nonblocking(void *state_data,
|
||||
*err = true;
|
||||
return -1;
|
||||
}
|
||||
#ifdef MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET
|
||||
/* TLS 1.3 servers send tickets after the handshake; not an error */
|
||||
else if (ret == MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET)
|
||||
continue;
|
||||
#endif
|
||||
else if (isagain((int)ret) || ret == MBEDTLS_ERR_SSL_WANT_READ)
|
||||
{
|
||||
/* Would block - return what we have so far */
|
||||
@@ -398,6 +424,11 @@ int ssl_socket_receive_all_blocking(void *state_data,
|
||||
|| ret == MBEDTLS_ERR_SSL_WANT_WRITE)
|
||||
continue;
|
||||
|
||||
#ifdef MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET
|
||||
if (ret == MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET)
|
||||
continue;
|
||||
#endif
|
||||
|
||||
if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY)
|
||||
break;
|
||||
|
||||
|
||||
+175
-18
@@ -100,6 +100,44 @@ static scond_t *finished_cond = NULL;
|
||||
static sthread_t *worker_thread = NULL;
|
||||
static bool worker_continue = true;
|
||||
/* use running_lock when touching it */
|
||||
|
||||
/* Where a worker is, for deinit. Per worker rather than global: a
|
||||
* worker deinit gave up on still owns its record after the next init
|
||||
* has started a new one, and must not read the new worker's. */
|
||||
enum task_worker_state
|
||||
{
|
||||
TASK_WORKER_IDLE = 0, /* in the queue's own code */
|
||||
TASK_WORKER_IN_HANDLER, /* inside task->handler() */
|
||||
TASK_WORKER_ORPHANED, /* deinit let go of it; it owns the rest */
|
||||
TASK_WORKER_EXITED /* left the loop; joinable */
|
||||
};
|
||||
|
||||
struct task_worker
|
||||
{
|
||||
retro_atomic_int_t state;
|
||||
/* The task whose handler it is in; written under running_lock. */
|
||||
retro_task_t *task;
|
||||
/* The primitives of the queue this worker was started for, so an
|
||||
* orphan can find and free its own generation's after deinit has
|
||||
* cleared the globals. Set at init, never changed. */
|
||||
slock_t *running_lock;
|
||||
slock_t *finished_lock;
|
||||
slock_t *queue_lock;
|
||||
scond_t *worker_cond;
|
||||
scond_t *finished_cond;
|
||||
};
|
||||
|
||||
static struct task_worker *worker_self = NULL;
|
||||
|
||||
/* How long deinit waits for the worker before it may leave one stuck
|
||||
* in a detachable task's handler behind. */
|
||||
#define TASK_QUEUE_DETACH_AFTER_US 1000000
|
||||
|
||||
/* Once a worker has been left behind, its handler may still be
|
||||
* setting task properties, which takes property_lock. The lock is
|
||||
* then kept for the life of the process and reused by every later
|
||||
* init instead of being freed. */
|
||||
static bool property_lock_pinned = false;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_GCD
|
||||
@@ -742,6 +780,8 @@ static void retro_task_threaded_retrieve(task_retriever_data_t *data)
|
||||
|
||||
static void threaded_worker(void *userdata)
|
||||
{
|
||||
struct task_worker *self = (struct task_worker*)userdata;
|
||||
|
||||
sthread_setname("ra-task");
|
||||
|
||||
for (;;)
|
||||
@@ -753,6 +793,10 @@ static void threaded_worker(void *userdata)
|
||||
|
||||
if (!worker_continue)
|
||||
{
|
||||
/* Tell deinit, which may be waiting on worker_cond with a
|
||||
* bound, that there is nothing left to wait for. */
|
||||
retro_atomic_store_release_int(&self->state, TASK_WORKER_EXITED);
|
||||
scond_broadcast(worker_cond);
|
||||
slock_unlock(running_lock);
|
||||
/* No: draining is the caller's job, done while the
|
||||
* subsystems that finish callbacks reach are still alive
|
||||
@@ -783,8 +827,34 @@ static void threaded_worker(void *userdata)
|
||||
}
|
||||
}
|
||||
|
||||
self->task = task;
|
||||
retro_atomic_store_release_int(&self->state, TASK_WORKER_IN_HANDLER);
|
||||
slock_unlock(running_lock);
|
||||
task->handler(task);
|
||||
|
||||
/* Deinit gave up on this handler and is taking the task off the
|
||||
* queue; the queue's globals may belong to a new init by now.
|
||||
* Deinit holds this generation's running_lock until it is done
|
||||
* letting go, so taking it once waits exactly that long. Then
|
||||
* free this generation's primitives and the task, and leave
|
||||
* without touching any global. The task's callback and cleanup
|
||||
* never run. */
|
||||
if (!retro_atomic_cas_int(&self->state,
|
||||
TASK_WORKER_IN_HANDLER, TASK_WORKER_IDLE))
|
||||
{
|
||||
slock_lock(self->running_lock);
|
||||
slock_unlock(self->running_lock);
|
||||
scond_free(self->worker_cond);
|
||||
scond_free(self->finished_cond);
|
||||
slock_free(self->running_lock);
|
||||
slock_free(self->finished_lock);
|
||||
slock_free(self->queue_lock);
|
||||
free(task->title);
|
||||
free(task->error);
|
||||
free(task);
|
||||
free(self);
|
||||
return;
|
||||
}
|
||||
#if defined(__EMSCRIPTEN__) || defined(_3DS)
|
||||
/* Workaround emscripten pthread bug where not parking the
|
||||
thread will prevent other important stuff from
|
||||
@@ -853,14 +923,17 @@ static void retro_task_sync_primitives_free(void)
|
||||
scond_free(finished_cond);
|
||||
slock_free(running_lock);
|
||||
slock_free(finished_lock);
|
||||
slock_free(property_lock);
|
||||
if (!property_lock_pinned)
|
||||
{
|
||||
slock_free(property_lock);
|
||||
property_lock = NULL;
|
||||
}
|
||||
slock_free(queue_lock);
|
||||
|
||||
worker_cond = NULL;
|
||||
finished_cond = NULL;
|
||||
running_lock = NULL;
|
||||
finished_lock = NULL;
|
||||
property_lock = NULL;
|
||||
queue_lock = NULL;
|
||||
}
|
||||
|
||||
@@ -872,7 +945,8 @@ static bool retro_task_sync_primitives_new(void)
|
||||
{
|
||||
running_lock = slock_new();
|
||||
finished_lock = slock_new();
|
||||
property_lock = slock_new();
|
||||
if (!property_lock_pinned)
|
||||
property_lock = slock_new();
|
||||
queue_lock = slock_new();
|
||||
worker_cond = scond_new();
|
||||
finished_cond = scond_new();
|
||||
@@ -894,6 +968,18 @@ static bool retro_task_threaded_init(void)
|
||||
if (!retro_task_sync_primitives_new())
|
||||
return false;
|
||||
|
||||
if (!(worker_self = (struct task_worker*)calloc(1, sizeof(*worker_self))))
|
||||
{
|
||||
retro_task_sync_primitives_free();
|
||||
return false;
|
||||
}
|
||||
retro_atomic_store_release_int(&worker_self->state, TASK_WORKER_IDLE);
|
||||
worker_self->running_lock = running_lock;
|
||||
worker_self->finished_lock = finished_lock;
|
||||
worker_self->queue_lock = queue_lock;
|
||||
worker_self->worker_cond = worker_cond;
|
||||
worker_self->finished_cond = finished_cond;
|
||||
|
||||
slock_lock(running_lock);
|
||||
worker_continue = true;
|
||||
slock_unlock(running_lock);
|
||||
@@ -902,37 +988,108 @@ static bool retro_task_threaded_init(void)
|
||||
* starts. Without a worker there is nobody to run what gets queued,
|
||||
* which is why this reports failure rather than leaving the queue
|
||||
* pointed at an implementation that cannot service it. */
|
||||
if ((worker_thread = sthread_create(threaded_worker, NULL)))
|
||||
if ((worker_thread = sthread_create(threaded_worker, worker_self)))
|
||||
return true;
|
||||
|
||||
worker_continue = false;
|
||||
free(worker_self);
|
||||
worker_self = NULL;
|
||||
retro_task_sync_primitives_free();
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Called with running_lock held, once the bound has passed and the
|
||||
* worker inside a detachable task's handler has been marked orphaned.
|
||||
* Takes the task off the queue and clears the globals; the worker,
|
||||
* blocked on this generation's running_lock as soon as its handler
|
||||
* returns, frees the primitives and the task after the unlock below,
|
||||
* which is therefore the last thing done here. property_lock stays:
|
||||
* the handler may still be setting task properties through it. */
|
||||
static void retro_task_threaded_orphan_worker(retro_task_t *task)
|
||||
{
|
||||
slock_t *lock = running_lock;
|
||||
|
||||
slock_lock(queue_lock);
|
||||
task_queue_remove(&tasks_running, task);
|
||||
slock_unlock(queue_lock);
|
||||
retro_atomic_fetch_sub_int(&tasks_running_count, 1);
|
||||
|
||||
sthread_detach(worker_thread);
|
||||
|
||||
worker_thread = NULL;
|
||||
worker_self = NULL;
|
||||
property_lock_pinned = true;
|
||||
worker_cond = NULL;
|
||||
finished_cond = NULL;
|
||||
running_lock = NULL;
|
||||
finished_lock = NULL;
|
||||
queue_lock = NULL;
|
||||
|
||||
slock_unlock(lock);
|
||||
}
|
||||
|
||||
/* Stops the worker, waiting for it with a bound. A worker that is
|
||||
* between tasks, or doing the queue's own bookkeeping, leaves as soon
|
||||
* as it sees worker_continue, so the bound only ever runs out on one
|
||||
* stuck inside a handler. If that task was pushed detachable, its
|
||||
* handler promises to touch nothing but the task itself, so the
|
||||
* worker is left to finish it alone and deinit returns. Otherwise the
|
||||
* handler may be holding state that teardown is about to free, and
|
||||
* the only safe thing is to keep waiting for it. */
|
||||
static void retro_task_threaded_deinit(void)
|
||||
{
|
||||
retro_time_t deadline = cpu_features_get_time_usec()
|
||||
+ TASK_QUEUE_DETACH_AFTER_US;
|
||||
|
||||
slock_lock(running_lock);
|
||||
worker_continue = false;
|
||||
scond_signal(worker_cond);
|
||||
slock_unlock(running_lock);
|
||||
scond_broadcast(worker_cond);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
int state = retro_atomic_load_acquire_int(&worker_self->state);
|
||||
retro_time_t left;
|
||||
|
||||
if (state == TASK_WORKER_EXITED)
|
||||
break;
|
||||
|
||||
left = deadline - cpu_features_get_time_usec();
|
||||
if (left > 0)
|
||||
{
|
||||
scond_wait_timeout(worker_cond, running_lock, left);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (state == TASK_WORKER_IN_HANDLER)
|
||||
{
|
||||
retro_task_t *task = worker_self->task;
|
||||
bool detachable;
|
||||
|
||||
slock_lock(property_lock);
|
||||
detachable = (task->flags & RETRO_TASK_FLG_DETACHABLE) != 0;
|
||||
slock_unlock(property_lock);
|
||||
|
||||
if (detachable && retro_atomic_cas_int(&worker_self->state,
|
||||
TASK_WORKER_IN_HANDLER, TASK_WORKER_ORPHANED))
|
||||
{
|
||||
retro_task_threaded_orphan_worker(task);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Not in a detachable handler: it is either leaving or can only
|
||||
* leave by returning. Wait for its signal, without a bound. */
|
||||
scond_wait(worker_cond, running_lock);
|
||||
}
|
||||
|
||||
slock_unlock(running_lock);
|
||||
sthread_join(worker_thread);
|
||||
|
||||
scond_free(worker_cond);
|
||||
scond_free(finished_cond);
|
||||
slock_free(running_lock);
|
||||
slock_free(finished_lock);
|
||||
slock_free(property_lock);
|
||||
slock_free(queue_lock);
|
||||
free(worker_self);
|
||||
retro_task_sync_primitives_free();
|
||||
|
||||
worker_thread = NULL;
|
||||
worker_cond = NULL;
|
||||
finished_cond = NULL;
|
||||
running_lock = NULL;
|
||||
finished_lock = NULL;
|
||||
property_lock = NULL;
|
||||
queue_lock = NULL;
|
||||
worker_self = NULL;
|
||||
}
|
||||
|
||||
static struct retro_task_impl impl_threaded = {
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
TARGET := task_queue_detach_test
|
||||
|
||||
LIBRETRO_COMM_DIR := ../../..
|
||||
|
||||
# Built with HAVE_THREADS: what is under test is how deinit stops the
|
||||
# threaded worker when a handler has not returned.
|
||||
SOURCES := \
|
||||
task_queue_detach_test.c \
|
||||
$(LIBRETRO_COMM_DIR)/queues/task_queue.c \
|
||||
$(LIBRETRO_COMM_DIR)/rthreads/rthreads.c \
|
||||
$(LIBRETRO_COMM_DIR)/features/features_cpu.c
|
||||
|
||||
OBJS := $(SOURCES:.c=.o)
|
||||
|
||||
CFLAGS += -Wall -pedantic -std=gnu99 -g -O1 -fno-omit-frame-pointer \
|
||||
-DHAVE_THREADS -I$(LIBRETRO_COMM_DIR)/include
|
||||
|
||||
LIBS += -lpthread
|
||||
|
||||
ifneq ($(SANITIZER),)
|
||||
CFLAGS := -fsanitize=$(SANITIZER) $(CFLAGS)
|
||||
LDFLAGS := -fsanitize=$(SANITIZER) $(LDFLAGS)
|
||||
endif
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
%.o: %.c
|
||||
$(CC) -c -o $@ $< $(CFLAGS)
|
||||
|
||||
$(TARGET): $(OBJS)
|
||||
$(CC) -o $@ $^ $(LDFLAGS) $(LIBS)
|
||||
|
||||
check: $(TARGET)
|
||||
./$(TARGET)
|
||||
|
||||
clean:
|
||||
rm -f $(TARGET) $(OBJS)
|
||||
|
||||
.PHONY: all check clean
|
||||
@@ -0,0 +1,194 @@
|
||||
/* Regression test for how task_queue_deinit() stops the threaded
|
||||
* worker in libretro-common/queues/task_queue.c.
|
||||
*
|
||||
* Deinit used to join the worker with no limit, so a handler blocked
|
||||
* in an OS call - a DirectInput device walk held by a Bluetooth stack
|
||||
* still coming up, say - held quit and every reinit of the queue for
|
||||
* as long as it stayed blocked.
|
||||
*
|
||||
* The contract this pins:
|
||||
*
|
||||
* idle worker -> deinit returns at once
|
||||
* detachable handler stuck -> deinit returns after its bound; the
|
||||
* task is dropped, its callback never
|
||||
* runs, a new queue works meanwhile,
|
||||
* and the handler finishing later
|
||||
* touches nothing of it
|
||||
* other handler past bound -> deinit still waits for it, since
|
||||
* its handler may use what teardown
|
||||
* frees next
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <boolean.h>
|
||||
#include <retro_common_api.h>
|
||||
#include <retro_timers.h>
|
||||
#include <retro_atomic.h>
|
||||
#include <features/features_cpu.h>
|
||||
#include <queues/task_queue.h>
|
||||
|
||||
static unsigned failures = 0;
|
||||
|
||||
static void check(bool cond, const char *what)
|
||||
{
|
||||
if (cond)
|
||||
printf(" [pass] %s\n", what);
|
||||
else
|
||||
{
|
||||
printf(" [FAIL] %s\n", what);
|
||||
failures++;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- a handler that blocks until released ---------------------------- */
|
||||
|
||||
static retro_atomic_int_t blocked_entered = RETRO_ATOMIC_INT_INITIALIZER(0);
|
||||
static retro_atomic_int_t blocked_release = RETRO_ATOMIC_INT_INITIALIZER(0);
|
||||
static retro_atomic_int_t blocked_returned = RETRO_ATOMIC_INT_INITIALIZER(0);
|
||||
static retro_atomic_int_t blocked_callback = RETRO_ATOMIC_INT_INITIALIZER(0);
|
||||
|
||||
static void blocked_handler(retro_task_t *task)
|
||||
{
|
||||
retro_atomic_store_release_int(&blocked_entered, 1);
|
||||
while (!retro_atomic_load_acquire_int(&blocked_release))
|
||||
retro_sleep(1);
|
||||
/* What an orphaned handler is still allowed to do. */
|
||||
task_set_progress(task, 100);
|
||||
task_set_flags(task, RETRO_TASK_FLG_FINISHED, true);
|
||||
retro_atomic_store_release_int(&blocked_returned, 1);
|
||||
}
|
||||
|
||||
static void blocked_cb(retro_task_t *task, void *td, void *ud, const char *e)
|
||||
{
|
||||
retro_atomic_store_release_int(&blocked_callback, 1);
|
||||
}
|
||||
|
||||
/* --- a handler that takes a fixed time, then finishes ---------------- */
|
||||
|
||||
#define SLOW_HANDLER_MS 1500
|
||||
|
||||
static retro_atomic_int_t slow_entered = RETRO_ATOMIC_INT_INITIALIZER(0);
|
||||
static retro_atomic_int_t slow_returned = RETRO_ATOMIC_INT_INITIALIZER(0);
|
||||
|
||||
static void slow_handler(retro_task_t *task)
|
||||
{
|
||||
retro_atomic_store_release_int(&slow_entered, 1);
|
||||
retro_sleep(SLOW_HANDLER_MS);
|
||||
task_set_flags(task, RETRO_TASK_FLG_FINISHED, true);
|
||||
retro_atomic_store_release_int(&slow_returned, 1);
|
||||
}
|
||||
|
||||
/* --- a quick task, to prove a queue works ---------------------------- */
|
||||
|
||||
static retro_atomic_int_t quick_callback = RETRO_ATOMIC_INT_INITIALIZER(0);
|
||||
|
||||
static void quick_handler(retro_task_t *task)
|
||||
{
|
||||
task_set_flags(task, RETRO_TASK_FLG_FINISHED, true);
|
||||
}
|
||||
|
||||
static void quick_cb(retro_task_t *task, void *td, void *ud, const char *e)
|
||||
{
|
||||
retro_atomic_store_release_int(&quick_callback, 1);
|
||||
}
|
||||
|
||||
static void push(retro_task_handler_t h, retro_task_callback_t cb,
|
||||
bool detachable)
|
||||
{
|
||||
retro_task_t *task = task_init();
|
||||
task->handler = h;
|
||||
task->callback = cb;
|
||||
task->flags |= RETRO_TASK_FLG_MUTE;
|
||||
if (detachable)
|
||||
task->flags |= RETRO_TASK_FLG_DETACHABLE;
|
||||
task_queue_push(task);
|
||||
}
|
||||
|
||||
static void wait_for(retro_atomic_int_t *flag)
|
||||
{
|
||||
while (!retro_atomic_load_acquire_int(flag))
|
||||
retro_sleep(1);
|
||||
}
|
||||
|
||||
static int64_t ms_since(retro_time_t start)
|
||||
{
|
||||
return (int64_t)((cpu_features_get_time_usec() - start) / 1000);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
retro_time_t start;
|
||||
int64_t took;
|
||||
int i;
|
||||
|
||||
printf("idle worker\n");
|
||||
task_queue_init(true, NULL);
|
||||
check(task_queue_is_threaded(), "queue is threaded");
|
||||
start = cpu_features_get_time_usec();
|
||||
task_queue_deinit();
|
||||
took = ms_since(start);
|
||||
check(took < 200, "deinit of an idle worker returns at once");
|
||||
|
||||
printf("detachable handler stuck past the bound\n");
|
||||
task_queue_init(true, NULL);
|
||||
push(blocked_handler, blocked_cb, true);
|
||||
wait_for(&blocked_entered);
|
||||
start = cpu_features_get_time_usec();
|
||||
task_queue_deinit();
|
||||
took = ms_since(start);
|
||||
check(took >= 900 && took < 3000, "deinit gives up after its bound");
|
||||
check(!retro_atomic_load_acquire_int(&blocked_returned),
|
||||
"the handler was still blocked when deinit returned");
|
||||
|
||||
/* A new queue while the orphan is still running. */
|
||||
task_queue_init(true, NULL);
|
||||
push(quick_handler, quick_cb, false);
|
||||
for (i = 0; i < 2000 && !retro_atomic_load_acquire_int(&quick_callback); i++)
|
||||
{
|
||||
task_queue_check();
|
||||
retro_sleep(1);
|
||||
}
|
||||
check(retro_atomic_load_acquire_int(&quick_callback),
|
||||
"a new queue runs tasks while the orphan is blocked");
|
||||
|
||||
/* Let the orphan finish while the new queue is live; it may set its
|
||||
* own task's properties and must touch nothing else. */
|
||||
retro_atomic_store_release_int(&blocked_release, 1);
|
||||
wait_for(&blocked_returned);
|
||||
for (i = 0; i < 200; i++)
|
||||
{
|
||||
task_queue_check();
|
||||
retro_sleep(1);
|
||||
}
|
||||
check(!retro_atomic_load_acquire_int(&blocked_callback),
|
||||
"the dropped task's callback never runs");
|
||||
|
||||
start = cpu_features_get_time_usec();
|
||||
task_queue_deinit();
|
||||
check(ms_since(start) < 200, "the new queue still deinits at once");
|
||||
|
||||
printf("non-detachable handler past the bound\n");
|
||||
task_queue_init(true, NULL);
|
||||
push(slow_handler, NULL, false);
|
||||
wait_for(&slow_entered);
|
||||
start = cpu_features_get_time_usec();
|
||||
task_queue_deinit();
|
||||
took = ms_since(start);
|
||||
check(retro_atomic_load_acquire_int(&slow_returned),
|
||||
"deinit waited for a handler that was not detachable");
|
||||
check(took >= SLOW_HANDLER_MS - 200, "and waited past the bound to do it");
|
||||
|
||||
/* Give a leftover orphan, if any, time to be seen by the sanitizers. */
|
||||
retro_sleep(50);
|
||||
|
||||
if (failures)
|
||||
{
|
||||
printf("\n%u failure(s)\n", failures);
|
||||
return 1;
|
||||
}
|
||||
printf("\nall passed\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user