文件位置:odoo\service\server.py
1、三种server:
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# configself.interface = config['http_interface'] or '0.0.0.0'self.port = config['http_port']# runtimeself.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 behaviorif e.errno != errno.ENOTCONN or platform.system() not in ['Darwin', 'Windows']:raisesock.close()@classmethoddef 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 serverload_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 = -1else: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#l862try:import ctypeslibc = ctypes.CDLL("libc.so.6")M_ARENA_MAX = -8assert 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 = Noneif '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 beginningsif 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 whilea 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 win32apiwin32api.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 = Truet.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 beterminated when the main process exits - with no consequence (the processingthreads 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/issue7980datetime.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 = Truet.type = 'cron't.start()_logger.debug("cron%d started!" % i)
到这里,一个主线程,一个http服务线程和两个cron线程就启动好了