websrv_lib: one case of cleaner exit.
[odoo/odoo.git] / bin / 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 """ This file contains instance of the net-rpc server
23
24     
25 """
26 import logging
27 import select
28 import socket
29 import sys
30 import threading
31 import traceback
32
33 import netsvc
34 import tiny_socket
35 import tools
36
37 class TinySocketClientThread(threading.Thread, netsvc.OpenERPDispatcher):
38     def __init__(self, sock, threads):
39         threading.Thread.__init__(self)
40         self.sock = sock
41         # Only at the server side, use a big timeout: close the
42         # clients connection when they're idle for 20min.
43         self.sock.settimeout(1200)
44         self.threads = threads
45
46     def __del__(self):
47         if self.sock:
48             try:
49                 self.socket.shutdown(
50                     getattr(socket, 'SHUT_RDWR', 2))
51             except Exception: pass
52             # That should garbage-collect and close it, too
53             self.sock = None
54
55     def run(self):
56         self.running = True
57         try:
58             ts = tiny_socket.mysocket(self.sock)
59         except Exception:
60             self.threads.remove(self)
61             self.running = False
62             return False
63
64         while self.running:
65             try:
66                 msg = ts.myreceive()
67                 result = self.dispatch(msg[0], msg[1], msg[2:])
68                 ts.mysend(result)
69             except socket.timeout:
70                 #terminate this channel because other endpoint is gone
71                 break
72             except netsvc.OpenERPDispatcherException, e:
73                 try:
74                     new_e = Exception(tools.exception_to_unicode(e.exception)) # avoid problems of pickeling
75                     logging.getLogger('web-services').debug("netrpc: rpc-dispatching exception", exc_info=True)
76                     ts.mysend(new_e, exception=True, traceback=e.traceback)
77                 except Exception:
78                     #terminate this channel if we can't properly send back the error
79                     logging.getLogger('web-services').exception("netrpc: cannot deliver exception message to client")
80                     break
81             except Exception, e:
82                 try:
83                     tb = getattr(e, 'traceback', sys.exc_info())
84                     tb_s = "".join(traceback.format_exception(*tb))
85                     logging.getLogger('web-services').debug("netrpc: communication-level exception", exc_info=True)
86                     ts.mysend(e, exception=True, traceback=tb_s)
87                 except Exception, ex:
88                     #terminate this channel if we can't properly send back the error
89                     logging.getLogger('web-services').exception("netrpc: cannot deliver exception message to client")
90                     break
91
92         self.threads.remove(self)
93         self.running = False
94         return True
95
96     def stop(self):
97         self.running = False
98
99
100 class TinySocketServerThread(threading.Thread,netsvc.Server):
101     def __init__(self, interface, port, secure=False):
102         threading.Thread.__init__(self, name="Net-RPC socket")
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 at %s port %d" % (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)))