odoo17后台启动过程3——三种server

文件位置:odoo\service\server.py

1、三种server:

odoo17后台启动过程3——三种server_第1张图片

1.1、Threaded

这是Odoo默认的选项,线程模式,我们知道python的多线程并不是真正的多线程,所以,这种模式下,并发性能较低,也无法利用多核的优势。 优点是比较安全,兼容型号,如果对并发要求不高,这种模式是没有问题的。这也是odoo默认的模式。

1.2、Gevented

利用了python协程,需要预先安装Gevent库,提高了并发西性能,但是安全性和兼容性可能不太好,需要多做测试。
我尝试在windows下通过下列命令启动odoo,但是老是报错, 暂时不去管它了

python odoo-bin gevent -c odoo.conf

1.3、Prefork

可以充分利用多核处理器,它其实是一种多进程模式,由多个进程来处理web请求。

这三种网关有一个共同的父类CommonServer


class CommonServer(object):
    _on_stop_funcs = []

    def __init__(self, app):
        self.app = app
        # config
        self.interface = config['http_interface'] or '0.0.0.0'
        self.port = config['http_port']
        # 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 as 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()

    @classmethod
    def on_stop(cls, func):
        """ Register a cleanup function to be executed when the server stops """
        cls._on_stop_funcs.append(func)

    def stop(self):
        for func in type(self)._on_stop_funcs:
            try:
                _logger.debug("on_close call %s", func)
                func()
            except Exception:
                _logger.warning("Exception in %s", func.__name__, exc_info=True)

1、构造函数需要传入一个app
2、提供了几个关闭服务的方法

2、start函数

这是一个入口函数,cli.server 就是通过调用这个函数来启动服务

odoo.service.server.start(preload=preload, stop=stop)

看看它的代码


def start(preload=None, stop=False):
    """ Start the odoo http server and cron processor.
    """
    global server

    load_server_wide_modules()

    if odoo.evented:
        server = GeventServer(odoo.http.root)
    elif config['workers']:
        if config['test_enable'] or config['test_file']:
            _logger.warning("Unit testing in workers mode could fail; use --workers 0.")

        server = PreforkServer(odoo.http.root)

        # Workaround for Python issue24291, fixed in 3.6 (see Python issue26721)
        if sys.version_info[:2] == (3,5):
            # turn on buffering also for wfile, to avoid partial writes (Default buffer = 8k)
            werkzeug.serving.WSGIRequestHandler.wbufsize = -1
    else:
        if platform.system() == "Linux" and sys.maxsize > 2**32 and "MALLOC_ARENA_MAX" not in os.environ:
            # glibc's malloc() uses arenas [1] in order to efficiently handle memory allocation of multi-threaded
            # applications. This allows better memory allocation handling in case of multiple threads that
            # would be using malloc() concurrently [2].
            # Due to the python's GIL, this optimization have no effect on multithreaded python programs.
            # Unfortunately, a downside of creating one arena per cpu core is the increase of virtual memory
            # which Odoo is based upon in order to limit the memory usage for threaded workers.
            # On 32bit systems the default size of an arena is 512K while on 64bit systems it's 64M [3],
            # hence a threaded worker will quickly reach it's default memory soft limit upon concurrent requests.
            # We therefore set the maximum arenas allowed to 2 unless the MALLOC_ARENA_MAX env variable is set.
            # Note: Setting MALLOC_ARENA_MAX=0 allow to explicitly set the default glibs's malloc() behaviour.
            #
            # [1] https://sourceware.org/glibc/wiki/MallocInternals#Arenas_and_Heaps
            # [2] https://www.gnu.org/software/libc/manual/html_node/The-GNU-Allocator.html
            # [3] https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=00ce48c;hb=0a8262a#l862
            try:
                import ctypes
                libc = ctypes.CDLL("libc.so.6")
                M_ARENA_MAX = -8
                assert libc.mallopt(ctypes.c_int(M_ARENA_MAX), ctypes.c_int(2))
            except Exception:
                _logger.warning("Could not set ARENA_MAX through mallopt()")
        server = ThreadedServer(odoo.http.root)

    watcher = None
    if 'reload' in config['dev_mode'] and not odoo.evented:
        if inotify:
            watcher = FSWatcherInotify()
            watcher.start()
        elif watchdog:
            watcher = FSWatcherWatchdog()
            watcher.start()
        else:
            if os.name == 'posix' and platform.system() != 'Darwin':
                module = 'inotify'
            else:
                module = 'watchdog'
            _logger.warning("'%s' module not installed. Code autoreload feature is disabled", module)

    rc = server.run(preload, stop)

    if watcher:
        watcher.stop()
    # like the legend of the phoenix, all ends with beginnings
    if getattr(odoo, 'phoenix', False):
        _reexec()

    return rc if rc else 0

1、调用了load_server_wide_modules函数,实际上就是加载base和web两个模块。
2、根据配置和命令行参数来决定启动哪一种服务,并通过odoo.http.rootd对象来初始化server,三种网关都是用这个对象来初始化化

server = ThreadedServer(odoo.http.root)

3、调用server的run方法启动服务

rc = server.run(preload, stop)

3、class ThreadedServer(CommonServer)

先看看run这个入口方法:

    def run(self, preload=None, stop=False):
        """ Start the http server and the cron thread then wait for a signal.

        The first SIGINT or SIGTERM signal will initiate a graceful shutdown while
        a second one if any will force an immediate exit.
        """
        self.start(stop=stop)

从注释中可以看出,该函数启动http server 和cron线程,然后就等待信号。

3.1 启动httpserver

在run方法中调用了 self.start(stop=stop) 来启动http服务。
在start方法中首先绑定了信号处理函数,然后调用了http_spawn 方法。

    def start(self, stop=False):
        _logger.debug("Setting signal handlers")
        set_limit_memory_hard()
        if os.name == 'posix':
            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.SIGXCPU, 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['http_enable'] and not stop):
            # some tests need the http daemon to be available...
            self.http_spawn()

http_spawn启动了一个后台线程,线程的执行函数是http_thread,线程名称叫odoo.service.httpd

        t = threading.Thread(target=self.http_thread, name="odoo.service.httpd")
        t.daemon = True
        t.start()

而http_thread 启动了一个ThreadedWSGIServerReloadable服务,并且调用了它的serve_forever方法,这估计是一个死循环,一直等待web请求。

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

到这里http服务线程就启动好了.

3.2 启动cron线程

server的run方法中还需要启动后台服务线程,一般是2个

        self.cron_spawn()

这个代码比较简单,根据max_cron_threads 参数来启动后台任务线程。

    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)
            t = threading.Thread(target=target, name="odoo.service.cron.cron%d" % i)
            t.daemon = True
            t.type = 'cron'
            t.start()
            _logger.debug("cron%d started!" % i)

到这里,一个主线程,一个http服务线程和两个cron线程就启动好了
odoo17后台启动过程3——三种server_第2张图片

你可能感兴趣的:(odoo17后端,odoo,后端,odoo17)