##// END OF EJS Templates
hooks: decrease pool interval to 10ms. For SVN operations and lots of requests...
marcink -
r2426:91d3d681 default
parent child Browse files
Show More
@@ -1,251 +1,251 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import json
22 22 import logging
23 23 import traceback
24 24 import threading
25 25 from BaseHTTPServer import BaseHTTPRequestHandler
26 26 from SocketServer import TCPServer
27 27
28 28 import rhodecode
29 29 from rhodecode.model import meta
30 30 from rhodecode.lib.base import bootstrap_request, bootstrap_config
31 31 from rhodecode.lib import hooks_base
32 32 from rhodecode.lib.utils2 import AttributeDict
33 33
34 34
35 35 log = logging.getLogger(__name__)
36 36
37 37
38 38 class HooksHttpHandler(BaseHTTPRequestHandler):
39 39 def do_POST(self):
40 40 method, extras = self._read_request()
41 41 try:
42 42 result = self._call_hook(method, extras)
43 43 except Exception as e:
44 44 exc_tb = traceback.format_exc()
45 45 result = {
46 46 'exception': e.__class__.__name__,
47 47 'exception_traceback': exc_tb,
48 48 'exception_args': e.args
49 49 }
50 50 self._write_response(result)
51 51
52 52 def _read_request(self):
53 53 length = int(self.headers['Content-Length'])
54 54 body = self.rfile.read(length).decode('utf-8')
55 55 data = json.loads(body)
56 56 return data['method'], data['extras']
57 57
58 58 def _write_response(self, result):
59 59 self.send_response(200)
60 60 self.send_header("Content-type", "text/json")
61 61 self.end_headers()
62 62 self.wfile.write(json.dumps(result))
63 63
64 64 def _call_hook(self, method, extras):
65 65 hooks = Hooks()
66 66 try:
67 67 result = getattr(hooks, method)(extras)
68 68 finally:
69 69 meta.Session.remove()
70 70 return result
71 71
72 72 def log_message(self, format, *args):
73 73 """
74 74 This is an overridden method of BaseHTTPRequestHandler which logs using
75 75 logging library instead of writing directly to stderr.
76 76 """
77 77
78 78 message = format % args
79 79
80 80 # TODO: mikhail: add different log levels support
81 81 log.debug(
82 82 "%s - - [%s] %s", self.client_address[0],
83 83 self.log_date_time_string(), message)
84 84
85 85
86 86 class DummyHooksCallbackDaemon(object):
87 87 def __init__(self):
88 88 self.hooks_module = Hooks.__module__
89 89
90 90 def __enter__(self):
91 91 log.debug('Running dummy hooks callback daemon')
92 92 return self
93 93
94 94 def __exit__(self, exc_type, exc_val, exc_tb):
95 95 log.debug('Exiting dummy hooks callback daemon')
96 96
97 97
98 98 class ThreadedHookCallbackDaemon(object):
99 99
100 100 _callback_thread = None
101 101 _daemon = None
102 102 _done = False
103 103
104 104 def __init__(self):
105 105 self._prepare()
106 106
107 107 def __enter__(self):
108 108 self._run()
109 109 return self
110 110
111 111 def __exit__(self, exc_type, exc_val, exc_tb):
112 112 log.debug('Callback daemon exiting now...')
113 113 self._stop()
114 114
115 115 def _prepare(self):
116 116 raise NotImplementedError()
117 117
118 118 def _run(self):
119 119 raise NotImplementedError()
120 120
121 121 def _stop(self):
122 122 raise NotImplementedError()
123 123
124 124
125 125 class HttpHooksCallbackDaemon(ThreadedHookCallbackDaemon):
126 126 """
127 127 Context manager which will run a callback daemon in a background thread.
128 128 """
129 129
130 130 hooks_uri = None
131 131
132 132 IP_ADDRESS = '127.0.0.1'
133 133
134 134 # From Python docs: Polling reduces our responsiveness to a shutdown
135 135 # request and wastes cpu at all other times.
136 POLL_INTERVAL = 0.1
136 POLL_INTERVAL = 0.01
137 137
138 138 def _prepare(self):
139 139 log.debug("Preparing HTTP callback daemon and registering hook object")
140 140
141 141 self._done = False
142 142 self._daemon = TCPServer((self.IP_ADDRESS, 0), HooksHttpHandler)
143 143 _, port = self._daemon.server_address
144 144 self.hooks_uri = '{}:{}'.format(self.IP_ADDRESS, port)
145 145
146 146 log.debug("Hooks uri is: %s", self.hooks_uri)
147 147
148 148 def _run(self):
149 149 log.debug("Running event loop of callback daemon in background thread")
150 150 callback_thread = threading.Thread(
151 151 target=self._daemon.serve_forever,
152 152 kwargs={'poll_interval': self.POLL_INTERVAL})
153 153 callback_thread.daemon = True
154 154 callback_thread.start()
155 155 self._callback_thread = callback_thread
156 156
157 157 def _stop(self):
158 158 log.debug("Waiting for background thread to finish.")
159 159 self._daemon.shutdown()
160 160 self._callback_thread.join()
161 161 self._daemon = None
162 162 self._callback_thread = None
163 163 log.debug("Background thread done.")
164 164
165 165
166 166 def prepare_callback_daemon(extras, protocol, use_direct_calls):
167 167 callback_daemon = None
168 168
169 169 if use_direct_calls:
170 170 callback_daemon = DummyHooksCallbackDaemon()
171 171 extras['hooks_module'] = callback_daemon.hooks_module
172 172 else:
173 173 if protocol == 'http':
174 174 callback_daemon = HttpHooksCallbackDaemon()
175 175 else:
176 176 log.error('Unsupported callback daemon protocol "%s"', protocol)
177 177 raise Exception('Unsupported callback daemon protocol.')
178 178
179 179 extras['hooks_uri'] = callback_daemon.hooks_uri
180 180 extras['hooks_protocol'] = protocol
181 181
182 182 log.debug('Prepared a callback daemon: %s', callback_daemon)
183 183 return callback_daemon, extras
184 184
185 185
186 186 class Hooks(object):
187 187 """
188 188 Exposes the hooks for remote call backs
189 189 """
190 190
191 191 def repo_size(self, extras):
192 192 log.debug("Called repo_size of %s object", self)
193 193 return self._call_hook(hooks_base.repo_size, extras)
194 194
195 195 def pre_pull(self, extras):
196 196 log.debug("Called pre_pull of %s object", self)
197 197 return self._call_hook(hooks_base.pre_pull, extras)
198 198
199 199 def post_pull(self, extras):
200 200 log.debug("Called post_pull of %s object", self)
201 201 return self._call_hook(hooks_base.post_pull, extras)
202 202
203 203 def pre_push(self, extras):
204 204 log.debug("Called pre_push of %s object", self)
205 205 return self._call_hook(hooks_base.pre_push, extras)
206 206
207 207 def post_push(self, extras):
208 208 log.debug("Called post_push of %s object", self)
209 209 return self._call_hook(hooks_base.post_push, extras)
210 210
211 211 def _call_hook(self, hook, extras):
212 212 extras = AttributeDict(extras)
213 213 server_url = extras['server_url']
214 214 request = bootstrap_request(application_url=server_url)
215 215
216 216 bootstrap_config(request) # inject routes and other interfaces
217 217
218 218 # inject the user for usage in hooks
219 219 request.user = AttributeDict({'username': extras.username,
220 220 'ip_addr': extras.ip,
221 221 'user_id': extras.user_id})
222 222
223 223 extras.request = request
224 224
225 225 try:
226 226 result = hook(extras)
227 227 except Exception as error:
228 228 exc_tb = traceback.format_exc()
229 229 log.exception('Exception when handling hook %s', hook)
230 230 error_args = error.args
231 231 return {
232 232 'status': 128,
233 233 'output': '',
234 234 'exception': type(error).__name__,
235 235 'exception_traceback': exc_tb,
236 236 'exception_args': error_args,
237 237 }
238 238 finally:
239 239 meta.Session.remove()
240 240
241 241 log.debug('Got hook call response %s', result)
242 242 return {
243 243 'status': result.status,
244 244 'output': result.output,
245 245 }
246 246
247 247 def __enter__(self):
248 248 return self
249 249
250 250 def __exit__(self, exc_type, exc_val, exc_tb):
251 251 pass
General Comments 0
You need to be logged in to leave comments. Login now