Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(351)

Side by Side Diff: gm/rebaseline_server/server.py

Issue 64273011: rebaseline_server: clean up thread locks (Closed) Base URL: http://skia.googlecode.com/svn/trunk/
Patch Set: Created 7 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | tools/svn.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/python 1 #!/usr/bin/python
2 2
3 """ 3 """
4 Copyright 2013 Google Inc. 4 Copyright 2013 Google Inc.
5 5
6 Use of this source code is governed by a BSD-style license that can be 6 Use of this source code is governed by a BSD-style license that can be
7 found in the LICENSE file. 7 found in the LICENSE file.
8 8
9 HTTP server for our HTML rebaseline viewer. 9 HTTP server for our HTML rebaseline viewer.
10 """ 10 """
(...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after
99 reload_seconds: polling interval with which to check for new results; 99 reload_seconds: polling interval with which to check for new results;
100 if 0, don't check for new results at all 100 if 0, don't check for new results at all
101 """ 101 """
102 self._actuals_dir = actuals_dir 102 self._actuals_dir = actuals_dir
103 self._expectations_dir = expectations_dir 103 self._expectations_dir = expectations_dir
104 self._port = port 104 self._port = port
105 self._export = export 105 self._export = export
106 self._editable = editable 106 self._editable = editable
107 self._reload_seconds = reload_seconds 107 self._reload_seconds = reload_seconds
108 108
109 self._actuals_repo = svn.Svn(actuals_dir)
epoger 2013/11/14 20:21:56 Now, the self._actuals_repo object is shared betwe
110 if not os.path.isdir(actuals_dir):
111 os.makedirs(actuals_dir)
112 self._actuals_repo.Checkout(ACTUALS_SVN_REPO, '.')
113
114 # We only update the expectations dir if the server was run with a
115 # nonzero --reload argument; otherwise, we expect the user to maintain
116 # her own expectations as she sees fit.
117 #
118 # TODO(epoger): Use git instead of svn to check out expectations, since
119 # the Skia repo is moving to git.
120 self._expectations_repo = None
121 if reload_seconds:
122 self._expectations_repo = svn.Svn(expectations_dir)
123 if not os.path.isdir(expectations_dir):
jcgregorio 2013/11/18 16:50:35 Duplicated code from above, break out into a funct
epoger 2013/11/20 19:51:34 Fixed in patchset 2.
124 os.makedirs(expectations_dir)
125 self._expectations_repo.Checkout(EXPECTATIONS_SVN_REPO, '.')
126
109 def is_exported(self): 127 def is_exported(self):
110 """ Returns true iff HTTP clients on other hosts are allowed to access 128 """ Returns true iff HTTP clients on other hosts are allowed to access
111 this server. """ 129 this server. """
112 return self._export 130 return self._export
113 131
114 def is_editable(self): 132 def is_editable(self):
115 """ Returns true iff HTTP clients are allowed to submit new baselines. """ 133 """ Returns true iff HTTP clients are allowed to submit new baselines. """
116 return self._editable 134 return self._editable
117 135
118 def reload_seconds(self): 136 def reload_seconds(self):
119 """ Returns the result reload period in seconds, or 0 if we don't reload 137 """ Returns the result reload period in seconds, or 0 if we don't reload
120 results. """ 138 results. """
121 return self._reload_seconds 139 return self._reload_seconds
122 140
123 def update_results(self): 141 def update_results(self):
124 """ Create or update self.results, based on the expectations in 142 """ Create or update self.results, based on the expectations in
125 self._expectations_dir and the latest actuals from skia-autogen. 143 self._expectations_dir and the latest actuals from skia-autogen.
126 """ 144 """
127 with self._svn_update_lock: 145 logging.info('Updating actual GM results in %s from SVN repo %s ...' % (
128 # self._svn_update_lock prevents us from updating the actual GM results 146 self._actuals_dir, ACTUALS_SVN_REPO))
129 # in multiple threads simultaneously 147 self._actuals_repo.Update('.')
130 logging.info('Updating actual GM results in %s from SVN repo %s ...' % ( 148
131 self._actuals_dir, ACTUALS_SVN_REPO)) 149 if self._expectations_repo:
132 actuals_repo = svn.Svn(self._actuals_dir) 150 logging.info(
133 if not os.path.isdir(self._actuals_dir): 151 'Updating expected GM results in %s from SVN repo %s ...' % (
134 os.makedirs(self._actuals_dir) 152 self._expectations_dir, EXPECTATIONS_SVN_REPO))
135 actuals_repo.Checkout(ACTUALS_SVN_REPO, '.') 153 self._expectations_repo.Update('.')
136 else:
137 actuals_repo.Update('.')
138 # We only update the expectations dir if the server was run with a
139 # nonzero --reload argument; otherwise, we expect the user to maintain
140 # her own expectations as she sees fit.
141 #
142 # self._svn_update_lock prevents us from updating the expected GM results
143 # in multiple threads simultaneously
144 #
145 # TODO(epoger): Use git instead of svn to check out expectations, since
146 # the Skia repo is moving to git.
147 if self._reload_seconds:
148 logging.info(
149 'Updating expected GM results in %s from SVN repo %s ...' % (
150 self._expectations_dir, EXPECTATIONS_SVN_REPO))
151 expectations_repo = svn.Svn(self._expectations_dir)
152 if not os.path.isdir(self._expectations_dir):
153 os.makedirs(self._expectations_dir)
154 expectations_repo.Checkout(EXPECTATIONS_SVN_REPO, '.')
155 else:
156 expectations_repo.Update('.')
157 # end of "with self._svn_update_lock:"
158 154
159 logging.info( 155 logging.info(
160 ('Parsing results from actuals in %s and expectations in %s, ' 156 ('Parsing results from actuals in %s and expectations in %s, '
161 + 'and generating pixel diffs (may take a while) ...') % ( 157 + 'and generating pixel diffs (may take a while) ...') % (
162 self._actuals_dir, self._expectations_dir)) 158 self._actuals_dir, self._expectations_dir))
163 new_results = results.Results( 159 self.results = results.Results(
epoger 2013/11/14 20:21:56 I *think* this is fine, because I assume self.resu
jcgregorio 2013/11/18 16:50:35 Yes, assignment won't happen until the RHS is eval
164 actuals_root=self._actuals_dir, 160 actuals_root=self._actuals_dir,
165 expected_root=self._expectations_dir, 161 expected_root=self._expectations_dir,
166 generated_images_root=GENERATED_IMAGES_ROOT) 162 generated_images_root=GENERATED_IMAGES_ROOT)
167 163
168 # Make sure we don't update self.results while a client is in the middle
169 # of reading from it.
170 with self.results_lock:
171 self.results = new_results
172
173 def _result_reloader(self): 164 def _result_reloader(self):
174 """ If --reload argument was specified, reload results at the appropriate 165 """ If --reload argument was specified, reload results at the appropriate
175 interval. 166 interval.
176 """ 167 """
177 while self._reload_seconds: 168 while self._reload_seconds:
178 time.sleep(self._reload_seconds) 169 time.sleep(self._reload_seconds)
179 self.update_results() 170 self.update_results()
180 171
181 def run(self): 172 def run(self):
182 self.results_lock = thread.allocate_lock()
183 self._svn_update_lock = thread.allocate_lock()
184 self.update_results() 173 self.update_results()
185 thread.start_new_thread(self._result_reloader, ()) 174 thread.start_new_thread(self._result_reloader, ())
186 175
187 if self._export: 176 if self._export:
188 server_address = ('', self._port) 177 server_address = ('', self._port)
189 host = get_routable_ip_address() 178 host = get_routable_ip_address()
190 if self._editable: 179 if self._editable:
191 logging.warning('Running with combination of "export" and "editable" ' 180 logging.warning('Running with combination of "export" and "editable" '
192 'flags. Users on other machines will ' 181 'flags. Users on other machines will '
193 'be able to modify your GM expectations!') 182 'be able to modify your GM expectations!')
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
229 218
230 def do_GET_results(self, type): 219 def do_GET_results(self, type):
231 """ Handle a GET request for GM results. 220 """ Handle a GET request for GM results.
232 221
233 Args: 222 Args:
234 type: string indicating which set of results to return; 223 type: string indicating which set of results to return;
235 must be one of the results.RESULTS_* constants 224 must be one of the results.RESULTS_* constants
236 """ 225 """
237 logging.debug('do_GET_results: sending results of type "%s"' % type) 226 logging.debug('do_GET_results: sending results of type "%s"' % type)
238 try: 227 try:
228 # Since we must make multiple calls to the Results object, grab a
229 # reference to it in case it is updated to point at a new Results
230 # object within another thread.
231 #
239 # TODO(epoger): Rather than using a global variable for the handler 232 # TODO(epoger): Rather than using a global variable for the handler
240 # to refer to the Server object, make Server a subclass of 233 # to refer to the Server object, make Server a subclass of
241 # HTTPServer, and then it could be available to the handler via 234 # HTTPServer, and then it could be available to the handler via
242 # the handler's .server instance variable. 235 # the handler's .server instance variable.
236 results_obj = _SERVER.results
237 response_dict = results_obj.get_results_of_type(type)
238 time_updated = results_obj.get_timestamp()
243 239
244 with _SERVER.results_lock:
245 response_dict = _SERVER.results.get_results_of_type(type)
246 time_updated = _SERVER.results.get_timestamp()
247 response_dict['header'] = { 240 response_dict['header'] = {
248 # Timestamps: 241 # Timestamps:
249 # 1. when this data was last updated 242 # 1. when this data was last updated
250 # 2. when the caller should check back for new data (if ever) 243 # 2. when the caller should check back for new data (if ever)
251 # 244 #
252 # We only return these timestamps if the --reload argument was passed; 245 # We only return these timestamps if the --reload argument was passed;
253 # otherwise, we have no idea when the expectations were last updated 246 # otherwise, we have no idea when the expectations were last updated
254 # (we allow the user to maintain her own expectations as she sees fit). 247 # (we allow the user to maintain her own expectations as she sees fit).
255 'timeUpdated': time_updated if _SERVER.reload_seconds() else None, 248 'timeUpdated': time_updated if _SERVER.reload_seconds() else None,
256 'timeNextUpdateAvailable': ( 249 'timeNextUpdateAvailable': (
(...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after
346 if content_type != 'application/json;charset=UTF-8': 339 if content_type != 'application/json;charset=UTF-8':
347 raise Exception('unsupported %s [%s]' % ( 340 raise Exception('unsupported %s [%s]' % (
348 _HTTP_HEADER_CONTENT_TYPE, content_type)) 341 _HTTP_HEADER_CONTENT_TYPE, content_type))
349 342
350 content_length = int(self.headers[_HTTP_HEADER_CONTENT_LENGTH]) 343 content_length = int(self.headers[_HTTP_HEADER_CONTENT_LENGTH])
351 json_data = self.rfile.read(content_length) 344 json_data = self.rfile.read(content_length)
352 data = json.loads(json_data) 345 data = json.loads(json_data)
353 logging.debug('do_POST_edits: received new GM expectations data [%s]' % 346 logging.debug('do_POST_edits: received new GM expectations data [%s]' %
354 data) 347 data)
355 348
356 with _SERVER.results_lock: 349 # Since we must make multiple calls to the Results object, grab a
357 oldResultsType = data['oldResultsType'] 350 # reference to it in case it is updated to point at a new Results
358 oldResults = _SERVER.results.get_results_of_type(oldResultsType) 351 # object within another thread.
359 oldResultsHash = str(hash(repr(oldResults['testData']))) 352 results_obj = _SERVER.results
360 if oldResultsHash != data['oldResultsHash']: 353 oldResultsType = data['oldResultsType']
361 raise Exception('results of type "%s" changed while the client was ' 354 oldResults = results_obj.get_results_of_type(oldResultsType)
362 'making modifications. The client should reload the ' 355 oldResultsHash = str(hash(repr(oldResults['testData'])))
363 'results and submit the modifications again.' % 356 if oldResultsHash != data['oldResultsHash']:
364 oldResultsType) 357 raise Exception('results of type "%s" changed while the client was '
365 _SERVER.results.edit_expectations(data['modifications']) 358 'making modifications. The client should reload the '
359 'results and submit the modifications again.' %
360 oldResultsType)
361 results_obj.edit_expectations(data['modifications'])
366 362
367 # Now that the edits have been committed, update results to reflect them. 363 # Now that the edits have been committed, update results to reflect them.
368 _SERVER.update_results() 364 _SERVER.update_results()
369 365
370 def redirect_to(self, url): 366 def redirect_to(self, url):
371 """ Redirect the HTTP client to a different url. 367 """ Redirect the HTTP client to a different url.
372 368
373 Args: 369 Args:
374 url: URL to redirect the HTTP client to 370 url: URL to redirect the HTTP client to
375 """ 371 """
(...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after
449 args = parser.parse_args() 445 args = parser.parse_args()
450 global _SERVER 446 global _SERVER
451 _SERVER = Server(actuals_dir=args.actuals_dir, 447 _SERVER = Server(actuals_dir=args.actuals_dir,
452 expectations_dir=args.expectations_dir, 448 expectations_dir=args.expectations_dir,
453 port=args.port, export=args.export, editable=args.editable, 449 port=args.port, export=args.export, editable=args.editable,
454 reload_seconds=args.reload) 450 reload_seconds=args.reload)
455 _SERVER.run() 451 _SERVER.run()
456 452
457 if __name__ == '__main__': 453 if __name__ == '__main__':
458 main() 454 main()
OLDNEW
« no previous file with comments | « no previous file | tools/svn.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698