Merge branch 'gsignal-emission-locks-cleanups' into 'main'

gsignal: Reduce lock/unlock pairs and ensure we always work on valid signal nodes

See merge request GNOME/glib!2824
This commit is contained in:
Philip Withnall 2023-05-30 13:57:27 +00:00
commit fe458559d8
2 changed files with 490 additions and 102 deletions

View File

@ -2305,7 +2305,6 @@ g_signal_chain_from_overridden_handler (gpointer instance,
} }
SIGNAL_UNLOCK (); SIGNAL_UNLOCK ();
instance_and_params->g_type = 0;
g_value_init_from_instance (instance_and_params, instance); g_value_init_from_instance (instance_and_params, instance);
SIGNAL_LOCK (); SIGNAL_LOCK ();
@ -3172,6 +3171,12 @@ g_signal_has_handler_pending (gpointer instance,
return has_pending; return has_pending;
} }
static void
signal_emitv_unlocked (const GValue *instance_and_params,
guint signal_id,
GQuark detail,
GValue *return_value);
/** /**
* g_signal_emitv: * g_signal_emitv:
* @instance_and_params: (array): argument list for the signal emission. * @instance_and_params: (array): argument list for the signal emission.
@ -3194,6 +3199,17 @@ g_signal_emitv (const GValue *instance_and_params,
guint signal_id, guint signal_id,
GQuark detail, GQuark detail,
GValue *return_value) GValue *return_value)
{
SIGNAL_LOCK ();
signal_emitv_unlocked (instance_and_params, signal_id, detail, return_value);
SIGNAL_UNLOCK ();
}
static void
signal_emitv_unlocked (const GValue *instance_and_params,
guint signal_id,
GQuark detail,
GValue *return_value)
{ {
gpointer instance; gpointer instance;
SignalNode *node; SignalNode *node;
@ -3211,19 +3227,16 @@ g_signal_emitv (const GValue *instance_and_params,
param_values = instance_and_params + 1; param_values = instance_and_params + 1;
#endif #endif
SIGNAL_LOCK ();
node = LOOKUP_SIGNAL_NODE (signal_id); node = LOOKUP_SIGNAL_NODE (signal_id);
if (!node || !g_type_is_a (G_TYPE_FROM_INSTANCE (instance), node->itype)) if (!node || !g_type_is_a (G_TYPE_FROM_INSTANCE (instance), node->itype))
{ {
g_critical ("%s: signal id '%u' is invalid for instance '%p'", G_STRLOC, signal_id, instance); g_critical ("%s: signal id '%u' is invalid for instance '%p'", G_STRLOC, signal_id, instance);
SIGNAL_UNLOCK ();
return; return;
} }
#ifdef G_ENABLE_DEBUG #ifdef G_ENABLE_DEBUG
if (detail && !(node->flags & G_SIGNAL_DETAILED)) if (detail && !(node->flags & G_SIGNAL_DETAILED))
{ {
g_critical ("%s: signal id '%u' does not support detail (%u)", G_STRLOC, signal_id, detail); g_critical ("%s: signal id '%u' does not support detail (%u)", G_STRLOC, signal_id, detail);
SIGNAL_UNLOCK ();
return; return;
} }
for (i = 0; i < node->n_params; i++) for (i = 0; i < node->n_params; i++)
@ -3235,7 +3248,6 @@ g_signal_emitv (const GValue *instance_and_params,
i, i,
node->name, node->name,
G_VALUE_TYPE_NAME (param_values + i)); G_VALUE_TYPE_NAME (param_values + i));
SIGNAL_UNLOCK ();
return; return;
} }
if (node->return_type != G_TYPE_NONE) if (node->return_type != G_TYPE_NONE)
@ -3246,7 +3258,6 @@ g_signal_emitv (const GValue *instance_and_params,
G_STRLOC, G_STRLOC,
type_debug_name (node->return_type), type_debug_name (node->return_type),
node->name); node->name);
SIGNAL_UNLOCK ();
return; return;
} }
else if (!node->accumulator && !G_TYPE_CHECK_VALUE_TYPE (return_value, node->return_type & ~G_SIGNAL_TYPE_STATIC_SCOPE)) else if (!node->accumulator && !G_TYPE_CHECK_VALUE_TYPE (return_value, node->return_type & ~G_SIGNAL_TYPE_STATIC_SCOPE))
@ -3256,7 +3267,6 @@ g_signal_emitv (const GValue *instance_and_params,
type_debug_name (node->return_type), type_debug_name (node->return_type),
node->name, node->name,
G_VALUE_TYPE_NAME (return_value)); G_VALUE_TYPE_NAME (return_value));
SIGNAL_UNLOCK ();
return; return;
} }
} }
@ -3283,14 +3293,15 @@ g_signal_emitv (const GValue *instance_and_params,
if (hlist == NULL || hlist->handlers == NULL) if (hlist == NULL || hlist->handlers == NULL)
{ {
/* nothing to do to emit this signal */ /* nothing to do to emit this signal */
SIGNAL_UNLOCK ();
/* g_printerr ("omitting emission of \"%s\"\n", node->name); */ /* g_printerr ("omitting emission of \"%s\"\n", node->name); */
return; return;
} }
} }
SIGNAL_UNLOCK (); /* Pass a stable node pointer, whose address can't change even if the
signal_emit_unlocked_R (node, detail, instance, return_value, instance_and_params); * g_signal_nodes array gets reallocated. */
SignalNode node_copy = *node;
signal_emit_unlocked_R (&node_copy, detail, instance, return_value, instance_and_params);
} }
static inline gboolean static inline gboolean
@ -3312,6 +3323,12 @@ accumulate (GSignalInvocationHint *ihint,
return continue_emission; return continue_emission;
} }
static gboolean
signal_emit_valist_unlocked (gpointer instance,
guint signal_id,
GQuark detail,
va_list var_args);
/** /**
* g_signal_emit_valist: (skip) * g_signal_emit_valist: (skip)
* @instance: (type GObject.TypeInstance): the instance the signal is being * @instance: (type GObject.TypeInstance): the instance the signal is being
@ -3333,36 +3350,60 @@ g_signal_emit_valist (gpointer instance,
guint signal_id, guint signal_id,
GQuark detail, GQuark detail,
va_list var_args) va_list var_args)
{
SIGNAL_LOCK ();
if (signal_emit_valist_unlocked (instance, signal_id, detail, var_args))
SIGNAL_UNLOCK ();
}
/*<private>
* signal_emit_valist_unlocked:
* @instance: The instance to emit from
* @signal_id: Signal id to emit
* @detail: Signal detail
* @var_args: Call arguments
*
* Returns: %TRUE if the signal mutex has been left locked
*/
static gboolean
signal_emit_valist_unlocked (gpointer instance,
guint signal_id,
GQuark detail,
va_list var_args)
{ {
GValue *instance_and_params; GValue *instance_and_params;
GType signal_return_type;
GValue *param_values; GValue *param_values;
SignalNode *node; SignalNode *node;
guint i, n_params; guint i;
g_return_if_fail (G_TYPE_CHECK_INSTANCE (instance)); g_return_val_if_fail (G_TYPE_CHECK_INSTANCE (instance), TRUE);
g_return_if_fail (signal_id > 0); g_return_val_if_fail (signal_id > 0, TRUE);
SIGNAL_LOCK ();
node = LOOKUP_SIGNAL_NODE (signal_id); node = LOOKUP_SIGNAL_NODE (signal_id);
if (!node || !g_type_is_a (G_TYPE_FROM_INSTANCE (instance), node->itype)) if (!node || !g_type_is_a (G_TYPE_FROM_INSTANCE (instance), node->itype))
{ {
g_critical ("%s: signal id '%u' is invalid for instance '%p'", G_STRLOC, signal_id, instance); g_critical ("%s: signal id '%u' is invalid for instance '%p'", G_STRLOC, signal_id, instance);
SIGNAL_UNLOCK (); return TRUE;
return;
} }
#ifndef G_DISABLE_CHECKS #ifndef G_DISABLE_CHECKS
if (detail && !(node->flags & G_SIGNAL_DETAILED)) if (detail && !(node->flags & G_SIGNAL_DETAILED))
{ {
g_critical ("%s: signal id '%u' does not support detail (%u)", G_STRLOC, signal_id, detail); g_critical ("%s: signal id '%u' does not support detail (%u)", G_STRLOC, signal_id, detail);
SIGNAL_UNLOCK (); return TRUE;
return;
} }
#endif /* !G_DISABLE_CHECKS */ #endif /* !G_DISABLE_CHECKS */
if (!node->single_va_closure_is_valid) if (!node->single_va_closure_is_valid)
node_update_single_va_closure (node); node_update_single_va_closure (node);
/* There's no need to deep copy this, because a SignalNode instance won't
* ever be destroyed, given that _g_signals_destroy() is not called in any
* real program, however the SignalNode pointer could change, so just store
* the struct contents references, so that we won't try to deference a
* potentially invalid (or changed) pointer;
*/
SignalNode node_copy = *node;
if (node->single_va_closure != NULL) if (node->single_va_closure != NULL)
{ {
HandlerList* hlist; HandlerList* hlist;
@ -3415,32 +3456,26 @@ g_signal_emit_valist (gpointer instance,
} }
} }
if (fastpath && closure == NULL && node->return_type == G_TYPE_NONE) if (fastpath && closure == NULL && node_copy.return_type == G_TYPE_NONE)
{ return TRUE;
SIGNAL_UNLOCK ();
return;
}
/* Don't allow no-recurse emission as we might have to restart, which means /* Don't allow no-recurse emission as we might have to restart, which means
we will run multiple handlers and thus must ref all arguments */ we will run multiple handlers and thus must ref all arguments */
if (closure != NULL && (node->flags & (G_SIGNAL_NO_RECURSE)) != 0) if (closure != NULL && (node_copy.flags & (G_SIGNAL_NO_RECURSE)) != 0)
fastpath = FALSE; fastpath = FALSE;
if (fastpath) if (fastpath)
{ {
SignalAccumulator *accumulator;
Emission emission; Emission emission;
GValue *return_accu, accu = G_VALUE_INIT; GValue *return_accu, accu = G_VALUE_INIT;
GType instance_type = G_TYPE_FROM_INSTANCE (instance); GType instance_type = G_TYPE_FROM_INSTANCE (instance);
GValue emission_return = G_VALUE_INIT; GValue emission_return = G_VALUE_INIT;
GType rtype = node->return_type & ~G_SIGNAL_TYPE_STATIC_SCOPE; GType rtype = node_copy.return_type & ~G_SIGNAL_TYPE_STATIC_SCOPE;
gboolean static_scope = node->return_type & G_SIGNAL_TYPE_STATIC_SCOPE; gboolean static_scope = node_copy.return_type & G_SIGNAL_TYPE_STATIC_SCOPE;
signal_id = node->signal_id;
accumulator = node->accumulator;
if (rtype == G_TYPE_NONE) if (rtype == G_TYPE_NONE)
return_accu = NULL; return_accu = NULL;
else if (accumulator) else if (node_copy.accumulator)
return_accu = &accu; return_accu = &accu;
else else
return_accu = &emission_return; return_accu = &emission_return;
@ -3456,18 +3491,18 @@ g_signal_emit_valist (gpointer instance,
if (fastpath_handler) if (fastpath_handler)
handler_ref (fastpath_handler); handler_ref (fastpath_handler);
SIGNAL_UNLOCK (); if (closure != NULL)
{
TRACE(GOBJECT_SIGNAL_EMIT(signal_id, detail, instance, instance_type)); TRACE(GOBJECT_SIGNAL_EMIT(signal_id, detail, instance, instance_type));
SIGNAL_UNLOCK ();
if (rtype != G_TYPE_NONE) if (rtype != G_TYPE_NONE)
g_value_init (&emission_return, rtype); g_value_init (&emission_return, rtype);
if (accumulator) if (node_copy.accumulator)
g_value_init (&accu, rtype); g_value_init (&accu, rtype);
if (closure != NULL)
{
/* /*
* Coverity doesnt understand the paired ref/unref here and seems * Coverity doesnt understand the paired ref/unref here and seems
* to ignore the ref, thus reports every call to g_signal_emit() * to ignore the ref, thus reports every call to g_signal_emit()
@ -3482,12 +3517,15 @@ g_signal_emit_valist (gpointer instance,
return_accu, return_accu,
instance, instance,
var_args, var_args,
node->n_params, node_copy.n_params,
node->param_types); node_copy.param_types);
accumulate (&emission.ihint, &emission_return, &accu, accumulator); accumulate (&emission.ihint, &emission_return, &accu, node_copy.accumulator);
}
SIGNAL_LOCK (); if (node_copy.accumulator)
g_value_unset (&accu);
SIGNAL_LOCK ();
}
emission.chain_type = G_TYPE_NONE; emission.chain_type = G_TYPE_NONE;
emission_pop (&emission); emission_pop (&emission);
@ -3495,20 +3533,20 @@ g_signal_emit_valist (gpointer instance,
if (fastpath_handler) if (fastpath_handler)
handler_unref_R (signal_id, instance, fastpath_handler); handler_unref_R (signal_id, instance, fastpath_handler);
SIGNAL_UNLOCK (); SIGNAL_UNLOCK ();
if (accumulator)
g_value_unset (&accu);
if (rtype != G_TYPE_NONE) if (rtype != G_TYPE_NONE)
{ {
gchar *error = NULL; gchar *error = NULL;
for (i = 0; i < node->n_params; i++) for (i = 0; i < node_copy.n_params; i++)
{ {
GType ptype = node->param_types[i] & ~G_SIGNAL_TYPE_STATIC_SCOPE; GType ptype = node_copy.param_types[i] & ~G_SIGNAL_TYPE_STATIC_SCOPE;
G_VALUE_COLLECT_SKIP (ptype, var_args); G_VALUE_COLLECT_SKIP (ptype, var_args);
} }
if (closure == NULL)
g_value_init (&emission_return, rtype);
G_VALUE_LCOPY (&emission_return, G_VALUE_LCOPY (&emission_return,
var_args, var_args,
static_scope ? G_VALUE_NOCOPY_CONTENTS : 0, static_scope ? G_VALUE_NOCOPY_CONTENTS : 0,
@ -3533,21 +3571,20 @@ g_signal_emit_valist (gpointer instance,
g_object_unref (instance); g_object_unref (instance);
#endif #endif
return; return FALSE;
} }
} }
SIGNAL_UNLOCK (); SIGNAL_UNLOCK ();
n_params = node->n_params; instance_and_params = g_newa0 (GValue, node_copy.n_params + 1);
signal_return_type = node->return_type;
instance_and_params = g_newa0 (GValue, n_params + 1);
param_values = instance_and_params + 1; param_values = instance_and_params + 1;
for (i = 0; i < node->n_params; i++) for (i = 0; i < node_copy.n_params; i++)
{ {
gchar *error; gchar *error;
GType ptype = node->param_types[i] & ~G_SIGNAL_TYPE_STATIC_SCOPE; GType ptype = node_copy.param_types[i] & ~G_SIGNAL_TYPE_STATIC_SCOPE;
gboolean static_scope = node->param_types[i] & G_SIGNAL_TYPE_STATIC_SCOPE; gboolean static_scope = node_copy.param_types[i] & G_SIGNAL_TYPE_STATIC_SCOPE;
G_VALUE_COLLECT_INIT (param_values + i, ptype, G_VALUE_COLLECT_INIT (param_values + i, ptype,
var_args, var_args,
@ -3564,24 +3601,29 @@ g_signal_emit_valist (gpointer instance,
while (i--) while (i--)
g_value_unset (param_values + i); g_value_unset (param_values + i);
return; return FALSE;
} }
} }
instance_and_params->g_type = 0;
g_value_init_from_instance (instance_and_params, instance); g_value_init_from_instance (instance_and_params, instance);
if (signal_return_type == G_TYPE_NONE) if (node_copy.return_type == G_TYPE_NONE)
signal_emit_unlocked_R (node, detail, instance, NULL, instance_and_params); {
SIGNAL_LOCK ();
signal_emit_unlocked_R (&node_copy, detail, instance, NULL, instance_and_params);
SIGNAL_UNLOCK ();
}
else else
{ {
GValue return_value = G_VALUE_INIT; GValue return_value = G_VALUE_INIT;
gchar *error = NULL; gchar *error = NULL;
GType rtype = signal_return_type & ~G_SIGNAL_TYPE_STATIC_SCOPE; GType rtype = node_copy.return_type & ~G_SIGNAL_TYPE_STATIC_SCOPE;
gboolean static_scope = signal_return_type & G_SIGNAL_TYPE_STATIC_SCOPE; gboolean static_scope = node_copy.return_type & G_SIGNAL_TYPE_STATIC_SCOPE;
g_value_init (&return_value, rtype); g_value_init (&return_value, rtype);
signal_emit_unlocked_R (node, detail, instance, &return_value, instance_and_params); SIGNAL_LOCK ();
signal_emit_unlocked_R (&node_copy, detail, instance, &return_value, instance_and_params);
SIGNAL_UNLOCK ();
G_VALUE_LCOPY (&return_value, G_VALUE_LCOPY (&return_value,
var_args, var_args,
@ -3599,9 +3641,11 @@ g_signal_emit_valist (gpointer instance,
*/ */
} }
} }
for (i = 0; i < n_params; i++) for (i = 0; i < node_copy.n_params; i++)
g_value_unset (param_values + i); g_value_unset (param_values + i);
g_value_unset (instance_and_params); g_value_unset (instance_and_params);
return FALSE;
} }
/** /**
@ -3663,19 +3707,41 @@ g_signal_emit_by_name (gpointer instance,
SIGNAL_LOCK (); SIGNAL_LOCK ();
signal_id = signal_parse_name (detailed_signal, itype, &detail, TRUE); signal_id = signal_parse_name (detailed_signal, itype, &detail, TRUE);
SIGNAL_UNLOCK ();
if (signal_id) if (signal_id)
{ {
va_list var_args; va_list var_args;
va_start (var_args, detailed_signal); va_start (var_args, detailed_signal);
g_signal_emit_valist (instance, signal_id, detail, var_args); if (signal_emit_valist_unlocked (instance, signal_id, detail, var_args))
SIGNAL_UNLOCK ();
va_end (var_args); va_end (var_args);
} }
else else
g_critical ("%s: signal name '%s' is invalid for instance '%p' of type '%s'", {
G_STRLOC, detailed_signal, instance, g_type_name (itype)); SIGNAL_UNLOCK ();
g_critical ("%s: signal name '%s' is invalid for instance '%p' of type '%s'",
G_STRLOC, detailed_signal, instance, g_type_name (itype));
}
}
G_ALWAYS_INLINE static inline GValue *
maybe_init_accumulator_unlocked (SignalNode *node,
GValue *emission_return,
GValue *accumulator_value)
{
if (node->accumulator)
{
if (accumulator_value->g_type)
return accumulator_value;
g_value_init (accumulator_value,
node->return_type & ~G_SIGNAL_TYPE_STATIC_SCOPE);
return accumulator_value;
}
return emission_return;
} }
static gboolean static gboolean
@ -3694,11 +3760,16 @@ signal_emit_unlocked_R (SignalNode *node,
guint signal_id; guint signal_id;
gulong max_sequential_handler_number; gulong max_sequential_handler_number;
gboolean return_value_altered = FALSE; gboolean return_value_altered = FALSE;
guint n_params;
TRACE(GOBJECT_SIGNAL_EMIT(node->signal_id, detail, instance, G_TYPE_FROM_INSTANCE (instance))); TRACE(GOBJECT_SIGNAL_EMIT(node->signal_id, detail, instance, G_TYPE_FROM_INSTANCE (instance)));
SIGNAL_LOCK (); /* We expect this function to be called with a stable SignalNode pointer
* that cannot change location, so accessing its stable members should
* always work even after a lock/unlock.
*/
signal_id = node->signal_id; signal_id = node->signal_id;
n_params = node->n_params + 1;
if (node->flags & G_SIGNAL_NO_RECURSE) if (node->flags & G_SIGNAL_NO_RECURSE)
{ {
@ -3707,20 +3778,10 @@ signal_emit_unlocked_R (SignalNode *node,
if (emission_node) if (emission_node)
{ {
emission_node->state = EMISSION_RESTART; emission_node->state = EMISSION_RESTART;
SIGNAL_UNLOCK ();
return return_value_altered; return return_value_altered;
} }
} }
accumulator = node->accumulator; accumulator = node->accumulator;
if (accumulator)
{
SIGNAL_UNLOCK ();
g_value_init (&accu, node->return_type & ~G_SIGNAL_TYPE_STATIC_SCOPE);
return_accu = &accu;
SIGNAL_LOCK ();
}
else
return_accu = emission_return;
emission.instance = instance; emission.instance = instance;
emission.ihint.signal_id = node->signal_id; emission.ihint.signal_id = node->signal_id;
emission.ihint.detail = detail; emission.ihint.detail = detail;
@ -3748,9 +3809,10 @@ signal_emit_unlocked_R (SignalNode *node,
emission.chain_type = G_TYPE_FROM_INSTANCE (instance); emission.chain_type = G_TYPE_FROM_INSTANCE (instance);
SIGNAL_UNLOCK (); SIGNAL_UNLOCK ();
return_accu = maybe_init_accumulator_unlocked (node, emission_return, &accu);
g_closure_invoke (class_closure, g_closure_invoke (class_closure,
return_accu, return_accu,
node->n_params + 1, n_params,
instance_and_params, instance_and_params,
&emission.ihint); &emission.ihint);
if (!accumulate (&emission.ihint, emission_return, &accu, accumulator) && if (!accumulate (&emission.ihint, emission_return, &accu, accumulator) &&
@ -3765,35 +3827,131 @@ signal_emit_unlocked_R (SignalNode *node,
else if (emission.state == EMISSION_RESTART) else if (emission.state == EMISSION_RESTART)
goto EMIT_RESTART; goto EMIT_RESTART;
} }
if (node->emission_hooks) if (node->emission_hooks)
{ {
gboolean need_destroy, was_in_call, may_recurse = TRUE;
GHook *hook; GHook *hook;
GHook *static_emission_hooks[3];
size_t n_emission_hooks = 0;
const gboolean may_recurse = TRUE;
guint i;
emission.state = EMISSION_HOOK; emission.state = EMISSION_HOOK;
/* Quick check to determine whether any hooks match this emission,
* before committing to the more complex work of calling those hooks.
* We save a few of them into a static array, to try to avoid further
* allocations.
*/
hook = g_hook_first_valid (node->emission_hooks, may_recurse); hook = g_hook_first_valid (node->emission_hooks, may_recurse);
while (hook) while (hook)
{ {
SignalHook *signal_hook = SIGNAL_HOOK (hook); SignalHook *signal_hook = SIGNAL_HOOK (hook);
if (!signal_hook->detail || signal_hook->detail == detail) if (!signal_hook->detail || signal_hook->detail == detail)
{ {
GSignalEmissionHook hook_func = (GSignalEmissionHook) hook->func; if (n_emission_hooks < G_N_ELEMENTS (static_emission_hooks))
{
was_in_call = G_HOOK_IN_CALL (hook); static_emission_hooks[n_emission_hooks] =
hook->flags |= G_HOOK_FLAG_IN_CALL; g_hook_ref (node->emission_hooks, hook);
SIGNAL_UNLOCK (); }
need_destroy = !hook_func (&emission.ihint, node->n_params + 1, instance_and_params, hook->data);
SIGNAL_LOCK (); n_emission_hooks += 1;
if (!was_in_call) }
hook->flags &= ~G_HOOK_FLAG_IN_CALL;
if (need_destroy)
g_hook_destroy_link (node->emission_hooks, hook);
}
hook = g_hook_next_valid (node->emission_hooks, hook, may_recurse); hook = g_hook_next_valid (node->emission_hooks, hook, may_recurse);
} }
/* Re-iterate back through the matching hooks and copy them into
* an array which wont change when we unlock to call the
* user-provided hook functions.
* These functions may change hook configuration for this signal,
* add / remove signal handlers, etc.
*/
if G_UNLIKELY (n_emission_hooks > 0)
{
guint8 static_hook_returns[G_N_ELEMENTS (static_emission_hooks)];
GHook **emission_hooks = NULL;
guint8 *hook_returns = NULL;
if G_LIKELY (n_emission_hooks <= G_N_ELEMENTS (static_emission_hooks))
{
emission_hooks = static_emission_hooks;
hook_returns = static_hook_returns;
}
else
{
emission_hooks = g_newa (GHook *, n_emission_hooks);
hook_returns = g_newa (guint8, n_emission_hooks);
/* We can't just memcpy the ones we have in the static array,
* to the alloca()'d one because otherwise we'd get an invalid
* ID assertion during unref
*/
i = 0;
for (hook = g_hook_first_valid (node->emission_hooks, may_recurse);
hook != NULL;
hook = g_hook_next_valid (node->emission_hooks, hook, may_recurse))
{
SignalHook *signal_hook = SIGNAL_HOOK (hook);
if (!signal_hook->detail || signal_hook->detail == detail)
{
if (i < G_N_ELEMENTS (static_emission_hooks))
{
emission_hooks[i] = g_steal_pointer (&static_emission_hooks[i]);
g_assert (emission_hooks[i] == hook);
}
else
{
emission_hooks[i] = g_hook_ref (node->emission_hooks, hook);
}
i += 1;
}
}
g_assert (i == n_emission_hooks);
}
SIGNAL_UNLOCK ();
for (i = 0; i < n_emission_hooks; ++i)
{
GSignalEmissionHook hook_func;
gboolean need_destroy;
guint old_flags;
hook = emission_hooks[i];
hook_func = (GSignalEmissionHook) hook->func;
old_flags = g_atomic_int_or (&hook->flags, G_HOOK_FLAG_IN_CALL);
need_destroy = !hook_func (&emission.ihint, n_params,
instance_and_params, hook->data);
if (!(old_flags & G_HOOK_FLAG_IN_CALL))
{
g_atomic_int_compare_and_exchange (&hook->flags,
old_flags | G_HOOK_FLAG_IN_CALL,
old_flags);
}
hook_returns[i] = !!need_destroy;
}
SIGNAL_LOCK ();
for (i = 0; i < n_emission_hooks; i++)
{
hook = emission_hooks[i];
g_hook_unref (node->emission_hooks, hook);
if (hook_returns[i])
g_hook_destroy_link (node->emission_hooks, hook);
}
}
if (emission.state == EMISSION_RESTART) if (emission.state == EMISSION_RESTART)
goto EMIT_RESTART; goto EMIT_RESTART;
} }
@ -3818,9 +3976,10 @@ signal_emit_unlocked_R (SignalNode *node,
handler->sequential_number < max_sequential_handler_number) handler->sequential_number < max_sequential_handler_number)
{ {
SIGNAL_UNLOCK (); SIGNAL_UNLOCK ();
return_accu = maybe_init_accumulator_unlocked (node, emission_return, &accu);
g_closure_invoke (handler->closure, g_closure_invoke (handler->closure,
return_accu, return_accu,
node->n_params + 1, n_params,
instance_and_params, instance_and_params,
&emission.ihint); &emission.ihint);
if (!accumulate (&emission.ihint, emission_return, &accu, accumulator) && if (!accumulate (&emission.ihint, emission_return, &accu, accumulator) &&
@ -3857,9 +4016,10 @@ signal_emit_unlocked_R (SignalNode *node,
emission.chain_type = G_TYPE_FROM_INSTANCE (instance); emission.chain_type = G_TYPE_FROM_INSTANCE (instance);
SIGNAL_UNLOCK (); SIGNAL_UNLOCK ();
return_accu = maybe_init_accumulator_unlocked (node, emission_return, &accu);
g_closure_invoke (class_closure, g_closure_invoke (class_closure,
return_accu, return_accu,
node->n_params + 1, n_params,
instance_and_params, instance_and_params,
&emission.ihint); &emission.ihint);
if (!accumulate (&emission.ihint, emission_return, &accu, accumulator) && if (!accumulate (&emission.ihint, emission_return, &accu, accumulator) &&
@ -3889,9 +4049,10 @@ signal_emit_unlocked_R (SignalNode *node,
handler->sequential_number < max_sequential_handler_number) handler->sequential_number < max_sequential_handler_number)
{ {
SIGNAL_UNLOCK (); SIGNAL_UNLOCK ();
return_accu = maybe_init_accumulator_unlocked (node, emission_return, &accu);
g_closure_invoke (handler->closure, g_closure_invoke (handler->closure,
return_accu, return_accu,
node->n_params + 1, n_params,
instance_and_params, instance_and_params,
&emission.ihint); &emission.ihint);
if (!accumulate (&emission.ihint, emission_return, &accu, accumulator) && if (!accumulate (&emission.ihint, emission_return, &accu, accumulator) &&
@ -3938,7 +4099,7 @@ signal_emit_unlocked_R (SignalNode *node,
} }
g_closure_invoke (class_closure, g_closure_invoke (class_closure,
node->return_type != G_TYPE_NONE ? &accu : NULL, node->return_type != G_TYPE_NONE ? &accu : NULL,
node->n_params + 1, n_params,
instance_and_params, instance_and_params,
&emission.ihint); &emission.ihint);
if (!accumulate (&emission.ihint, emission_return, &accu, accumulator) && if (!accumulate (&emission.ihint, emission_return, &accu, accumulator) &&
@ -3959,7 +4120,6 @@ signal_emit_unlocked_R (SignalNode *node,
handler_unref_R (signal_id, instance, handler_list); handler_unref_R (signal_id, instance, handler_list);
emission_pop (&emission); emission_pop (&emission);
SIGNAL_UNLOCK ();
if (accumulator) if (accumulator)
g_value_unset (&accu); g_value_unset (&accu);

View File

@ -1130,12 +1130,35 @@ hook_func (GSignalInvocationHint *ihint,
return TRUE; return TRUE;
} }
static gboolean
hook_func_removal (GSignalInvocationHint *ihint,
guint n_params,
const GValue *params,
gpointer data)
{
gint *count = data;
(*count)++;
return FALSE;
}
static void
simple_handler_remove_hook (GObject *sender,
gpointer data)
{
gulong *hook = data;
g_signal_remove_emission_hook (simple_id, *hook);
}
static void static void
test_emission_hook (void) test_emission_hook (void)
{ {
GObject *test1, *test2; GObject *test1, *test2;
gint count = 0; gint count = 0;
gulong hook; gulong hook;
gulong connection_id;
test1 = g_object_new (test_get_type (), NULL); test1 = g_object_new (test_get_type (), NULL);
test2 = g_object_new (test_get_type (), NULL); test2 = g_object_new (test_get_type (), NULL);
@ -1150,6 +1173,73 @@ test_emission_hook (void)
g_signal_emit_by_name (test1, "simple"); g_signal_emit_by_name (test1, "simple");
g_assert_cmpint (count, ==, 2); g_assert_cmpint (count, ==, 2);
count = 0;
hook = g_signal_add_emission_hook (simple_id, 0, hook_func_removal, &count, NULL);
g_assert_cmpint (count, ==, 0);
g_signal_emit_by_name (test1, "simple");
g_assert_cmpint (count, ==, 1);
g_signal_emit_by_name (test2, "simple");
g_assert_cmpint (count, ==, 1);
g_test_expect_message ("GLib-GObject", G_LOG_LEVEL_CRITICAL,
"*simple* had no hook * to remove");
g_signal_remove_emission_hook (simple_id, hook);
g_test_assert_expected_messages ();
count = 0;
hook = g_signal_add_emission_hook (simple_id, 0, hook_func, &count, NULL);
connection_id = g_signal_connect (test1, "simple",
G_CALLBACK (simple_handler_remove_hook), &hook);
g_assert_cmpint (count, ==, 0);
g_signal_emit_by_name (test1, "simple");
g_assert_cmpint (count, ==, 1);
g_signal_emit_by_name (test2, "simple");
g_assert_cmpint (count, ==, 1);
g_test_expect_message ("GLib-GObject", G_LOG_LEVEL_CRITICAL,
"*simple* had no hook * to remove");
g_signal_remove_emission_hook (simple_id, hook);
g_test_assert_expected_messages ();
g_clear_signal_handler (&connection_id, test1);
gulong hooks[10];
count = 0;
for (size_t i = 0; i < G_N_ELEMENTS (hooks); ++i)
hooks[i] = g_signal_add_emission_hook (simple_id, 0, hook_func, &count, NULL);
g_assert_cmpint (count, ==, 0);
g_signal_emit_by_name (test1, "simple");
g_assert_cmpint (count, ==, 10);
g_signal_emit_by_name (test2, "simple");
g_assert_cmpint (count, ==, 20);
for (size_t i = 0; i < G_N_ELEMENTS (hooks); ++i)
g_signal_remove_emission_hook (simple_id, hooks[i]);
g_signal_emit_by_name (test1, "simple");
g_assert_cmpint (count, ==, 20);
count = 0;
for (size_t i = 0; i < G_N_ELEMENTS (hooks); ++i)
hooks[i] = g_signal_add_emission_hook (simple_id, 0, hook_func_removal, &count, NULL);
g_assert_cmpint (count, ==, 0);
g_signal_emit_by_name (test1, "simple");
g_assert_cmpint (count, ==, 10);
g_signal_emit_by_name (test2, "simple");
g_assert_cmpint (count, ==, 10);
for (size_t i = 0; i < G_N_ELEMENTS (hooks); ++i)
{
g_test_expect_message ("GLib-GObject", G_LOG_LEVEL_CRITICAL,
"*simple* had no hook * to remove");
g_signal_remove_emission_hook (simple_id, hooks[i]);
g_test_assert_expected_messages ();
}
g_object_unref (test1); g_object_unref (test1);
g_object_unref (test2); g_object_unref (test2);
} }
@ -1818,6 +1908,143 @@ test_signal_is_valid_name (void)
g_assert_false (g_signal_is_valid_name (invalid_names[i])); g_assert_false (g_signal_is_valid_name (invalid_names[i]));
} }
static void
test_emitv (void)
{
GArray *values;
GObject *test;
GValue return_value = G_VALUE_INIT;
gint count = 0;
guint signal_id;
gulong hook;
gulong id;
test = g_object_new (test_get_type (), NULL);
values = g_array_new (TRUE, TRUE, sizeof (GValue));
g_array_set_clear_func (values, (GDestroyNotify) g_value_unset);
g_array_set_size (values, 1);
g_value_init (&g_array_index (values, GValue, 0), G_TYPE_OBJECT);
g_value_set_object (&g_array_index (values, GValue, 0), test);
hook = g_signal_add_emission_hook (simple_id, 0, hook_func, &count, NULL);
g_assert_cmpint (count, ==, 0);
g_signal_emitv ((GValue *) values->data, simple_id, 0, NULL);
g_assert_cmpint (count, ==, 1);
g_signal_remove_emission_hook (simple_id, hook);
g_array_set_size (values, 20);
g_value_init (&g_array_index (values, GValue, 1), G_TYPE_INT);
g_value_set_int (&g_array_index (values, GValue, 1), 42);
g_value_init (&g_array_index (values, GValue, 2), G_TYPE_BOOLEAN);
g_value_set_boolean (&g_array_index (values, GValue, 2), TRUE);
g_value_init (&g_array_index (values, GValue, 3), G_TYPE_CHAR);
g_value_set_schar (&g_array_index (values, GValue, 3), 17);
g_value_init (&g_array_index (values, GValue, 4), G_TYPE_UCHAR);
g_value_set_uchar (&g_array_index (values, GValue, 4), 140);
g_value_init (&g_array_index (values, GValue, 5), G_TYPE_UINT);
g_value_set_uint (&g_array_index (values, GValue, 5), G_MAXUINT - 42);
g_value_init (&g_array_index (values, GValue, 6), G_TYPE_LONG);
g_value_set_long (&g_array_index (values, GValue, 6), -1117);
g_value_init (&g_array_index (values, GValue, 7), G_TYPE_ULONG);
g_value_set_ulong (&g_array_index (values, GValue, 7), G_MAXULONG - 999);
g_value_init (&g_array_index (values, GValue, 8), enum_type);
g_value_set_enum (&g_array_index (values, GValue, 8), MY_ENUM_VALUE);
g_value_init (&g_array_index (values, GValue, 9), flags_type);
g_value_set_flags (&g_array_index (values, GValue, 9),
MY_FLAGS_FIRST_BIT | MY_FLAGS_THIRD_BIT | MY_FLAGS_LAST_BIT);
g_value_init (&g_array_index (values, GValue, 10), G_TYPE_FLOAT);
g_value_set_float (&g_array_index (values, GValue, 10), 0.25);
g_value_init (&g_array_index (values, GValue, 11), G_TYPE_DOUBLE);
g_value_set_double (&g_array_index (values, GValue, 11), 1.5);
g_value_init (&g_array_index (values, GValue, 12), G_TYPE_STRING);
g_value_set_string (&g_array_index (values, GValue, 12), "Test");
g_value_init (&g_array_index (values, GValue, 13), G_TYPE_PARAM_LONG);
g_value_take_param (&g_array_index (values, GValue, 13),
g_param_spec_long ("param", "nick", "blurb", 0, 10, 4, 0));
g_value_init (&g_array_index (values, GValue, 14), G_TYPE_BYTES);
g_value_take_boxed (&g_array_index (values, GValue, 14),
g_bytes_new_static ("Blah", 5));
g_value_init (&g_array_index (values, GValue, 15), G_TYPE_POINTER);
g_value_set_pointer (&g_array_index (values, GValue, 15), &enum_type);
g_value_init (&g_array_index (values, GValue, 16), test_get_type ());
g_value_set_object (&g_array_index (values, GValue, 16), test);
g_value_init (&g_array_index (values, GValue, 17), G_TYPE_VARIANT);
g_value_take_variant (&g_array_index (values, GValue, 17),
g_variant_ref_sink (g_variant_new_uint16 (99)));
g_value_init (&g_array_index (values, GValue, 18), G_TYPE_INT64);
g_value_set_int64 (&g_array_index (values, GValue, 18), G_MAXINT64 - 1234);
g_value_init (&g_array_index (values, GValue, 19), G_TYPE_UINT64);
g_value_set_uint64 (&g_array_index (values, GValue, 19), G_MAXUINT64 - 123456);
id = g_signal_connect (test, "all-types", G_CALLBACK (all_types_handler_cb), &flags_type);
signal_id = g_signal_lookup ("all-types", test_get_type ());
g_assert_cmpuint (signal_id, >, 0);
count = 0;
hook = g_signal_add_emission_hook (signal_id, 0, hook_func, &count, NULL);
g_assert_cmpint (count, ==, 0);
g_signal_emitv ((GValue *) values->data, signal_id, 0, NULL);
g_assert_cmpint (count, ==, 1);
g_signal_remove_emission_hook (signal_id, hook);
g_clear_signal_handler (&id, test);
signal_id = g_signal_lookup ("generic-marshaller-int-return", test_get_type ());
g_assert_cmpuint (signal_id, >, 0);
g_array_set_size (values, 1);
id = g_signal_connect (test,
"generic-marshaller-int-return",
G_CALLBACK (on_generic_marshaller_int_return_signed_1),
NULL);
count = 0;
hook = g_signal_add_emission_hook (signal_id, 0, hook_func, &count, NULL);
g_assert_cmpint (count, ==, 0);
g_value_init (&return_value, G_TYPE_INT);
g_signal_emitv ((GValue *) values->data, signal_id, 0, &return_value);
g_assert_cmpint (count, ==, 1);
g_assert_cmpint (g_value_get_int (&return_value), ==, -30);
g_signal_remove_emission_hook (signal_id, hook);
g_clear_signal_handler (&id, test);
#ifdef G_ENABLE_DEBUG
g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
"*return*value*generic-marshaller-int-return*NULL*");
g_signal_emitv ((GValue *) values->data, signal_id, 0, NULL);
g_test_assert_expected_messages ();
g_value_unset (&return_value);
g_value_init (&return_value, G_TYPE_FLOAT);
g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
"*return*value*generic-marshaller-int-return*gfloat*");
g_signal_emitv ((GValue *) values->data, signal_id, 0, &return_value);
g_test_assert_expected_messages ();
#endif
g_object_unref (test);
g_array_unref (values);
}
/* --- */ /* --- */
int int
@ -1839,6 +2066,7 @@ main (int argc,
g_test_add_func ("/gobject/signals/custom-marshaller", test_custom_marshaller); g_test_add_func ("/gobject/signals/custom-marshaller", test_custom_marshaller);
g_test_add_func ("/gobject/signals/connect", test_connect); g_test_add_func ("/gobject/signals/connect", test_connect);
g_test_add_func ("/gobject/signals/emission-hook", test_emission_hook); g_test_add_func ("/gobject/signals/emission-hook", test_emission_hook);
g_test_add_func ("/gobject/signals/emitv", test_emitv);
g_test_add_func ("/gobject/signals/accumulator", test_accumulator); g_test_add_func ("/gobject/signals/accumulator", test_accumulator);
g_test_add_func ("/gobject/signals/accumulator-class", test_accumulator_class); g_test_add_func ("/gobject/signals/accumulator-class", test_accumulator_class);
g_test_add_func ("/gobject/signals/introspection", test_introspection); g_test_add_func ("/gobject/signals/introspection", test_introspection);