17.odoo入门——初探后台启动过程(四)

第17天——————————

因为odoo服务器有三种模式——prefork, gevented, threaded,首先得大致了解一下这三种MPM(Multi-Processing Module,多进程处理模块)模式的特点,可以参考一篇关于apache服务器的博文:    http://blog.jobbole.com/91920/

读了这篇博文之后大致了解了这三种模式的区别和优缺点。

接下来看到我们的源代码,由于GeventServerPreforkServerThreadedServer三个类都继承于CommonServer类,其源代码如下:

class CommonServer(object):
    def __init__(self, app):
        # TODO Change the xmlrpc_* options to http_*
        self.app = app  #传入的参数是 odoo.service.wsgi_server.application ,先搁置着
        # config
        self.interface = config['xmlrpc_interface'] or '0.0.0.0' #在我的配置文件中为空
        self.port = config['xmlrpc_port’] #在配置文件中可以查看到,xmlrpc_port = 8069
        # runtime
        self.pid = os.getpid()

    def close_socket(self, sock):  #反正就是关闭套接字啰
        """ Closes a socket instance cleanly
        :param sock: the network socket to close
        :type sock: socket.socket
        """
        try:
            sock.shutdown(socket.SHUT_RDWR)
        except socket.error, e:
            if e.errno == errno.EBADF:
                # Werkzeug > 0.9.6 closes the socket itself (see commit
                # https://github.com/mitsuhiko/werkzeug/commit/4d8ca089)
                return
            # On OSX, socket shutdowns both sides if any side closes it
            # causing an error 57 'Socket is not connected' on shutdown
            # of the other side (or something), see
            # http://bugs.python.org/issue4397
            # note: stdlib fixed test, not behavior
            if e.errno != errno.ENOTCONN or platform.system() not in ['Darwin', 'Windows']:
                raise
        sock.close()

由于我的配置中使用的是threaded,那么看到ThreadedServer类:

首先这个类里面有比较多的函数,这里不贴一整段代码,而是一个函数一个函数去查看源码,首先要了解signal handler的概念,其实,要看懂几个常用的结束信号,可以参考:

http://blog.csdn.net/yikai2009/article/details/8643818  linux下的几个信号。 而cron是linux下的一个定时执行工具,大概是可以定时分配cpu给每个线程吧——自行百度吧

linux下的spawn,参考——http://blog.csdn.net/ysdaniel/article/details/7059511

Import threading #这个模块中导入了这个库,python内置的多线程处理的库

类的构造函数:

def __init__(self, app):
    super(ThreadedServer, self).__init__(app)
    self.main_thread_id = threading.currentThread().ident
    # Variable keeping track of the number of calls to the signal handler defined
    # below. This variable is monitored by ``quit_on_signals()``.
    self.quit_signals_received = 0 #统计收到的退出信号

    #self.socket = None
    self.httpd = None

信号处理函数:

def signal_handler(self, sig, frame): #应该就是收到2次结束信号就退出
    if sig in [signal.SIGINT, signal.SIGTERM]:
        # shutdown on kill -INT or -TERM
        self.quit_signals_received += 1
        if self.quit_signals_received > 1:
            # logging.shutdown was already called at this point.
            sys.stderr.write("Forced shutdown.\n")
            os._exit(0)
    elif sig == signal.SIGHUP:
        # restart on kill -HUP
        odoo.phoenix = True
        self.quit_signals_received += 1

再挑顺序看到ThreadedServer类的start函数:

def start(self, stop=False):
    _logger.debug("Setting signal handlers")  #就是这里了,控制台每次启动都会出的一条语句来自这里
    if os.name == 'posix':  #对linux定义几个声明
        signal.signal(signal.SIGINT, self.signal_handler)
        signal.signal(signal.SIGTERM, self.signal_handler)
        signal.signal(signal.SIGCHLD, self.signal_handler)
        signal.signal(signal.SIGHUP, self.signal_handler)
        signal.signal(signal.SIGQUIT, dumpstacks)
        signal.signal(signal.SIGUSR1, log_ormcache_stats)
    elif os.name == 'nt':
        import win32api
        win32api.SetConsoleCtrlHandler(lambda sig: self.signal_handler(sig, None), 1)

    test_mode = config['test_enable'] or config['test_file']
    if test_mode or (config['xmlrpc'] and not stop):
        # some tests need the http deamon to be available...
        self.http_spawn() #执行了这条语句

    if not stop:
        # only relevant if we are not in "--stop-after-init" mode
        self.cron_spawn()

由输出调试后可知,我们进入了self.http_spawn()这条语句,

看到ThreadedServer下的这个函数:

def http_spawn(self):
    t = threading.Thread(target=self.http_thread, name="odoo.service.httpd") #参考 http://python.jobbole.com/81546/
	#参数target是一个可调用对象(也称为活动[activity]),在线程启动后执行;
    t.setDaemon(True)
    t.start()
    _logger.info('HTTP service (werkzeug) running on %s:%s', self.interface, self.port)
	#每次启动都会看到的这句话,原来输出是来自于这里
再看到http_thread这个东西,它来自于 ThreadedServer 类下的这个函数:

def http_thread(self):
    def app(e, s):
        return self.app(e, s)
    self.httpd = ThreadedWSGIServerReloadable(self.interface, self.port, app)
    self.httpd.serve_forever()

ThreadedWSGIServerReloadable的大致源代码为:

class ThreadedWSGIServerReloadable(LoggingBaseWSGIServerMixIn, werkzeug.serving.ThreadedWSGIServer):
    """ werkzeug Threaded WSGI Server patched to allow reusing a listen socket
    given by the environement, this is used by autoreload to keep the listen
    socket open when a reload happens.
    """
    def __init__(self, host, port, app):
        super(ThreadedWSGIServerReloadable, self).__init__(host, port, app,
                                                           handler=RequestHandler)
….若干函数

这个暂时不太了解werkzeug.serving.ThreadedWSGIServer,但是大致知道应该是个处理httpweb服务器程序,那么到此我们就大概了解了ThreadedServer类的启动和运行过程了。这里接触到的一个知识点就是,ThreadedServer类通过接受信号来知道自己是否被kill——这也算是进程间的通讯吧。

看到启动过程中控制台的最后一句输出:

INFO ? odoo.service.server: HTTP service (werkzeug) running on 0.0.0.0:8069
那么也就是说 http 服务器是跟 ThreadedServer 类下的 http_thread ,也就是 http_thread 这个函数啦。

由自己输出调试可知,接下来执行了

def cron_spawn(self):
    """ Start the above runner function in a daemon thread.

    The thread is a typical daemon thread: it will never quit and must be
    terminated when the main process exits - with no consequence (the processing
    threads it spawns are not marked daemon).

    """
    # Force call to strptime just before starting the cron thread
    # to prevent time.strptime AttributeError within the thread.
    # See: http://bugs.python.org/issue7980
    datetime.datetime.strptime('2012-01-01', '%Y-%m-%d')
    for i in range(odoo.tools.config['max_cron_threads']):
        def target():
            self.cron_thread(i)
        #print "one”#这是我自己的输出调试
        t = threading.Thread(target=target, name="odoo.service.cron.cron%d" % i)
        t.setDaemon(True)
        t.start()
        _logger.debug("cron%d started!" % i)

这里是根据配置文件中的参数max_cron_threads开启了若干个线程,线程启动后运行的便是cron_thread函数,看到这个函数源码:

def cron_thread(self, number):
    while True:
        time.sleep(SLEEP_INTERVAL + number)     # Steve Reich timing style
        registries = odoo.modules.registry.Registry.registries
        _logger.debug('cron%d polling for jobs', number)
        for db_name, registry in registries.iteritems():
            while registry.ready:
                try:
                    acquired = odoo.addons.base.ir.ir_cron.ir_cron._acquire_job(db_name)
                    if not acquired:
                        break
                except Exception:
                    _logger.warning('cron%d encountered an Exception:', number, exc_info=True)
                    break

大概就是先睡眠一定的时间,睡眠完成后变成就绪状态,就可以等待工作的到来而获得cpu.不过从我的输出调试来看,在我不进行任何操作的时候,多个线程轮流切换,但是也没有进行执行任何有意义的行动,因为acquired总是空,而且目前我也还没找到odoo.addons.base.ir.ir_cron.ir_cron._acquire_job,那么这个问题暂且搁置

下一步要探讨的地方应该是 werkzeug.serving.ThreadedWSGIServer这样的一个http服务器   和  多线程具体执行的工作, 以及在我们打开8069这个网页的时候后台都做了些什么工作。


版权声明:本文为qq_33826977原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。