[MRG] merge trunk.
[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 import openerp
35 import openerp.netsvc as netsvc
36 import openerp.tiny_socket as tiny_socket
37 import openerp.tools as tools
38
39 _logger = logging.getLogger(__name__)
40
41 class TinySocketClientThread(threading.Thread):
42     def __init__(self, sock, threads):
43         spn = sock and sock.getpeername()
44         spn = 'netrpc-client-%s:%s' % spn[0:2]
45         threading.Thread.__init__(self, name=spn)
46         self.sock = sock
47         # Only at the server side, use a big timeout: close the
48         # clients connection when they're idle for 20min.
49         self.sock.settimeout(1200)
50         self.threads = threads
51
52     def run(self):
53         self.running = True
54         try:
55             ts = tiny_socket.mysocket(self.sock)
56         except Exception:
57             self.threads.remove(self)
58             self.running = False
59             return False
60
61         while self.running:
62             try:
63                 msg = ts.myreceive()
64                 result = netsvc.dispatch_rpc(msg[0], msg[1], msg[2:])
65                 ts.mysend(result)
66             except socket.timeout:
67                 #terminate this channel because other endpoint is gone
68                 break
69             except Exception, e:
70                 try:
71                     valid_exception = Exception(netrpc_handle_exception_legacy(e)) 
72                     valid_traceback = getattr(e, 'traceback', sys.exc_info())
73                     formatted_traceback = "".join(traceback.format_exception(*valid_traceback))
74                     _logger.debug("netrpc: communication-level exception", exc_info=True)
75                     ts.mysend(valid_exception, exception=True, traceback=formatted_traceback)
76                     break
77                 except Exception, ex:
78                     #terminate this channel if we can't properly send back the error
79                     _logger.exception("netrpc: cannot deliver exception message to client")
80                     break
81
82         netsvc.close_socket(self.sock)
83         self.sock = None
84         self.threads.remove(self)
85         self.running = False
86         return True
87
88     def stop(self):
89         self.running = False
90         
91 def netrpc_handle_exception_legacy(e):
92     if isinstance(e, openerp.osv.osv.except_osv):
93         return 'warning -- ' + e.name + '\n\n' + e.value
94     if isinstance(e, openerp.exceptions.Warning):
95         return 'warning -- Warning\n\n' + str(e)
96     if isinstance(e, openerp.exceptions.AccessError):
97         return 'warning -- AccessError\n\n' + str(e)
98     if isinstance(e, openerp.exceptions.AccessDenied):
99         return 'AccessDenied ' + str(e)
100     return openerp.tools.exception_to_unicode(e)
101
102 class TinySocketServerThread(threading.Thread,netsvc.Server):
103     def __init__(self, interface, port, secure=False):
104         threading.Thread.__init__(self, name="NetRPCDaemon-%d"%port)
105         netsvc.Server.__init__(self)
106         self.__port = port
107         self.__interface = interface
108         self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
109         self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
110         self.socket.bind((self.__interface, self.__port))
111         self.socket.listen(5)
112         self.threads = []
113         _logger.info("starting NET-RPC service on %s:%s", interface or '0.0.0.0', port)
114
115     def run(self):
116         try:
117             self.running = True
118             while self.running:
119                 fd_sets = select.select([self.socket], [], [], self._busywait_timeout)
120                 if not fd_sets[0]:
121                     continue
122                 (clientsocket, address) = self.socket.accept()
123                 ct = TinySocketClientThread(clientsocket, self.threads)
124                 clientsocket = None
125                 self.threads.append(ct)
126                 ct.start()
127                 lt = len(self.threads)
128                 if (lt > 10) and (lt % 10 == 0):
129                     # Not many threads should be serving at the same time, so log
130                     # their abuse.
131                     _logger.debug("Netrpc: %d threads", len(self.threads))
132             self.socket.close()
133         except Exception, e:
134             _logger.warning("Netrpc: closing because of exception %s" % str(e))
135             self.socket.close()
136             return False
137
138     def stop(self):
139         self.running = False
140         for t in self.threads:
141             t.stop()
142         self._close_socket()
143
144     def stats(self):
145         res = "Net-RPC: " + ( (self.running and "running") or  "stopped")
146         i = 0
147         for t in self.threads:
148             i += 1
149             res += "\nNet-RPC #%d: %s " % (i, t.name)
150             if t.isAlive():
151                 res += "running"
152             else:
153                 res += "finished"
154             if t.sock:
155                 res += ", socket"
156         return res
157
158 netrpcd = None
159
160 def init_servers():
161     global netrpcd
162     if tools.config.get('netrpc', False):
163         netrpcd = TinySocketServerThread(
164             tools.config.get('netrpc_interface', ''), 
165             int(tools.config.get('netrpc_port', 8070)))
166
167 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: