[IMP] db creation: cleanup logging and exception traceback reification
[odoo/odoo.git] / openerp / service / netrpc_server.py
1 # -*- coding: utf-8 -*-
2
3 #
4 # Copyright P. Christeas <p_christ@hol.gr> 2008,2009
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.     
19 #
20 ##############################################################################
21
22 #.apidoc title: NET-RPC Server
23
24 """ This file contains instance of the net-rpc server
25
26     
27 """
28 import logging
29 import select
30 import socket
31 import sys
32 import threading
33 import traceback
34
35 import openerp.netsvc as netsvc
36 import openerp.tiny_socket as tiny_socket
37 import openerp.tools as tools
38
39 class TinySocketClientThread(threading.Thread, netsvc.OpenERPDispatcher):
40     def __init__(self, sock, threads):
41         spn = sock and sock.getpeername()
42         spn = 'netrpc-client-%s:%s' % spn[0:2]
43         threading.Thread.__init__(self, name=spn)
44         self.sock = sock
45         # Only at the server side, use a big timeout: close the
46         # clients connection when they're idle for 20min.
47         self.sock.settimeout(1200)
48         self.threads = threads
49
50     def run(self):
51         self.running = True
52         try:
53             ts = tiny_socket.mysocket(self.sock)
54         except Exception:
55             self.threads.remove(self)
56             self.running = False
57             return False
58
59         while self.running:
60             try:
61                 msg = ts.myreceive()
62                 result = self.dispatch(msg[0], msg[1], msg[2:])
63                 ts.mysend(result)
64             except socket.timeout:
65                 #terminate this channel because other endpoint is gone
66                 break
67             except netsvc.OpenERPDispatcherException, e:
68                 try:
69                     new_e = Exception(tools.exception_to_unicode(e.exception)) # avoid problems of pickeling
70                     logging.getLogger('web-services').debug("netrpc: rpc-dispatching exception", exc_info=True)
71                     ts.mysend(new_e, exception=True, traceback=e.traceback)
72                 except Exception:
73                     #terminate this channel if we can't properly send back the error
74                     logging.getLogger('web-services').exception("netrpc: cannot deliver exception message to client")
75                     break
76             except Exception, e:
77                 try:
78                     tb = getattr(e, 'traceback', sys.exc_info())
79                     tb_s = "".join(traceback.format_exception(*tb))
80                     logging.getLogger('web-services').debug("netrpc: communication-level exception", exc_info=True)
81                     ts.mysend(e, exception=True, traceback=tb_s)
82                     break
83                 except Exception, ex:
84                     #terminate this channel if we can't properly send back the error
85                     logging.getLogger('web-services').exception("netrpc: cannot deliver exception message to client")
86                     break
87
88         netsvc.close_socket(self.sock)
89         self.sock = None
90         self.threads.remove(self)
91         self.running = False
92         return True
93
94     def stop(self):
95         self.running = False
96
97
98 class TinySocketServerThread(threading.Thread,netsvc.Server):
99     def __init__(self, interface, port, secure=False):
100         threading.Thread.__init__(self, name="NetRPCDaemon-%d"%port)
101         netsvc.Server.__init__(self)
102         self.__port = port
103         self.__interface = interface
104         self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
105         self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
106         self.socket.bind((self.__interface, self.__port))
107         self.socket.listen(5)
108         self.threads = []
109         netsvc.Logger().notifyChannel("web-services", netsvc.LOG_INFO, 
110                          "starting NET-RPC service at %s port %d" % (interface or '0.0.0.0', port,))
111
112     def run(self):
113         try:
114             self.running = True
115             while self.running:
116                 fd_sets = select.select([self.socket], [], [], self._busywait_timeout)
117                 if not fd_sets[0]:
118                     continue
119                 (clientsocket, address) = self.socket.accept()
120                 ct = TinySocketClientThread(clientsocket, self.threads)
121                 clientsocket = None
122                 self.threads.append(ct)
123                 ct.start()
124                 lt = len(self.threads)
125                 if (lt > 10) and (lt % 10 == 0):
126                      # Not many threads should be serving at the same time, so log
127                      # their abuse.
128                      netsvc.Logger().notifyChannel("web-services", netsvc.LOG_DEBUG,
129                         "Netrpc: %d threads" % len(self.threads))
130             self.socket.close()
131         except Exception, e:
132             logging.getLogger('web-services').warning("Netrpc: closing because of exception %s" % str(e))
133             self.socket.close()
134             return False
135
136     def stop(self):
137         self.running = False
138         for t in self.threads:
139             t.stop()
140         self._close_socket()
141
142     def stats(self):
143         res = "Net-RPC: " + ( (self.running and "running") or  "stopped")
144         i = 0
145         for t in self.threads:
146             i += 1
147             res += "\nNet-RPC #%d: %s " % (i, t.name)
148             if t.isAlive():
149                 res += "running"
150             else:
151                 res += "finished"
152             if t.sock:
153                 res += ", socket"
154         return res
155
156 netrpcd = None
157
158 def init_servers():
159     global netrpcd
160     if tools.config.get('netrpc', False):
161         netrpcd = TinySocketServerThread(
162             tools.config.get('netrpc_interface', ''), 
163             int(tools.config.get('netrpc_port', 8070)))