Blame SOURCES/0001-IntrospectionModule-handle-two-threads-loading-type-.patch

2ce772
From 1212d1cd3d4b47294770408a7abd18bc4c578a64 Mon Sep 17 00:00:00 2001
2ce772
From: Ray Strode <rstrode@redhat.com>
2ce772
Date: Wed, 10 Jun 2020 18:04:07 -0400
2ce772
Subject: [PATCH] IntrospectionModule: handle two threads loading type at same
2ce772
 time
2ce772
2ce772
If two threads are trying to load a type at exactly the same time,
2ce772
it's possible for two wrappers to get generated for the type.
2ce772
One thread will end up with the wrapper that's not blessed as the
2ce772
"real" one and future calls will fail.  The blessed wrapper will
2ce772
be incomplete, and so future calls from it will fail as well.
2ce772
2ce772
This commit adds a lock to ensure the two threads don't stomp
2ce772
on each others toes.
2ce772
---
2ce772
 gi/module.py | 110 +++++++++++++++++++++++++++------------------------
2ce772
 1 file changed, 58 insertions(+), 52 deletions(-)
2ce772
2ce772
diff --git a/gi/module.py b/gi/module.py
2ce772
index f9e26bc2..93b8e6a3 100644
2ce772
--- a/gi/module.py
2ce772
+++ b/gi/module.py
2ce772
@@ -1,53 +1,54 @@
2ce772
 # -*- Mode: Python; py-indent-offset: 4 -*-
2ce772
 # vim: tabstop=4 shiftwidth=4 expandtab
2ce772
 #
2ce772
 # Copyright (C) 2007-2009 Johan Dahlin <johan@gnome.org>
2ce772
 #
2ce772
 #   module.py: dynamic module for introspected libraries.
2ce772
 #
2ce772
 # This library is free software; you can redistribute it and/or
2ce772
 # modify it under the terms of the GNU Lesser General Public
2ce772
 # License as published by the Free Software Foundation; either
2ce772
 # version 2.1 of the License, or (at your option) any later version.
2ce772
 #
2ce772
 # This library is distributed in the hope that it will be useful,
2ce772
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
2ce772
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
2ce772
 # Lesser General Public License for more details.
2ce772
 #
2ce772
 # You should have received a copy of the GNU Lesser General Public
2ce772
 # License along with this library; if not, write to the Free Software
2ce772
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
2ce772
 # USA
2ce772
 
2ce772
 import importlib
2ce772
+from threading import Lock
2ce772
 
2ce772
 import gi
2ce772
 
2ce772
 from ._gi import \
2ce772
     Repository, \
2ce772
     FunctionInfo, \
2ce772
     RegisteredTypeInfo, \
2ce772
     EnumInfo, \
2ce772
     ObjectInfo, \
2ce772
     InterfaceInfo, \
2ce772
     ConstantInfo, \
2ce772
     StructInfo, \
2ce772
     UnionInfo, \
2ce772
     CallbackInfo, \
2ce772
     Struct, \
2ce772
     Boxed, \
2ce772
     CCallback, \
2ce772
     enum_add, \
2ce772
     enum_register_new_gtype_and_add, \
2ce772
     flags_add, \
2ce772
     flags_register_new_gtype_and_add, \
2ce772
     GInterface
2ce772
 from .types import \
2ce772
     GObjectMeta, \
2ce772
     StructMeta
2ce772
 
2ce772
 from ._constants import \
2ce772
     TYPE_NONE, \
2ce772
     TYPE_BOXED, \
2ce772
     TYPE_POINTER, \
2ce772
@@ -90,152 +91,157 @@ def get_interfaces_for_object(object_info):
2ce772
         namespace = interface_info.get_namespace()
2ce772
         name = interface_info.get_name()
2ce772
 
2ce772
         module = importlib.import_module('gi.repository.' + namespace)
2ce772
         interfaces.append(getattr(module, name))
2ce772
     return interfaces
2ce772
 
2ce772
 
2ce772
 class IntrospectionModule(object):
2ce772
     """An object which wraps an introspection typelib.
2ce772
 
2ce772
     This wrapping creates a python module like representation of the typelib
2ce772
     using gi repository as a foundation. Accessing attributes of the module
2ce772
     will dynamically pull them in and create wrappers for the members.
2ce772
     These members are then cached on this introspection module.
2ce772
     """
2ce772
     def __init__(self, namespace, version=None):
2ce772
         """Might raise gi._gi.RepositoryError"""
2ce772
 
2ce772
         repository.require(namespace, version)
2ce772
         self._namespace = namespace
2ce772
         self._version = version
2ce772
         self.__name__ = 'gi.repository.' + namespace
2ce772
 
2ce772
         path = repository.get_typelib_path(self._namespace)
2ce772
         self.__path__ = [path]
2ce772
 
2ce772
         if self._version is None:
2ce772
             self._version = repository.get_version(self._namespace)
2ce772
 
2ce772
+        self._lock = Lock()
2ce772
+
2ce772
     def __getattr__(self, name):
2ce772
         info = repository.find_by_name(self._namespace, name)
2ce772
         if not info:
2ce772
             raise AttributeError("%r object has no attribute %r" % (
2ce772
                                  self.__name__, name))
2ce772
 
2ce772
         if isinstance(info, EnumInfo):
2ce772
             g_type = info.get_g_type()
2ce772
-            wrapper = g_type.pytype
2ce772
 
2ce772
-            if wrapper is None:
2ce772
-                if info.is_flags():
2ce772
-                    if g_type.is_a(TYPE_FLAGS):
2ce772
-                        wrapper = flags_add(g_type)
2ce772
-                    else:
2ce772
-                        assert g_type == TYPE_NONE
2ce772
-                        wrapper = flags_register_new_gtype_and_add(info)
2ce772
-                else:
2ce772
-                    if g_type.is_a(TYPE_ENUM):
2ce772
-                        wrapper = enum_add(g_type)
2ce772
+            with self._lock:
2ce772
+                wrapper = g_type.pytype
2ce772
+
2ce772
+                if wrapper is None:
2ce772
+                    if info.is_flags():
2ce772
+                        if g_type.is_a(TYPE_FLAGS):
2ce772
+                            wrapper = flags_add(g_type)
2ce772
+                        else:
2ce772
+                            assert g_type == TYPE_NONE
2ce772
+                            wrapper = flags_register_new_gtype_and_add(info)
2ce772
                     else:
2ce772
-                        assert g_type == TYPE_NONE
2ce772
-                        wrapper = enum_register_new_gtype_and_add(info)
2ce772
-
2ce772
-                wrapper.__info__ = info
2ce772
-                wrapper.__module__ = 'gi.repository.' + info.get_namespace()
2ce772
-
2ce772
-                # Don't use upper() here to avoid locale specific
2ce772
-                # identifier conversion (e. g. in Turkish 'i'.upper() == 'i')
2ce772
-                # see https://bugzilla.gnome.org/show_bug.cgi?id=649165
2ce772
-                ascii_upper_trans = ''.maketrans(
2ce772
-                    'abcdefgjhijklmnopqrstuvwxyz',
2ce772
-                    'ABCDEFGJHIJKLMNOPQRSTUVWXYZ')
2ce772
-                for value_info in info.get_values():
2ce772
-                    value_name = value_info.get_name_unescaped().translate(ascii_upper_trans)
2ce772
-                    setattr(wrapper, value_name, wrapper(value_info.get_value()))
2ce772
-                for method_info in info.get_methods():
2ce772
-                    setattr(wrapper, method_info.__name__, method_info)
2ce772
-
2ce772
-            if g_type != TYPE_NONE:
2ce772
-                g_type.pytype = wrapper
2ce772
+                        if g_type.is_a(TYPE_ENUM):
2ce772
+                            wrapper = enum_add(g_type)
2ce772
+                        else:
2ce772
+                            assert g_type == TYPE_NONE
2ce772
+                            wrapper = enum_register_new_gtype_and_add(info)
2ce772
+
2ce772
+                    wrapper.__info__ = info
2ce772
+                    wrapper.__module__ = 'gi.repository.' + info.get_namespace()
2ce772
+
2ce772
+                    # Don't use upper() here to avoid locale specific
2ce772
+                    # identifier conversion (e. g. in Turkish 'i'.upper() == 'i')
2ce772
+                    # see https://bugzilla.gnome.org/show_bug.cgi?id=649165
2ce772
+                    ascii_upper_trans = ''.maketrans(
2ce772
+                        'abcdefgjhijklmnopqrstuvwxyz',
2ce772
+                        'ABCDEFGJHIJKLMNOPQRSTUVWXYZ')
2ce772
+                    for value_info in info.get_values():
2ce772
+                        value_name = value_info.get_name_unescaped().translate(ascii_upper_trans)
2ce772
+                        setattr(wrapper, value_name, wrapper(value_info.get_value()))
2ce772
+                    for method_info in info.get_methods():
2ce772
+                        setattr(wrapper, method_info.__name__, method_info)
2ce772
+
2ce772
+                if g_type != TYPE_NONE:
2ce772
+                    g_type.pytype = wrapper
2ce772
 
2ce772
         elif isinstance(info, RegisteredTypeInfo):
2ce772
             g_type = info.get_g_type()
2ce772
 
2ce772
             # Create a wrapper.
2ce772
             if isinstance(info, ObjectInfo):
2ce772
                 parent = get_parent_for_object(info)
2ce772
                 interfaces = tuple(interface for interface in get_interfaces_for_object(info)
2ce772
                                    if not issubclass(parent, interface))
2ce772
                 bases = (parent,) + interfaces
2ce772
                 metaclass = GObjectMeta
2ce772
             elif isinstance(info, CallbackInfo):
2ce772
                 bases = (CCallback,)
2ce772
                 metaclass = GObjectMeta
2ce772
             elif isinstance(info, InterfaceInfo):
2ce772
                 bases = (GInterface,)
2ce772
                 metaclass = GObjectMeta
2ce772
             elif isinstance(info, (StructInfo, UnionInfo)):
2ce772
                 if g_type.is_a(TYPE_BOXED):
2ce772
                     bases = (Boxed,)
2ce772
                 elif (g_type.is_a(TYPE_POINTER) or
2ce772
                       g_type == TYPE_NONE or
2ce772
                       g_type.fundamental == g_type):
2ce772
                     bases = (Struct,)
2ce772
                 else:
2ce772
                     raise TypeError("unable to create a wrapper for %s.%s" % (info.get_namespace(), info.get_name()))
2ce772
                 metaclass = StructMeta
2ce772
             else:
2ce772
                 raise NotImplementedError(info)
2ce772
 
2ce772
-            # Check if there is already a Python wrapper that is not a parent class
2ce772
-            # of the wrapper being created. If it is a parent, it is ok to clobber
2ce772
-            # g_type.pytype with a new child class wrapper of the existing parent.
2ce772
-            # Note that the return here never occurs under normal circumstances due
2ce772
-            # to caching on the __dict__ itself.
2ce772
-            if g_type != TYPE_NONE:
2ce772
-                type_ = g_type.pytype
2ce772
-                if type_ is not None and type_ not in bases:
2ce772
-                    self.__dict__[name] = type_
2ce772
-                    return type_
2ce772
-
2ce772
-            dict_ = {
2ce772
-                '__info__': info,
2ce772
-                '__module__': 'gi.repository.' + self._namespace,
2ce772
-                '__gtype__': g_type
2ce772
-            }
2ce772
-            wrapper = metaclass(name, bases, dict_)
2ce772
-
2ce772
-            # Register the new Python wrapper.
2ce772
-            if g_type != TYPE_NONE:
2ce772
-                g_type.pytype = wrapper
2ce772
+            with self._lock:
2ce772
+                # Check if there is already a Python wrapper that is not a parent class
2ce772
+                # of the wrapper being created. If it is a parent, it is ok to clobber
2ce772
+                # g_type.pytype with a new child class wrapper of the existing parent.
2ce772
+                # Note that the return here never occurs under normal circumstances due
2ce772
+                # to caching on the __dict__ itself.
2ce772
+                if g_type != TYPE_NONE:
2ce772
+                    type_ = g_type.pytype
2ce772
+                    if type_ is not None and type_ not in bases:
2ce772
+                        self.__dict__[name] = type_
2ce772
+                        return type_
2ce772
+
2ce772
+                dict_ = {
2ce772
+                    '__info__': info,
2ce772
+                    '__module__': 'gi.repository.' + self._namespace,
2ce772
+                    '__gtype__': g_type
2ce772
+                }
2ce772
+                wrapper = metaclass(name, bases, dict_)
2ce772
+
2ce772
+                # Register the new Python wrapper.
2ce772
+                if g_type != TYPE_NONE:
2ce772
+                    g_type.pytype = wrapper
2ce772
 
2ce772
         elif isinstance(info, FunctionInfo):
2ce772
             wrapper = info
2ce772
         elif isinstance(info, ConstantInfo):
2ce772
             wrapper = info.get_value()
2ce772
         else:
2ce772
             raise NotImplementedError(info)
2ce772
 
2ce772
         # Cache the newly created wrapper which will then be
2ce772
         # available directly on this introspection module instead of being
2ce772
         # lazily constructed through the __getattr__ we are currently in.
2ce772
         self.__dict__[name] = wrapper
2ce772
         return wrapper
2ce772
 
2ce772
     def __repr__(self):
2ce772
         path = repository.get_typelib_path(self._namespace)
2ce772
         return "<IntrospectionModule %r from %r>" % (self._namespace, path)
2ce772
 
2ce772
     def __dir__(self):
2ce772
         # Python's default dir() is just dir(self.__class__) + self.__dict__.keys()
2ce772
         result = set(dir(self.__class__))
2ce772
         result.update(self.__dict__.keys())
2ce772
 
2ce772
         # update *set* because some repository attributes have already been
2ce772
         # wrapped by __getattr__() and included in self.__dict__; but skip
2ce772
         # Callback types, as these are not real objects which we can actually
2ce772
         # get
2ce772
         namespace_infos = repository.get_infos(self._namespace)
2ce772
         result.update(info.get_name() for info in namespace_infos if
2ce772
                       not isinstance(info, CallbackInfo))
2ce772
-- 
2ce772
2.31.1
2ce772