文件位置: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
# 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线程就启动好了