[MERGE] lp:912793 (account/demo: fix last day of February period)
[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 class TinySocketClientThread(threading.Thread):
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 = netsvc.dispatch_rpc(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 Exception, e:
68                 try:
69                     valid_exception = Exception(netrpc_handle_exception_legacy(e)) 
70                     valid_traceback = getattr(e, 'traceback', sys.exc_info())
71                     formatted_traceback = "".join(traceback.format_exception(*valid_traceback))
72                     logging.getLogger('web-services').debug("netrpc: communication-level exception", exc_info=True)
73                     ts.mysend(valid_exception, exception=True, traceback=formatted_traceback)
74                     break
75                 except Exception, ex:
76                     #terminate this channel if we can't properly send back the error
77                     logging.getLogger('web-services').exception("netrpc: cannot deliver exception message to client")
78                     break
79
80         netsvc.close_socket(self.sock)
81         self.sock = None
82         self.threads.remove(self)
83         self.running = False
84         return True
85
86     def stop(self):
87         self.running = False
88         
89 def netrpc_handle_exception_legacy(e):
90     if isinstance(e, openerp.osv.osv.except_osv):
91         return 'warning -- ' + e.name + '\n\n' + e.value
92     if isinstance(e, openerp.exceptions.Warning):
93         return 'warning -- Warning\n\n' + str(e)
94     if isinstance(e, openerp.exceptions.AccessError):
95         return 'warning -- AccessError\n\n' + str(e)
96     if isinstance(e, openerp.exceptions.AccessDenied):
97         return 'AccessDenied ' + str(e)
98     return openerp.tools.exception_to_unicode(e)
99
100 class TinySocketServerThread(threading.Thread,netsvc.Server):
101     def __init__(self, interface, port, secure=False):
102         threading.Thread.__init__(self, name="NetRPCDaemon-%d"%port)
103         netsvc.Server.__init__(self)
104         self.__port = port
105         self.__interface = interface
106         self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
107         self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
108         self.socket.bind((self.__interface, self.__port))
109         self.socket.listen(5)
110         self.threads = []
111         netsvc.Logger().notifyChannel("web-services", netsvc.LOG_INFO, 
112                          "starting NET-RPC service on %s:%s" % (interface or '0.0.0.0', port,))
113
114     def run(self):
115         try:
116             self.running = True
117             while self.running:
118                 fd_sets = select.select([self.socket], [], [], self._busywait_timeout)
119                 if not fd_sets[0]:
120                     continue
121                 (clientsocket, address) = self.socket.accept()
122                 ct = TinySocketClientThread(clientsocket, self.threads)
123                 clientsocket = None
124                 self.threads.append(ct)
125                 ct.start()
126                 lt = len(self.threads)
127                 if (lt > 10) and (lt % 10 == 0):
128                      # Not many threads should be serving at the same time, so log
129                      # their abuse.
130                      netsvc.Logger().notifyChannel("web-services", netsvc.LOG_DEBUG,
131                         "Netrpc: %d threads" % len(self.threads))
132             self.socket.close()
133         except Exception, e:
134             logging.getLogger('web-services').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: