[MERGE] latest 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
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                     new_e = Exception(tools.exception_to_unicode(e)) # avoid problems of pickeling
70                     tb = getattr(e, 'traceback', sys.exc_info())
71                     tb_s = "".join(traceback.format_exception(*tb))
72                     logging.getLogger('web-services').debug("netrpc: communication-level exception", exc_info=True)
73                     ts.mysend(new_e, exception=True, traceback=tb_s)
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
90 class TinySocketServerThread(threading.Thread,netsvc.Server):
91     def __init__(self, interface, port, secure=False):
92         threading.Thread.__init__(self, name="NetRPCDaemon-%d"%port)
93         netsvc.Server.__init__(self)
94         self.__port = port
95         self.__interface = interface
96         self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
97         self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
98         self.socket.bind((self.__interface, self.__port))
99         self.socket.listen(5)
100         self.threads = []
101         netsvc.Logger().notifyChannel("web-services", netsvc.LOG_INFO, 
102                          "starting NET-RPC service on %s:%s" % (interface or '0.0.0.0', port,))
103
104     def run(self):
105         try:
106             self.running = True
107             while self.running:
108                 fd_sets = select.select([self.socket], [], [], self._busywait_timeout)
109                 if not fd_sets[0]:
110                     continue
111                 (clientsocket, address) = self.socket.accept()
112                 ct = TinySocketClientThread(clientsocket, self.threads)
113                 clientsocket = None
114                 self.threads.append(ct)
115                 ct.start()
116                 lt = len(self.threads)
117                 if (lt > 10) and (lt % 10 == 0):
118                      # Not many threads should be serving at the same time, so log
119                      # their abuse.
120                      netsvc.Logger().notifyChannel("web-services", netsvc.LOG_DEBUG,
121                         "Netrpc: %d threads" % len(self.threads))
122             self.socket.close()
123         except Exception, e:
124             logging.getLogger('web-services').warning("Netrpc: closing because of exception %s" % str(e))
125             self.socket.close()
126             return False
127
128     def stop(self):
129         self.running = False
130         for t in self.threads:
131             t.stop()
132         self._close_socket()
133
134     def stats(self):
135         res = "Net-RPC: " + ( (self.running and "running") or  "stopped")
136         i = 0
137         for t in self.threads:
138             i += 1
139             res += "\nNet-RPC #%d: %s " % (i, t.name)
140             if t.isAlive():
141                 res += "running"
142             else:
143                 res += "finished"
144             if t.sock:
145                 res += ", socket"
146         return res
147
148 netrpcd = None
149
150 def init_servers():
151     global netrpcd
152     if tools.config.get('netrpc', False):
153         netrpcd = TinySocketServerThread(
154             tools.config.get('netrpc_interface', ''), 
155             int(tools.config.get('netrpc_port', 8070)))
156
157 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: