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

Side by Side Diff: commit-queue/commit_queue.py

Issue 24434003: CQ: add landmines to delete database after unclean shutdown (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/
Patch Set: Created 7 years, 2 months 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 | no next file » | 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/env python 1 #!/usr/bin/env python
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 """Commit queue executable. 5 """Commit queue executable.
6 6
7 Reuse Rietveld and the Chromium Try Server to process and automatically commit 7 Reuse Rietveld and the Chromium Try Server to process and automatically commit
8 patches. 8 patches.
9 """ 9 """
10 10
(...skipping 105 matching lines...) Expand 10 before | Expand all | Expand 10 after
116 logging_rotating_file = logging.handlers.RotatingFileHandler( 116 logging_rotating_file = logging.handlers.RotatingFileHandler(
117 filename=os.path.join(log_directory, 'commit_queue.log'), 117 filename=os.path.join(log_directory, 'commit_queue.log'),
118 maxBytes= 10*1024*1024, 118 maxBytes= 10*1024*1024,
119 backupCount=50) 119 backupCount=50)
120 logging_rotating_file.setLevel(logging.DEBUG) 120 logging_rotating_file.setLevel(logging.DEBUG)
121 logging_rotating_file.setFormatter(logging.Formatter( 121 logging_rotating_file.setFormatter(logging.Formatter(
122 '%(asctime)s %(levelname)-8s %(module)15s(%(lineno)4d): %(message)s')) 122 '%(asctime)s %(levelname)-8s %(module)15s(%(lineno)4d): %(message)s'))
123 logging.getLogger().addHandler(logging_rotating_file) 123 logging.getLogger().addHandler(logging_rotating_file)
124 124
125 125
126 class SignalInterrupt(Exception):
127 """Exception that indicates being interrupted by a caught signal."""
iannucci 2013/09/25 19:47:07 It might be nice to eventually encapsulate the act
Paweł Hajdan Jr. 2013/09/25 21:53:36 Done.
128 pass
129
130
126 def main(): 131 def main():
127 parser = optparse.OptionParser( 132 parser = optparse.OptionParser(
128 description=sys.modules['__main__'].__doc__) 133 description=sys.modules['__main__'].__doc__)
129 project_choices = projects.supported_projects() 134 project_choices = projects.supported_projects()
130 parser.add_option('-v', '--verbose', action='store_true') 135 parser.add_option('-v', '--verbose', action='store_true')
131 parser.add_option( 136 parser.add_option(
132 '--no-dry-run', 137 '--no-dry-run',
133 action='store_false', 138 action='store_false',
134 dest='dry_run', 139 dest='dry_run',
135 default=True, 140 default=True,
(...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after
228 # Disable the checkout. 233 # Disable the checkout.
229 print 'Using no checkout' 234 print 'Using no checkout'
230 pc.context.checkout = FakeCheckout() 235 pc.context.checkout = FakeCheckout()
231 else: 236 else:
232 print 'Using read-only checkout' 237 print 'Using read-only checkout'
233 pc.context.checkout = checkout.ReadOnlyCheckout(pc.context.checkout) 238 pc.context.checkout = checkout.ReadOnlyCheckout(pc.context.checkout)
234 # Save pushed events on disk. 239 # Save pushed events on disk.
235 print 'Using read-only chromium-status interface' 240 print 'Using read-only chromium-status interface'
236 pc.context.status = async_push.AsyncPushStore() 241 pc.context.status = async_push.AsyncPushStore()
237 242
243 landmine_path = os.path.join(work_dir,
244 pc.context.checkout.project_name + '.landmine')
238 db_path = os.path.join(work_dir, pc.context.checkout.project_name + '.json') 245 db_path = os.path.join(work_dir, pc.context.checkout.project_name + '.json')
239 if os.path.isfile(db_path): 246 if os.path.isfile(db_path):
240 try: 247 if os.path.isfile(landmine_path):
241 pc.load(db_path) 248 # Remove saved database if previous shutdown was not clean.
242 except ValueError: 249 # this helps to avoid running with inconsistent database
250 # and potentially running changes without verification.
251 logging.warning('Deleting database because previous shutdown '
252 'was unclean.')
243 os.remove(db_path) 253 os.remove(db_path)
iannucci 2013/09/25 19:47:07 Can we save the db off to a mktemp backup instead,
Paweł Hajdan Jr. 2013/09/25 21:53:36 Done.
254 else:
255 try:
256 pc.load(db_path)
257 except ValueError:
258 os.remove(db_path)
iannucci 2013/09/25 19:47:07 same here with backup/log message
Paweł Hajdan Jr. 2013/09/25 21:53:36 Done.
259
260 # Create a file to indicate unclean shutdown.
261 with open(landmine_path, 'w') as _landmine_file:
iannucci 2013/09/25 19:47:07 you can just leave off the 'as ...' part.
Paweł Hajdan Jr. 2013/09/25 21:53:36 Done.
262 pass
244 263
245 sig_handler.installHandlers( 264 sig_handler.installHandlers(
246 signal.SIGINT, 265 signal.SIGINT,
247 signal.SIGHUP 266 signal.SIGHUP
248 ) 267 )
249 268
250 # Sync every 5 minutes. 269 # Sync every 5 minutes.
251 SYNC_DELAY = 5*60 270 SYNC_DELAY = 5*60
252 try: 271 try:
253 if options.query_only: 272 if options.query_only:
254 pc.look_for_new_pending_commit() 273 pc.look_for_new_pending_commit()
255 pc.update_status() 274 pc.update_status()
256 print(str(pc.queue)) 275 print(str(pc.queue))
276 os.remove(landmine_path)
257 return 0 277 return 0
258 278
259 now = time.time() 279 now = time.time()
260 next_loop = now + options.poll_interval 280 next_loop = now + options.poll_interval
261 # First sync is on second loop. 281 # First sync is on second loop.
262 next_sync = now + options.poll_interval * 2 282 next_sync = now + options.poll_interval * 2
263 while True: 283 while True:
264 # In theory, we would gain in performance to parallelize these tasks. In 284 # In theory, we would gain in performance to parallelize these tasks. In
265 # practice I'm not sure it matters. 285 # practice I'm not sure it matters.
266 pc.look_for_new_pending_commit() 286 pc.look_for_new_pending_commit()
267 pc.process_new_pending_commit() 287 pc.process_new_pending_commit()
268 pc.update_status() 288 pc.update_status()
269 pc.scan_results() 289 pc.scan_results()
270 if sig_handler.getTriggeredSignals(): 290 if sig_handler.getTriggeredSignals():
271 raise KeyboardInterrupt() 291 raise SignalInterrupt()
272 # Save the db at each loop. The db can easily be in the 1mb range so 292 # Save the db at each loop. The db can easily be in the 1mb range so
273 # it's slowing down the CQ a tad but it in the 100ms range even for that 293 # it's slowing down the CQ a tad but it in the 100ms range even for that
274 # size. 294 # size.
275 pc.save(db_path) 295 pc.save(db_path)
276 296
277 # More than a second to wait and due to sync. 297 # More than a second to wait and due to sync.
278 now = time.time() 298 now = time.time()
279 if (next_loop - now) >= 1 and (next_sync - now) <= 0: 299 if (next_loop - now) >= 1 and (next_sync - now) <= 0:
280 if sys.stdout.isatty(): 300 if sys.stdout.isatty():
281 sys.stdout.write('Syncing while waiting \r') 301 sys.stdout.write('Syncing while waiting \r')
282 sys.stdout.flush() 302 sys.stdout.flush()
283 try: 303 try:
284 pc.context.checkout.prepare(None) 304 pc.context.checkout.prepare(None)
285 except subprocess2.CalledProcessError as e: 305 except subprocess2.CalledProcessError as e:
286 # Don't crash, most of the time it's the svn server that is dead. 306 # Don't crash, most of the time it's the svn server that is dead.
287 # How fun. Send a stack trace to annoy the maintainer. 307 # How fun. Send a stack trace to annoy the maintainer.
288 errors.send_stack(e) 308 errors.send_stack(e)
289 next_sync = time.time() + SYNC_DELAY 309 next_sync = time.time() + SYNC_DELAY
290 310
291 now = time.time() 311 now = time.time()
292 next_loop = max(now, next_loop) 312 next_loop = max(now, next_loop)
293 while True: 313 while True:
294 # Abort if any signals are set 314 # Abort if any signals are set
295 if sig_handler.getTriggeredSignals(): 315 if sig_handler.getTriggeredSignals():
296 raise KeyboardInterrupt() 316 raise SignalInterrupt()
297 delay = next_loop - now 317 delay = next_loop - now
298 if delay <= 0: 318 if delay <= 0:
299 break 319 break
300 if sys.stdout.isatty(): 320 if sys.stdout.isatty():
301 sys.stdout.write('Sleeping for %1.1f seconds \r' % delay) 321 sys.stdout.write('Sleeping for %1.1f seconds \r' % delay)
302 sys.stdout.flush() 322 sys.stdout.flush()
303 time.sleep(min(delay, 0.1)) 323 time.sleep(min(delay, 0.1))
304 now = time.time() 324 now = time.time()
305 if sys.stdout.isatty(): 325 if sys.stdout.isatty():
306 sys.stdout.write('Running (please do not interrupt) \r') 326 sys.stdout.write('Running (please do not interrupt) \r')
307 sys.stdout.flush() 327 sys.stdout.flush()
308 next_loop = time.time() + options.poll_interval 328 next_loop = time.time() + options.poll_interval
309 except: # Catch all fatal exit conditions. 329 except: # Catch all fatal exit conditions.
310 logging.exception('CQ loop terminating') 330 logging.exception('CQ loop terminating')
311 raise 331 raise
312 finally: 332 finally:
313 logging.warning('Saving db...') 333 logging.warning('Saving db...')
314 pc.save(db_path) 334 pc.save(db_path)
315 pc.close() 335 pc.close()
316 logging.warning('db save successful.') 336 logging.warning('db save successful.')
337 except SignalInterrupt as e:
iannucci 2013/09/25 19:47:07 unless you want to do something with e, you should
Paweł Hajdan Jr. 2013/09/25 21:53:36 Done.
338 # This is considered a clean shutdown: we only throw this exception
339 # from selected places in the code where the database should be
340 # in a known and consistent state.
341 os.remove(landmine_path)
342
343 print 'By bye'
iannucci 2013/09/25 19:47:07 'Bye bye: caught SignalInterrupt' Also, should th
Paweł Hajdan Jr. 2013/09/25 21:53:36 Not sure, they were prints before so I left it tha
344 # 23 is an arbitrary value to signal loop.sh that it must stop looping.
345 return 23
317 except KeyboardInterrupt as e: 346 except KeyboardInterrupt as e:
iannucci 2013/09/25 19:47:07 same here re: 'as e'
Paweł Hajdan Jr. 2013/09/25 21:53:36 Done.
347 # This is actually an unclean shutdown. Do not remove the landmine file.
348 # One example of this is user hitting ctrl-c twice at an arbitrary point
349 # inside the CQ loop. There are no guarantees about consistent state
350 # of the database then.
351
318 print 'Bye bye' 352 print 'Bye bye'
iannucci 2013/09/25 19:47:07 maybe 'Unclean shutdown! caught KeyboardInterrupt'
Paweł Hajdan Jr. 2013/09/25 21:53:36 Done.
319 # 23 is an arbitrary value to signal loop.sh that it must stop looping. 353 # 23 is an arbitrary value to signal loop.sh that it must stop looping.
320 return 23 354 return 23
321 except errors.ConfigurationError as e: 355 except errors.ConfigurationError as e:
322 parser.error(str(e)) 356 parser.error(str(e))
iannucci 2013/09/25 19:47:07 Can we log here?
Paweł Hajdan Jr. 2013/09/25 21:53:36 I'd prefer not to clutter the patch with random cl
iannucci 2013/09/25 22:15:48 Actually I think parser.error will log/print anyho
323 return 1 357 return 1
358
359 # CQ generally doesn't exit by itself, but if we ever get here, it looks
360 # like a clean shutdown so remove the landmine file.
361 os.remove(landmine_path)
iannucci 2013/09/25 19:47:07 Maybe add a log/print here
Paweł Hajdan Jr. 2013/09/25 21:53:36 I don't think it's needed - if you can come up wit
iannucci 2013/09/25 22:15:48 Just reacting to the comment. If we never expect t
Paweł Hajdan Jr. 2013/09/25 23:27:17 Added a TODO. It seems too risky to change behavio
324 return 0 362 return 0
325 363
326 364
327 if __name__ == '__main__': 365 if __name__ == '__main__':
328 fix_encoding.fix_encoding() 366 fix_encoding.fix_encoding()
329 sys.exit(main()) 367 sys.exit(main())
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698