zoukankan      html  css  js  c++  java
  • 记一次 Apache HUE 优化之因使用 Python 魔术方法而遇到的坑

    最近的工作是基于 Apache HUE 做二次开发.刚接手 HUE 的代码的时候,内心是崩溃的:开源的代码,风格很多种, 代码比较杂乱; 虽是基于 Django 开发的,但是项目的结构改变很大; 很多地方留下了坑; 前人基于此项目做了一些开发, 考虑欠佳, 杂乱中又增添了些杂乱......
    没办法,既然参与了进来,就贡献自己的一份力量.
    今天在优化 Lib Sentry 的时候,不经意间就出现了一个 Bug. 项目中,有处使用了全局锁的形式,来将 Sentry 的链接存入到全局变量中. 我试着用 Django 缓存的形式将其替换,以提高代码的效率.但是, run 起来的时候,很快就出现了调用栈溢出的现象.为什么会出现这种情况? 难道是导入不合理?先就是一顿 import review. 发现并没有类似的循环导入, 目录结构也还OK啊.那问题出现哪呢? 没办法,借助日志, 发现了一些问题:

    File "/home/hp/Project/platform/desktop/core/src/desktop/lib/thrift_util.py" in __getattr__
      364.     superclient = _connection_pool.get_client(self.conf,
    File "/home/hp/Project/platform/desktop/core/src/desktop/lib/thrift_util.py" in __getattr__
      364.     superclient = _connection_pool.get_client(self.conf,
    File "/home/hp/Project/platform/desktop/core/src/desktop/lib/thrift_util.py" in __getattr__
      364.     superclient = _connection_pool.get_client(self.conf,
    File "/home/hp/Project/platform/desktop/core/src/desktop/lib/thrift_util.py" in __getattr__
      364.     superclient = _connection_pool.get_client(self.conf,
    File "/home/hp/Project/platform/desktop/core/src/desktop/lib/thrift_util.py" in __getattr__
      364.     superclient = _connection_pool.get_client(self.conf,
    File "/home/hp/Project/platform/desktop/core/src/desktop/lib/thrift_util.py" in __getattr__
      364.     superclient = _connection_pool.get_client(self.conf,
    File "/home/hp/Project/platform/desktop/core/src/desktop/lib/thrift_util.py" in __getattr__
      364.     superclient = _connection_pool.get_client(self.conf,
    File "/home/hp/Project/platform/desktop/core/src/desktop/lib/thrift_util.py" in __getattr__
      364.     superclient = _connection_pool.get_client(self.conf,
    File "/home/hp/Project/platform/desktop/core/src/desktop/lib/thrift_util.py" in __getattr__
      364.     superclient = _connection_pool.get_client(self.conf,
    

    日志的信息显示,在 thrift_utils.py 文件中,发现一直有个方法在执行,且是同一行.为什么?看源码.

    class PooledClient(object):
      """
      A wrapper for a SuperClient
      """
      def __init__(self, conf):
        self.conf = conf
    
      def __getattr__(self, attr_name):
        if attr_name in self.__dict__:
          return self.__dict__[attr_name]
    
        # Fetch the thrift client from the pool
        superclient = _connection_pool.get_client(self.conf,
            get_client_timeout=self.conf.timeout_seconds)
    
        # Fetch the attribute. If it's callable, wrap it in a wrapper that re-gets
        # the client.
        try:
          attr = getattr(superclient, attr_name)
    
          if callable(attr):
            return self._wrap_callable(attr_name)
          else:
            return attr
        finally:
          self._return_client(superclient)
    
    

    这是 HUE 源码的片段, 抛错就是从这里出现的. 发现一直在执行 superclient = _connection_pool.get_client(... 这块.WHY? 难道是 conf 没有?试着去加些打印信息,发现果然是没有 conf. 不能啊!为什么会没有 conf 呢?
    于是,再看下Django抛出的 error 信息,发现了一些信息:

    py2.7.egg/django/core/cache/backends/locmem.py" in get
      48.                     return pickle.loads(pickled)
    

    程序是执行到这之后,才一直在重复执行上面的错误的.为什么 loads 的时候会出错呢? 首先猜想的是, loads 的时候,因为什么原因导致了 PooledClient 的 object 没有 conf 属性. 那就看下 pickle.loads. 看完之后,再借助了 log 信息, 发现其是因为去寻找 __setstate__ 属性的时候才导致了这种错误.好了,至此,问题就得以描述清楚了.
    之所以调用 Django core cache 导致了调用栈溢出, 是因为 Django 在 cache get 的方法中将存储的数据反序列化成对象,而这个对象在此时还没有生成,且序列化的时候要去调用 __setstate__ 方法, 但是类中没有定义,只是定义了 __getattr__ 方法.而 __getattr__ 方法中又使用了 conf 方法, 这时候 conf 还没有, 所以,又触发了 __getattr__ 方法的执行.如此反复,导致了最终的调用栈溢出现象.
    好了,既然找到问题了,那就解决吧.
    我这里是自己实现了 __getstate__, __setstate__ 的魔术方法,这样,就可以解决了找不到 __setstate__ 的问题. 还有一种解决方法,就是将 conf 定位为 类属性. 这样是从找不到 conf 源头解决问题.
    问题解决,开始总结下 Python 魔术方法.
    __setstate__, __getstate__ 方法在 pickle 序列化和反序列化的时候会触发执行. getattr 是当 object 的某个属性找不到的时候触发执行.
    下面是我模拟的测试代码:

    # coding=utf8
    
    import pickle
    import StringIO
    
    
    class PeopleObject(object):
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def display(self):
            print 'name:', self.name, 'address:', self.age
    
        def __getattr__(self, attr_name):
            if attr_name in self.__dict__:
                return self.__dict__[attr_name]
            else:
                print self.name
    
        def __getstate__(self):
            state = self.__dict__.copy()
            return state
    
        # def __setstate__(self, state):
        #     print state
        #     self.__dict__.update(state)
    
    
    hanmeimei = PeopleObject("Han Meimei", 18)
    hanmeimei.display()
    store_file = StringIO.StringIO()
    
    pickle.dump(hanmeimei, store_file, 0)  # 序列化
    
    # del Person #反序列的时候,必须能找到对应类的定义。否则反序列化操作失败。
    store_file.seek(0)
    hanmeimei_ins = pickle.load(store_file)  # 反序列化
    hanmeimei_ins.display()
    store_file.close()
    
    

    执行会发现,很快就会出现同样的错误.
    关于魔术方法,详见:

  • 相关阅读:
    为什么我们不要 .NET 程序员
    Jquery异步请求数据实例代码
    关系数据库中表的基本属性有哪些
    利用VC从DLL传递消息到EXE
    新实体与原实体之间为一对多关系
    本人C++ Builder开发的仿Windows桌面应用程序源码
    delphi窗体动态设计 在系统运行时动态更改控件属性
    DB.ASP 用Javascript写ASP很灵活很好用很easy
    CrazyScan Satellite scan software 卫星扫描
    delphi中窗体半透明效果如何实现
  • 原文地址:https://www.cnblogs.com/scharfsinnig/p/7405976.html
Copyright © 2011-2022 走看看