mirror of https://github.com/mpv-player/mpv
client API: add async path; fix deadlock for vo_libmpv users
In commit 065c307e8e
, I broke everything. It seemed like a nice idea,
but it explicitly broke an assumption vo_libmpv were explicitly allowed
to make: that observing properties does not lock the core. The commit
did just that and locked the core for property updates. This made for
example mpv's own OSX backend freeze (it uses vo_libmpv for convenience
to make up for Apple's incredibly broken OpenGL shit).
I don't want to revert that commit just because vo_libmpv's design is
horrible. So instead add an optional asynchronous path, that is only
used if vo_libmpv is in use (best idea ever?).
Interestingly, this isn't so hard. It adds about 90 lines of code, which
are only run on OSX and libmpv users, so I don't have to care about the
crashes and weird behavior this might cause. It even worked on the first
try except for a quickly fixed memory leak. The code path can be tested
anywhere by just turning the uses_vo_libmpv condition into always true.
The atomic is out of laziness. Saves some thinking how to get around the
locking order.
This commit is contained in:
parent
767c35c883
commit
d66eb93e5d
104
player/client.c
104
player/client.c
|
@ -39,6 +39,7 @@
|
||||||
#include "options/m_property.h"
|
#include "options/m_property.h"
|
||||||
#include "options/path.h"
|
#include "options/path.h"
|
||||||
#include "options/parse_configfile.h"
|
#include "options/parse_configfile.h"
|
||||||
|
#include "osdep/atomic.h"
|
||||||
#include "osdep/threads.h"
|
#include "osdep/threads.h"
|
||||||
#include "osdep/timer.h"
|
#include "osdep/timer.h"
|
||||||
#include "osdep/io.h"
|
#include "osdep/io.h"
|
||||||
|
@ -65,6 +66,8 @@ struct mp_client_api {
|
||||||
|
|
||||||
pthread_mutex_t lock;
|
pthread_mutex_t lock;
|
||||||
|
|
||||||
|
atomic_bool uses_vo_libmpv;
|
||||||
|
|
||||||
// -- protected by lock
|
// -- protected by lock
|
||||||
|
|
||||||
struct mpv_handle **clients;
|
struct mpv_handle **clients;
|
||||||
|
@ -81,6 +84,7 @@ struct mp_client_api {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct observe_property {
|
struct observe_property {
|
||||||
|
struct mpv_handle *owner;
|
||||||
char *name;
|
char *name;
|
||||||
int id; // ==mp_get_property_id(name)
|
int id; // ==mp_get_property_id(name)
|
||||||
uint64_t event_mask; // ==mp_get_property_event_mask(name)
|
uint64_t event_mask; // ==mp_get_property_event_mask(name)
|
||||||
|
@ -91,6 +95,12 @@ struct observe_property {
|
||||||
bool dead; // property unobserved while retrieving value
|
bool dead; // property unobserved while retrieving value
|
||||||
bool value_valid;
|
bool value_valid;
|
||||||
union m_option_value value;
|
union m_option_value value;
|
||||||
|
// Only if async. update is used.
|
||||||
|
uint64_t async_change_ts; // logical timestamp incremented on each change
|
||||||
|
uint64_t async_value_ts; // logical timestamp for async_value contents
|
||||||
|
bool async_updating; // if true, updating async_value_ts to change_ts
|
||||||
|
bool async_value_valid;
|
||||||
|
union m_option_value async_value;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct mpv_handle {
|
struct mpv_handle {
|
||||||
|
@ -126,6 +136,7 @@ struct mpv_handle {
|
||||||
int first_event; // events[first_event] is the first readable event
|
int first_event; // events[first_event] is the first readable event
|
||||||
int num_events; // number of readable events
|
int num_events; // number of readable events
|
||||||
int reserved_events; // number of entries reserved for replies
|
int reserved_events; // number of entries reserved for replies
|
||||||
|
size_t async_counter; // pending other async events
|
||||||
bool choked; // recovering from queue overflow
|
bool choked; // recovering from queue overflow
|
||||||
|
|
||||||
struct observe_property **properties;
|
struct observe_property **properties;
|
||||||
|
@ -355,7 +366,7 @@ static void unlock_core(mpv_handle *ctx)
|
||||||
void mpv_wait_async_requests(mpv_handle *ctx)
|
void mpv_wait_async_requests(mpv_handle *ctx)
|
||||||
{
|
{
|
||||||
pthread_mutex_lock(&ctx->lock);
|
pthread_mutex_lock(&ctx->lock);
|
||||||
while (ctx->reserved_events)
|
while (ctx->reserved_events || ctx->async_counter)
|
||||||
wait_wakeup(ctx, INT64_MAX);
|
wait_wakeup(ctx, INT64_MAX);
|
||||||
pthread_mutex_unlock(&ctx->lock);
|
pthread_mutex_unlock(&ctx->lock);
|
||||||
}
|
}
|
||||||
|
@ -1416,8 +1427,13 @@ int mpv_get_property_async(mpv_handle *ctx, uint64_t ud, const char *name,
|
||||||
static void property_free(void *p)
|
static void property_free(void *p)
|
||||||
{
|
{
|
||||||
struct observe_property *prop = p;
|
struct observe_property *prop = p;
|
||||||
if (prop->type)
|
|
||||||
|
assert(!prop->async_updating);
|
||||||
|
|
||||||
|
if (prop->type) {
|
||||||
m_option_free(prop->type, &prop->value);
|
m_option_free(prop->type, &prop->value);
|
||||||
|
m_option_free(prop->type, &prop->async_value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int mpv_observe_property(mpv_handle *ctx, uint64_t userdata,
|
int mpv_observe_property(mpv_handle *ctx, uint64_t userdata,
|
||||||
|
@ -1434,6 +1450,7 @@ int mpv_observe_property(mpv_handle *ctx, uint64_t userdata,
|
||||||
struct observe_property *prop = talloc_ptrtype(ctx, prop);
|
struct observe_property *prop = talloc_ptrtype(ctx, prop);
|
||||||
talloc_set_destructor(prop, property_free);
|
talloc_set_destructor(prop, property_free);
|
||||||
*prop = (struct observe_property){
|
*prop = (struct observe_property){
|
||||||
|
.owner = ctx,
|
||||||
.name = talloc_strdup(prop, name),
|
.name = talloc_strdup(prop, name),
|
||||||
.id = mp_get_property_id(ctx->mpctx, name),
|
.id = mp_get_property_id(ctx->mpctx, name),
|
||||||
.event_mask = mp_get_property_event_mask(name),
|
.event_mask = mp_get_property_event_mask(name),
|
||||||
|
@ -1453,6 +1470,7 @@ static void mark_property_changed(struct mpv_handle *client, int index)
|
||||||
{
|
{
|
||||||
struct observe_property *prop = client->properties[index];
|
struct observe_property *prop = client->properties[index];
|
||||||
prop->changed = true;
|
prop->changed = true;
|
||||||
|
prop->async_change_ts += 1;
|
||||||
client->lowest_changed = MPMIN(client->lowest_changed, index);
|
client->lowest_changed = MPMIN(client->lowest_changed, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1509,13 +1527,18 @@ static void notify_property_events(struct mpv_handle *ctx, uint64_t event_mask)
|
||||||
wakeup_client(ctx);
|
wakeup_client(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool update_prop(struct mpv_handle *ctx, struct observe_property *prop)
|
static void update_prop_async(void *p)
|
||||||
{
|
{
|
||||||
if (!prop->type)
|
struct observe_property *prop = p;
|
||||||
return true;
|
struct mpv_handle *ctx = prop->owner;
|
||||||
|
|
||||||
union m_option_value val = {0};
|
union m_option_value val = {0};
|
||||||
|
bool val_valid = false;
|
||||||
|
uint64_t value_ts;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&ctx->lock);
|
||||||
|
value_ts = prop->async_change_ts;
|
||||||
|
assert(prop->async_updating);
|
||||||
pthread_mutex_unlock(&ctx->lock);
|
pthread_mutex_unlock(&ctx->lock);
|
||||||
|
|
||||||
struct getproperty_request req = {
|
struct getproperty_request req = {
|
||||||
|
@ -1524,11 +1547,71 @@ static bool update_prop(struct mpv_handle *ctx, struct observe_property *prop)
|
||||||
.format = prop->format,
|
.format = prop->format,
|
||||||
.data = &val,
|
.data = &val,
|
||||||
};
|
};
|
||||||
run_locked(ctx, getproperty_fn, &req);
|
getproperty_fn(&req);
|
||||||
|
val_valid = req.status >= 0;
|
||||||
|
|
||||||
pthread_mutex_lock(&ctx->lock);
|
pthread_mutex_lock(&ctx->lock);
|
||||||
|
|
||||||
bool val_valid = req.status >= 0;
|
assert(prop->async_updating);
|
||||||
|
|
||||||
|
// Move to prop->async_value
|
||||||
|
m_option_free(prop->type, &prop->async_value);
|
||||||
|
memcpy(&prop->async_value, &val, prop->type->type->size);
|
||||||
|
prop->async_value_valid = val_valid;
|
||||||
|
|
||||||
|
prop->async_value_ts = value_ts;
|
||||||
|
prop->async_updating = false;
|
||||||
|
|
||||||
|
// Cause it to re-check the property.
|
||||||
|
prop->changed = true;
|
||||||
|
ctx->lowest_changed = 0;
|
||||||
|
|
||||||
|
ctx->async_counter -= 1;
|
||||||
|
wakeup_client(ctx);
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&ctx->lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool update_prop(struct mpv_handle *ctx, struct observe_property *prop)
|
||||||
|
{
|
||||||
|
if (!prop->type)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
union m_option_value val = {0};
|
||||||
|
bool val_valid = false;
|
||||||
|
|
||||||
|
// With vo_libmpv, we can't lock the core for stupid reasons.
|
||||||
|
// Yes, that's FUCKING HORRIBLE. On the other hand, might be useful for
|
||||||
|
// true async. properties in the future.
|
||||||
|
if (atomic_load_explicit(&ctx->clients->uses_vo_libmpv, memory_order_relaxed)) {
|
||||||
|
if (prop->async_change_ts > prop->async_value_ts) {
|
||||||
|
if (!prop->async_updating) {
|
||||||
|
prop->async_updating = true;
|
||||||
|
ctx->async_counter += 1;
|
||||||
|
mp_dispatch_enqueue(ctx->mpctx->dispatch, update_prop_async, prop);
|
||||||
|
}
|
||||||
|
return false; // re-update later when the changed value comes in
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to val
|
||||||
|
memcpy(&val, &prop->async_value, prop->type->type->size);
|
||||||
|
val_valid = prop->async_value_valid;
|
||||||
|
prop->async_value = (union m_option_value){0};
|
||||||
|
prop->async_value_valid = false;
|
||||||
|
} else {
|
||||||
|
pthread_mutex_unlock(&ctx->lock);
|
||||||
|
|
||||||
|
struct getproperty_request req = {
|
||||||
|
.mpctx = ctx->mpctx,
|
||||||
|
.name = prop->name,
|
||||||
|
.format = prop->format,
|
||||||
|
.data = &val,
|
||||||
|
};
|
||||||
|
run_locked(ctx, getproperty_fn, &req);
|
||||||
|
val_valid = req.status >= 0;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&ctx->lock);
|
||||||
|
}
|
||||||
|
|
||||||
bool changed = prop->value_valid != val_valid;
|
bool changed = prop->value_valid != val_valid;
|
||||||
if (prop->value_valid && val_valid)
|
if (prop->value_valid && val_valid)
|
||||||
|
@ -1603,8 +1686,10 @@ static bool gen_property_change_event(struct mpv_handle *ctx, bool *unlocked)
|
||||||
for (int n = ctx->num_properties - 1; n >= 0; n--) {
|
for (int n = ctx->num_properties - 1; n >= 0; n--) {
|
||||||
struct observe_property *prop = ctx->properties[n];
|
struct observe_property *prop = ctx->properties[n];
|
||||||
if (prop->dead) {
|
if (prop->dead) {
|
||||||
MP_TARRAY_REMOVE_AT(ctx->properties, ctx->num_properties, n);
|
if (!prop->async_updating) {
|
||||||
talloc_free(prop);
|
MP_TARRAY_REMOVE_AT(ctx->properties, ctx->num_properties, n);
|
||||||
|
talloc_free(prop);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ctx->property_event_masks |= prop->event_mask;
|
ctx->property_event_masks |= prop->event_mask;
|
||||||
}
|
}
|
||||||
|
@ -1836,6 +1921,7 @@ bool mp_set_main_render_context(struct mp_client_api *client_api,
|
||||||
if (res)
|
if (res)
|
||||||
client_api->render_context = active ? ctx : NULL;
|
client_api->render_context = active ? ctx : NULL;
|
||||||
pthread_mutex_unlock(&client_api->lock);
|
pthread_mutex_unlock(&client_api->lock);
|
||||||
|
atomic_store(&client_api->uses_vo_libmpv, active);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue