After an evaluation, GNOME has moved from Bugzilla to GitLab. Learn more about GitLab.
No new issues can be reported in GNOME Bugzilla anymore.
To report an issue in a GNOME project, go to GNOME GitLab.
Do not go to GNOME Gitlab for: Bluefish, Doxygen, GnuCash, GStreamer, java-gnome, LDTP, NetworkManager, Tomboy.
Bug 65041 - _get_type() functions aren't thread safe
_get_type() functions aren't thread safe
Status: RESOLVED FIXED
Product: glib
Classification: Platform
Component: gobject
unspecified
Other Linux
: Normal normal
: ---
Assigned To: Tim Janik
gtkdev
: 161770 300659 317246 (view as bug list)
Depends on: 69668
Blocks:
 
 
Reported: 2001-11-21 16:00 UTC by Owen Taylor
Modified: 2011-02-18 16:14 UTC
See Also:
GNOME target: ---
GNOME version: ---


Attachments
like this ? (1.23 KB, patch)
2003-10-30 23:21 UTC, Matthias Clasen
rejected Details | Review
Makes GObject _get_type() functions thread-safe (8.76 KB, patch)
2004-12-20 14:44 UTC, Jean-Yves Lefort
needs-work Details | Review
the patch (12.14 KB, patch)
2005-03-10 04:11 UTC, Matthias Clasen
none Details | Review
Idempotentize type registration functions (12.88 KB, patch)
2006-05-22 07:38 UTC, ahunter
none Details | Review
Test program (11.81 KB, text/x-csrc)
2006-05-22 07:40 UTC, ahunter
  Details
patch to implement the g_once_init fonctions , THIS PATCH DOES NOT WORK (3.02 KB, patch)
2006-11-02 21:42 UTC, Antoine Tremblay
none Details | Review
Test to check the last patch (1.34 KB, patch)
2006-11-02 21:45 UTC, Antoine Tremblay
none Details | Review
Implementation of g_once_init_enter and _leave (3.36 KB, patch)
2006-11-03 15:52 UTC, Antoine Tremblay
none Details | Review
patch to implement the g_once_init fonctions using a slist (2.55 KB, patch)
2007-04-14 18:04 UTC, Antoine Tremblay
none Details | Review

Description Owen Taylor 2001-11-21 16:00:36 UTC
The standard:

static GType my_type = 0;
if (!my_type) {
   my_type = g_boxed_type_register_static (...);
}

return my_type

Functions aren't thread safe. This isn't a problem for GTK+,
but is for GObject, and for third party libraries using
libgobject in a threaded fashion.

As a hack, this could be solved for GObject by calling all get_type()
functions in g_type_init(). In general, it could be solved
by:

 - Using explicit locks in get_type() functions. (Expensive)

or:

 - If assignment to GType is atomic, and read/write ordering is
   conservative enough to make double-checked-locking working,
   making type registration idempotent - registering a type
   a second time does nothing and just returns the same type.

   The two assumptions are not considered to be absolutely portable,
   but they are apparently safe on most common systems.
Comment 1 Owen Taylor 2001-11-23 14:43:31 UTC
What we might want to do is to encapsulate the operation as:

 static GType type;

 return g_type_register_once (...., &type);

Though that does involve another function call, possibly with
a lot of arguments. To make it perfectly portable, it would
also have to be more along the lines of:

 static GType type;
 static pthread_once_t once = PTHREAD_ONCE_INIT;
 return g_type_register_once (...., &type, &once);

Comment 2 Matthias Clasen 2003-10-30 23:21:52 UTC
Created attachment 21074 [details] [review]
like this ?
Comment 3 Tim Janik 2004-02-20 01:16:08 UTC
a new registration function shouldn't take once data, but simply be a
variant of g_type_register_static() that takes a GType*type_p as first
argument. internally, we can use expensive locking/blocking in case
*type_p==0 if type assignment occours atomic (i.e. once we have atomic
ints).
Comment 4 Jean-Yves Lefort 2004-12-20 14:44:11 UTC
Created attachment 35045 [details] [review]
Makes GObject _get_type() functions thread-safe

In the meanwhile GOnce has been added, this patch uses it.
Comment 5 Jean-Yves Lefort 2004-12-20 14:52:59 UTC
g_type_register_once() is too specialized, it won't help when some extra
processing (such as adding interfaces) is needed after registering a type.

Instead, I suggest to make G_DEFINE_TYPE_EXTENDED thread-safe (using a GOnce).
Comment 6 Owen Taylor 2004-12-20 17:08:24 UTC
G_DEFINE_TYPE is just convenience functionality... it has to be based
on public functionality. Probably the right way to handle interfaces
and prerequisites is to do something along the lines of:

GType
foo_get_type
{
 static GType type;
 static pthread_once_t once = PTHREAD_ONCE_INIT;

 return g_type_register_once (real_init_func, &type, &once);
}

Tim: GOnce is meant to encapsulate system dependencies on 
memory coherency/read/write barriers, etc. To do without, we
have to do one of:

 a) Use double-checked locking, accept that it is questionable but works
    a lot of places
 b) Use double-checked locking, pretend that people will notice when
    it doesn't work and file bug reports so we can work around 
    on particular architectures.
 c) *always* lock on every call to get_type()

I don't much like any of those.
Comment 7 Matthias Clasen 2005-03-10 04:11:39 UTC
Created attachment 38486 [details] [review]
the patch
Comment 8 Matthias Clasen 2005-03-10 12:29:42 UTC
I like the approach taken by the second patch (not add new GType api, let
users use GOnce themselves). My patch extends that and changes G_DEFINE_TYPE
to also create threadsafe get_type functions
Comment 9 Andy Wingo 2005-04-03 19:17:27 UTC
Another thing to keep in mind is that these get_type functions are not really
G_GNUC_CONST, because they access global memory. G_GNUC_PURE functions can
access global memory, but can't change it, so they're not pure either.

Calling the get_type functions from the main thread before spawning off other
threads has been a common technique for getting around threadsafety, but a smart
compiler will omit the function call completely if the return value isn't used,
which can then cause problems when threads actually call a get_type for its value.

Regarding the performance effects of CONST declarations, a hugely serious
scientifical test: A simple startup of gedit shows (startlingly) about 30000
gtk_toolbar_get_type() calls, with kcachegrind showing a 'relative cost' of the
function at 0.05. If I redefine gtk_toolbar_get_type without G_GNUC_CONST in
gtktoolbar.h, I get the same results. FWIW.
Comment 10 Manish Singh 2005-04-03 19:45:27 UTC
How'd you test this? gedit doesn't call any toolbar code directly, lower
libraries in the stack do that work. Did you build the whole stack with the
gtktoolbar.h change?

It may be worth proposing to the gcc people a call_once attribute or somesuch.
Comment 11 Andy Wingo 2005-04-04 08:46:53 UTC
I ran it with valgrind-callgrind, and looked at it with kcachegrind. The calls
are all coming from inside gtk. You can run it yourself if you like: valgrind
--skin=callgrind gedit, then kcachegrind callgrind.out.PID.

Also, the number of times toolbar_get_type was called was the same on both tries
to within one call, meaning the proc was always called for for its value, and
the compiler never decided to cache the return in a register or some such.
Normal CFLAGS (-g, -O2).

I would not worry too much about optimizing get_type calls. First, they're
called most when #ifndef G_DISABLE_CHECKS, which adds more overhead because of
the runtime type checking, which is more expensive than get_type. Second the
cost is low: again with these kcachegrind pseudobenchmarks,
g_type_check_instance_is_a takes about 1.2% of startup time (180 000 calls),
while get_type functions take about 0.2% of startup time (somewhere around 100
000 calls). That's not why gedit is slow to start up ;-)
Comment 12 Manish Singh 2005-04-04 08:51:06 UTC
You didn't answer my question. The question was did you recompile gedit and all
the dependent libs down to gtk+ with the gtktoolbar.h change, or just gedit
itself. Cause if it's just gedit (or even just gedit and gtk+) you probably
won't get a change in the number of gtk_toolbar_get_type calls.
Comment 13 Andy Wingo 2005-04-04 09:06:24 UTC
Only Gtk.
Comment 14 Matthias Clasen 2005-04-04 14:02:54 UTC
The get_type functions are intentionally marked as const to allow the compiler
to optimize them away. If you want to force the call, declare the variable
receiving the result as volatile.
Comment 15 Andy Wingo 2005-04-05 10:29:44 UTC
Right. But (1) They aren't const, in the objective sense of the term, because
they call non-const functions (i.e. g_type_register_static), and (2) I don't
think it makes any difference at all speed-wise.
Comment 16 Manish Singh 2005-04-14 21:34:33 UTC
*** Bug 300659 has been marked as a duplicate of this bug. ***
Comment 17 DINH Viet Hoa 2005-04-15 17:16:36 UTC
I think that g_object_new() should be passed in the pointer to the _get_type() function and should call it 
whenever it decides, g_object_new() will do the right job.
Data types won't have to bother about this.
Comment 18 Matthias Clasen 2005-06-27 21:23:04 UTC
My latest patch doesn't add api.
Comment 19 Matthias Clasen 2005-09-26 14:56:33 UTC
*** Bug 317246 has been marked as a duplicate of this bug. ***
Comment 20 DINH Viet Hoa 2006-04-18 10:32:49 UTC
the last patch does not seem to fit.

1. Trial without API change
since initialization of an object looks like this :

GType
test_object_get_type (void)
{
  static GType test_object_type = 0;

  if (!test_object_type)
    {
      static const GTypeInfo test_object_info =
      {
	sizeof (TestObjectClass),
	NULL,           /* base_init */
	NULL,           /* base_finalize */
	(GClassInitFunc) test_object_class_init,
	NULL,           /* class_finalize */
	NULL,           /* class_data */
	sizeof (TestObject),
	5,              /* n_preallocs */
	(GInstanceInitFunc) test_object_init,
      };
      GInterfaceInfo iface_info = { test_object_test_iface_init, NULL, GUINT_TO_POINTER (42) };

      test_object_type = g_type_register_static (G_TYPE_OBJECT, "TestObject", &test_object_info, 0);
      g_type_add_interface_static (test_object_type, TEST_TYPE_IFACE, &iface_info);
    }

  return test_object_type;
}

(this is extracted from the example in glib)

to avoid API change to all derived gobject, we need to operate into g_type_register_static().
g_type_register_static() will have to check whether the given (GTypeInfo *) is already registered and if this is the case, g_type_register_static() will have to return immediately with the identifier of the object.
A big lock has to be placed in g_type_register_static() but since this is supposed to be called only at initialization phase, this should not be a problem.
A hash table is needed to make it correspond an identifier for a given (GTypeInfo *).

g_type_add_interface_static() will also have to check if the interface is already registered for a given (GInterfaceInfo *).

Though, it will not work for the example since the GInterfaceInfo is allocated on the stack ...
If the developer who defined the object forgot to declare static the GTypeInfo and GInterfaceInfo, this will not work.
Then, g_object_new API must change.

2. proposed API change

GType (* gobject_get_type_declarator)(void);

gpointer g_object_thread_safe_new (g_object_get_type_declarator decl, const gchar *first_property_name, ...);
(and friends of function g_object_thread_safe_new() will also have the same kind of interface)

then, a g_once() of the given gobject_get_type_declarator() will be called in g_object_thread_safe_new().

That will not break the old objects, but will introduce thread-safety for the objects that need it.
Comment 21 Matthias Clasen 2006-05-11 13:01:47 UTC
I am not sure I understand what you are saying here.

> _type_register_static() will have to check whether the given (GTypeInfo *) 
> is already registered and if this is the case, g_type_register_static() will
> have to return immediately with the identifier of the object.

The get_type functions already try to do this; although not in a 
threadsafe way. The GOnce patch will make it threadsafe and remove
the issue.
Comment 22 DINH Viet Hoa 2006-05-11 13:25:14 UTC
> Though, it will not work for the example since the GInterfaceInfo is allocated
> on the stack ...
> If the developer who defined the object forgot to declare static the GTypeInfo
> and GInterfaceInfo, this will not work.
> Then, g_object_new API must change.
Comment 23 ahunter 2006-05-22 07:38:34 UTC
Created attachment 65981 [details] [review]
Idempotentize type registration functions

Patch for Owen Taylor's original 2nd option
Comment 24 ahunter 2006-05-22 07:40:32 UTC
Created attachment 65982 [details]
Test program

Program to test idempotentize patch
Comment 25 ahunter 2006-05-22 07:53:21 UTC
The GOnce solution is way better than the idempotentize solution, but it doesn't help code that is already written the old way.
Comment 26 Tim Janik 2006-05-31 14:53:37 UTC
here's a thread from 2003 also complaining about the issue:
  http://mail.gnome.org/archives/gtk-list/2003-July/msg00033.html
Comment 27 Tim Janik 2006-05-31 14:55:03 UTC
*** Bug 161770 has been marked as a duplicate of this bug. ***
Comment 28 Tim Janik 2006-10-26 13:54:18 UTC
(In reply to comment #6)

> GType
> foo_get_type
> {
>  static GType type;
>  static pthread_once_t once = PTHREAD_ONCE_INIT;
> 
>  return g_type_register_once (real_init_func, &type, &once);
> }
> 
> Tim: GOnce is meant to encapsulate system dependencies on 
> memory coherency/read/write barriers, etc. To do without, we
> have to do one of:
> 
>  a) Use double-checked locking, accept that it is questionable but works
>     a lot of places
>  b) Use double-checked locking, pretend that people will notice when
>     it doesn't work and file bug reports so we can work around 
>     on particular architectures.
>  c) *always* lock on every call to get_type()
> 
> I don't much like any of those.

with the arrival of atomic operations, this is fortunately not true anymore.
i think we can join the type_id with the "once" field conveniently now with something like:

GType
foo_get_type
{
  static GType type_id = 0;
  if (g_once_init_enter (&type_id))
    {
      GType id = g_type_register_static (...);
      g_once_init_leave (&type_id, id);
    }
  return type_id;
}

with these prototypes (using gsize ensures pointer type width):

static inline gboolean  g_once_init_enter (volatile gsize *value_location);
void                    g_once_init_leave (volatile gsize *value_location,
                                           gsize           value);

note the following:
- the type registration code can stay *inline* and doesn't need a seperate
  real_init_func() to be implemented (one of my main concerns with the former
  GOnce suggestions)
- g_once_init_enter() can be implemented static inline which allows to quickly
  return FALSE if g_atomic_pointer_get (value_location) != 0.
- if g_atomic_pointer_get (value_location) == 0, g_once_init_enter() returns
  TRUE if called the first time, or blocks, until g_once_init_leave() was called
  for the same address value_location.
- g_once_init_leave() ensures via return_if_fail, that *value_location is still
  ==0. other than that, it assigns *value_location and causes any pending
  g_once_init_enter() calls blocking on the same address value_location to
  return.
- the only performance impact this approach has over the current single-threaded
  _get_type() functions is using g_atomic_pointer_get() instead of a normal
  static variable read for the common case.
- the heavy performance penalties (acquirering/releasing a mutex and blocking on
  a condition) are hidden inside g_once_init_enter/g_once_init_leave and are
  only triggered in the contention case, which is acceptable.
Comment 29 Antoine Tremblay 2006-10-31 23:11:27 UTC
I'm about to change gstreamer code to use the g_once solution to fix the problem in our get_type fonctions...

But I'm hesitating since this seems to be still in discussion... should I wait before undertaking this "rewrite" (there's a lot of get_type fonctions) ? 

Matthias Clasen seemed to have an acceptable patch in March 2005 it's now October 2006 ...now there's a new suggestion as comment #28 wich looks good to me except that it adds API and thus it will introduce a glib version requirement.. ( the gonce patch would too but wouldn't be a "hard" requirement because the API would not change). 

In short I would favor the patch of Matthias Clasen but what I would really like is someone to decide so that the apps based on glib can finally stop crashing... ?





Comment 30 Tim Janik 2006-10-31 23:16:13 UTC
(In reply to comment #29)
> I'm about to change gstreamer code to use the g_once solution to fix the
> problem in our get_type fonctions...
> 
> But I'm hesitating since this seems to be still in discussion... should I wait
> before undertaking this "rewrite" (there's a lot of get_type fonctions) ? 
> 
> Matthias Clasen seemed to have an acceptable patch in March 2005 it's now
> October 2006 ...now there's a new suggestion as comment #28 wich looks good to
> me except that it adds API and thus it will introduce a glib version
> requirement.. ( the gonce patch would too but wouldn't be a "hard" requirement
> because the API would not change). 
> 
> In short I would favor the patch of Matthias Clasen but what I would really
> like is someone to decide so that the apps based on glib can finally stop
> crashing... ?

i intend to implement #28 as soon as i get the time (that might be in a couple weeks though) unless there are serious objections to this approach.
note that you can still implement your own GOnce or GMutex based get_type workarounds and don't need to be affected by the then-newly offered API if you are striving for an earlier solution.
Comment 31 Antoine Tremblay 2006-10-31 23:22:05 UTC
ok :) thanks for the very quick reply!
Comment 32 Antoine Tremblay 2006-11-01 19:04:11 UTC
I will implement the

static inline gboolean  gst_g_once_init_enter (volatile gsize *value_location);
void  g_once_init_leave (volatile gsize *value_location,  gsize value);

fonctions in gstreamer with the same api so that when they are in g_lib we can use them direclty...

Maybe my code would fit in glib too...

There's just 1 or 2 things I need to clarify :

when you say :

- if g_atomic_pointer_get (value_location) == 0, g_once_init_enter() returns
  TRUE if called the first time, or blocks, until g_once_init_leave() was
called

Won't we need a Gonce struct passed to the fonction to be able to set it a state and know if it was called the 1st time etc.. ? .. So I can 

so it would be more like :

static inline gboolean  g_once_init_enter (GOnce* once, volatile gsize *value_location);
void  g_once_init_leave (GOnce* once, volatile gsize *value_location,  gsize value);

And when it's not the first time, I would need a mutex or something to signal that leave is done should we create one and pass it to the fonction or any idea on how I would wait for this ? use the gonce_mutex ?

 


Comment 33 Tim Janik 2006-11-02 11:53:20 UTC
(In reply to comment #32)
> I will implement the
> 
> static inline gboolean  gst_g_once_init_enter (volatile gsize *value_location);
> void  g_once_init_leave (volatile gsize *value_location,  gsize value);
> 
> fonctions in gstreamer with the same api so that when they are in g_lib we can
> use them direclty...
> 
> Maybe my code would fit in glib too...

if you provided a patch for glib as well, that could speed up the implementation.

> when you say :
> 
> - if g_atomic_pointer_get (value_location) == 0, g_once_init_enter() returns
>   TRUE if called the first time, or blocks, until g_once_init_leave() was
> called
> 
> Won't we need a Gonce struct passed to the fonction to be able to set it a
> state and know if it was called the 1st time etc.. ? .. So I can 

no, the point is to get rid of that GOnce as well and we can do that, because the code portion is uniquely identified by the pointer to the static type_id.

> And when it's not the first time, I would need a mutex or something to signal
> that leave is done should we create one and pass it to the fonction or any idea
> on how I would wait for this ? use the gonce_mutex ?

well, one possible implementation can look like this (for the case where *value_location is still == 0):

g_once_init_enter gets a mutex, a condition and a global list. the list contains value_locations from _enter without _leave calls.
when _enter is called, it determines if value_location is in the list, if not, it adds it and returns. if it is present already, it waits in a loop on the condition until it's not in the list anymore then returns.
_leave then asserts value_location is in the list, makes sure *value_location==0 (via return_if_fail), removes it, assigns it signals condition and returns.
Comment 34 Antoine Tremblay 2006-11-02 15:57:56 UTC
(In reply to comment #33)
> if you provided a patch for glib as well, that could speed up the
> implementation.

I will :)

> well, one possible implementation can look like this (for the case where
> *value_location is still == 0):
> 
> g_once_init_enter gets a mutex, a condition and a global list. the list
> contains value_locations from _enter without _leave calls.
> when _enter is called, it determines if value_location is in the list, if not,
> it adds it and returns. if it is present already, it waits in a loop on the
> condition until it's not in the list anymore then returns.
> _leave then asserts value_location is in the list, makes sure
> *value_location==0 (via return_if_fail), removes it, assigns it signals
> condition and returns.
 
Humm, I don't understand why we would prefer using a locked global list rather then a struct with a atomic read/set (GOnce struct) ... seems to me the the list (wich could get big) introduces more of a performance penality no ?

Here's how I saw it until now:


static inline gboolean  g_once_init_enter (GOnce* once, volatile gsize *value_location)
{
  if (g_atomic_pointer_get (value_location) !=0)
  {
    return FALSE;
  }
  else
  {
    if (g_atomic_get_int (once->status) == G_ONCE_STATUS_NOTCALLED)
      return TRUE;
    else
    {
      while(g_atomic_get_int (once->status) != G_ONCE_STATUS_READY)
        g_cond_wait (g_once_cond, g_once_mutex);
      
      return FALSE;
    }
  }
}

void  g_once_init_leave (GOnce* once, volatile gsize *value_location,  gsize value)
{
  g_return_if_fail (value_location == 0);

  *value_location = value;

  g_atomic_set_int (once->status, G_ONCE_STATUS_READY);
  g_cond_broadcast (g_once_cond);
  
}

We don't need any locking for the brodcast/signal since it uses atomic operations , the mutex would be used only for the signaling/blocking... but maybe another mutex might be used in other not to conflict with g_once_impl...

Seems faster then locking /parsing a table... what do you think?



Comment 35 Tim Janik 2006-11-02 16:04:49 UTC
(In reply to comment #34)
> (In reply to comment #33)
> > g_once_init_enter gets a mutex, a condition and a global list. the list
> > contains value_locations from _enter without _leave calls.
> > when _enter is called, it determines if value_location is in the list, if not,
> > it adds it and returns. if it is present already, it waits in a loop on the
> > condition until it's not in the list anymore then returns.
> > _leave then asserts value_location is in the list, makes sure
> > *value_location==0 (via return_if_fail), removes it, assigns it signals
> > condition and returns.
> 
> Humm, I don't understand why we would prefer using a locked global list rather
> then a struct with a atomic read/set (GOnce struct) ...

not needing GOnce in the public API is the main point in the API i outlined,
i'm afraid you have to stick to that ;-)

> seems to me the the
> list (wich could get big) introduces more of a performance penality no ?

no, the list can not get big. it only contains entries if there is any thread between _enter/_leave. and the time spend there is supposed to be short. so the list is almost always empty.

> We don't need any locking for the brodcast/signal since it uses atomic
> operations , the mutex would be used only for the signaling/blocking... but
> maybe another mutex might be used in other not to conflict with g_once_impl...
> 
> Seems faster then locking /parsing a table... what do you think?

g_once_init_enter() as outlined in my previous comments is already very fast in the common case, because of it's initial if (atomic_get() != 0) return ...;
everything that comes thereafter maybe slow because it's very seldomly triggered.
Comment 36 Antoine Tremblay 2006-11-02 16:34:37 UTC
> not needing GOnce in the public API is the main point in the API i outlined,
> i'm afraid you have to stick to that ;-)

ok :)

> no, the list can not get big. it only contains entries if there is any thread
> between _enter/_leave. and the time spend there is supposed to be short. so the
> list is almost always empty.

you're right it seemed bad to me since I tought of it as an O(N) operation with a fixed table and that it introduced a possible problem with the max size of the table too..

But I guess I could do it with a hash table and it would bo O(1) with no real limitation on size .. that's how you saw it I guess ?


Comment 37 Antoine Tremblay 2006-11-02 20:10:17 UTC
And why declare g_once_init_enter() static ? doesn't make sense to me since it's a public fonction of the api .. ?
Comment 38 Antoine Tremblay 2006-11-02 21:42:05 UTC
Created attachment 75891 [details] [review]
patch to implement the g_once_init fonctions , THIS PATCH DOES NOT WORK 

I tryed to implement the fonctions but it doesn't seem to work (see test program below).. not sure why.. if anyone wants to review... ?
Comment 39 Antoine Tremblay 2006-11-02 21:45:24 UTC
Created attachment 75892 [details] [review]
Test to check the last patch 

Run this on a SMP machine with a compile line like :

LD_LIBRARY_PATH=/opt/glib/2.12.4/lib gcc -g -O0 -I/opt/glib/2.12.4/include/glib-2.0/  -I/opt/glib/2.12.4/include/ -o test  -pthread -L /opt/glib/2.12.4/lib -lgobject-2.0 -lgthread-2.0 -lgmodule-2.0 -ldl -lglib-2.0 test.c

You will endup with a crash (maybe after a few trys ) like :

  • #8 IA__g_convert_with_fallback

Currently I have no idea why... comments are welcome
Comment 40 Antoine Tremblay 2006-11-03 01:23:12 UTC
I found my problem.. need to recheck after the 1st lock in the enter... will send a correct patch tomorow
Comment 41 Antoine Tremblay 2006-11-03 15:52:56 UTC
Created attachment 75941 [details] [review]
Implementation of g_once_init_enter and _leave 

This patch is working and should be clean... this is my final patch

Please review/comment
Comment 42 Matthias Clasen 2006-12-16 14:19:19 UTC
Tim, does this implement what you had in mind  ?
Comment 43 Antoine Tremblay 2007-03-02 21:42:42 UTC
Tim are you there ?
Comment 44 Tim Janik 2007-03-09 10:56:45 UTC
(In reply to comment #43)
> Tim are you there ?

sorry, been really busy with other projects meanwhile.
thanks for working on the patch.

(In reply to comment #36)
> > so the list is almost always empty.
> 
> you're right it seemed bad to me since I tought of it as an O(N) operation with
> a fixed table and that it introduced a possible problem with the max size of
> the table too..
> 
> But I guess I could do it with a hash table and it would bo O(1) with no real
> limitation on size .. that's how you saw it I guess ?

yes, allthough i think using a hashtable is overkill. that's because the hashtable/list is only filled in *contention* scenarios, and lookups are performed by an otherwise idle/blocked thread anyway. so O(N) list operation is by far good enough.
the patch looks like a good start, some nit picks:
- please use diff -up for generation
- please add g_return_if_fail (value_location != NULL); etc. checks at the start of your functions
- please put the return value onto an extra line, like:
  int
  foo ()
  {}
- also, your patch can actually use g_once_mutex and g_once_cond, so you can skip your initialization code.
- you don't need to guard atomic accessors with locks. the idea is to use an atomic accessor to speed up the common case (no contention) by getting around the need to use a lock, so this:
  +  g_mutex_lock (g_once_init_mutex);
  +  if (g_atomic_pointer_get (value_location) !=0)
  +    { g_mutex_unlock (g_once_init_mutex); return FALSE; }
  should be:
  +  if (g_atomic_pointer_get (value_location) !=0)
  +    return FALSE;
  +  g_mutex_lock (g_once_init_mutex);

as an aside, while reading over your patch, i think the technique used here could more generally be useful for initializations where you don't want to tear your code apart into initialization callbacks, e.g. also

an_initialization_function (int a, double b, void *foo)
{
  static gsize initialized = 0;
  if (g_once_init_enter (&initialized))
    {
      my_initialization_code (a, b);
      setup (foo);
      g_once_init_leave (&initialized, 1);
    }
}

this just bears the question of whether gsize is the right type. i think the answer to that is yes, because you'd almost always want an integer there, but the exceptional case where you want to store a pointer is still supported via casts.
Comment 45 Antoine Tremblay 2007-03-09 15:33:21 UTC
Thanks a lot for the comments :)


I will rework the patch next week ( I hope )

Comment 46 Antoine Tremblay 2007-04-14 18:04:59 UTC
Created attachment 86351 [details] [review]
patch to implement the g_once_init fonctions using a slist 

This patch uses a GSlist rather then a hash table
It also uses the g_once_mutex and cond rather then it's own
Added the return type on an extra line
Removed the unnecessary lock with a atomic function (don't know why I had this)
Made with diff -up

Hope it's ok :)
Comment 47 Tim Janik 2007-07-06 11:43:44 UTC
(In reply to comment #46)
> Created an attachment (id=86351) [edit]
> patch to implement the g_once_init fonctions using a slist 
> 
> This patch uses a GSlist rather then a hash table
> It also uses the g_once_mutex and cond rather then it's own
> Added the return type on an extra line
> Removed the unnecessary lock with a atomic function (don't know why I had this)
> Made with diff -up
> 
> Hope it's ok :)

looks good now except for some coding style changes. and we should slightly rename g_once_init_enter_locked() because often the "_locked" postfix indicates that a function is called assuming a specific lock is acquired. enter_locking() or enter_slow() migth be alternatives...
also, we should have docs for the new functions and use it for a few existing _get_type cases.
Comment 48 Tim Janik 2007-07-10 10:54:03 UTC
Tue Jul 10 12:24:35 2007  Tim Janik  <timj@imendio.com>

        * glib/gthread.[hc]: implemented g_once_init_enter(),
        g_once_init_enter_impl() and g_once_init_leave(), based on a patch by
        Antoine Tremblay, fixes #65041.
        adapted exported inline function mechanism from gutils.[hc] for inlining
        g_once_init_enter_impl() in gthread.[hc].

Tue Jul 10 12:31:50 2007  Tim Janik  <timj@imendio.com>

        * gtype.h: use g_once_init_enter/g_once_init_leave to guard critical
        initialization section of *_get_type implementations in G_DEFINE_TYPE,
        bug #65041.
Comment 49 Federico Mena Quintero 2007-08-10 15:36:28 UTC
This breaks Konqueror's nspluginviewer and operapluginwrap.  See https://bugzilla.novell.com/show_bug.cgi?id=294385 for details.
Comment 50 Matthias Clasen 2007-08-10 15:56:30 UTC
Are you compiling with -O3 ? 
gcc seems to have started moving our const-annotated get_type() functions wildly around, which could cause that issue.
Comment 51 Federico Mena Quintero 2007-08-11 00:14:25 UTC
(In reply to comment #50)
> Are you compiling with -O3 ? 
> gcc seems to have started moving our const-annotated get_type() functions
> wildly around, which could cause that issue.

Nope, plain -O2 plus some extra flags (-fstack-protector -fomit-frame-pointer etc.)
Comment 52 Matthias Clasen 2007-08-11 00:29:59 UTC
Can you verify if the problem is that a get_type function is called before 
g_type_init ?
Comment 53 Tim Janik 2007-08-14 00:15:40 UTC
(In reply to comment #49)
> This breaks Konqueror's nspluginviewer and operapluginwrap.  See
> https://bugzilla.novell.com/show_bug.cgi?id=294385 for details.

the warning "(process:19191): GLib-GObject-CRITICAL **: gtype.c:2242: initialization assertion failed, use IA__g_type_init() prior to this function" from that bug report is due to gcc moving around GNUC_CONST functions (before gtk_init() and other initializers). get rid of that attribute or call g_type_init() *very* early on in your main as a workaround, also see bug #446565 for a proper fix.

i did find a race in the once_init code however, fixed in SVN. if you run into any new issues, please open a new bug report and attach test cases/references there. this one is long enough already and the original issues of this report has been adressed.

Tue Aug 14 02:06:10 2007  Tim Janik  <timj@imendio.com>

        * glib/gthread.c (g_once_init_enter_impl): prevent race covered
        by g_once_init_enter(), by checking for previous initializations
        before entering initialisation branch.

        * tests/onceinit.c: added multi-thread/multi-initializer stress test
        using unoptimized g_once_init_enter_impl().