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