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

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
11 import logging 11 import logging
12 import logging.handlers 12 import logging.handlers
13 import optparse 13 import optparse
14 import os 14 import os
15 import signal 15 import signal
16 import sys 16 import sys
17 import tempfile
17 import time 18 import time
18 19
19 import find_depot_tools # pylint: disable=W0611 20 import find_depot_tools # pylint: disable=W0611
20 import checkout 21 import checkout
21 import fix_encoding 22 import fix_encoding
22 import rietveld 23 import rietveld
23 import subprocess2 24 import subprocess2
24 25
25 import async_push 26 import async_push
26 import cq_alerts 27 import cq_alerts
(...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after
116 logging_rotating_file = logging.handlers.RotatingFileHandler( 117 logging_rotating_file = logging.handlers.RotatingFileHandler(
117 filename=os.path.join(log_directory, 'commit_queue.log'), 118 filename=os.path.join(log_directory, 'commit_queue.log'),
118 maxBytes= 10*1024*1024, 119 maxBytes= 10*1024*1024,
119 backupCount=50) 120 backupCount=50)
120 logging_rotating_file.setLevel(logging.DEBUG) 121 logging_rotating_file.setLevel(logging.DEBUG)
121 logging_rotating_file.setFormatter(logging.Formatter( 122 logging_rotating_file.setFormatter(logging.Formatter(
122 '%(asctime)s %(levelname)-8s %(module)15s(%(lineno)4d): %(message)s')) 123 '%(asctime)s %(levelname)-8s %(module)15s(%(lineno)4d): %(message)s'))
123 logging.getLogger().addHandler(logging_rotating_file) 124 logging.getLogger().addHandler(logging_rotating_file)
124 125
125 126
127 class SignalInterrupt(Exception):
128 """Exception that indicates being interrupted by a caught signal."""
129
130 def __init__(self, signal_set=None, *args, **kwargs):
131 super(SignalInterrupt, self).__init__(*args, **kwargs)
132 self.signal_set = signal_set
133
134
135 def SaveDatabaseCopyForDebugging(db_path):
136 """Saves database file for debugging. Returns name of the saved file."""
137 with tempfile.NamedTemporaryFile(
138 dir=os.path.dirname(db_path),
139 prefix='db.debug.',
140 suffix='.json',
141 delete=False) as tmp_file:
142 with open(db_path) as db_file:
143 tmp_file.write(db_file.read())
iannucci 2013/09/25 22:15:49 use one of the shutil copy methods to avoid readin
Paweł Hajdan Jr. 2013/09/25 23:27:17 Done.
144 return tmp_file.name
145
146
126 def main(): 147 def main():
127 parser = optparse.OptionParser( 148 parser = optparse.OptionParser(
128 description=sys.modules['__main__'].__doc__) 149 description=sys.modules['__main__'].__doc__)
129 project_choices = projects.supported_projects() 150 project_choices = projects.supported_projects()
130 parser.add_option('-v', '--verbose', action='store_true') 151 parser.add_option('-v', '--verbose', action='store_true')
131 parser.add_option( 152 parser.add_option(
132 '--no-dry-run', 153 '--no-dry-run',
133 action='store_false', 154 action='store_false',
134 dest='dry_run', 155 dest='dry_run',
135 default=True, 156 default=True,
(...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after
228 # Disable the checkout. 249 # Disable the checkout.
229 print 'Using no checkout' 250 print 'Using no checkout'
230 pc.context.checkout = FakeCheckout() 251 pc.context.checkout = FakeCheckout()
231 else: 252 else:
232 print 'Using read-only checkout' 253 print 'Using read-only checkout'
233 pc.context.checkout = checkout.ReadOnlyCheckout(pc.context.checkout) 254 pc.context.checkout = checkout.ReadOnlyCheckout(pc.context.checkout)
234 # Save pushed events on disk. 255 # Save pushed events on disk.
235 print 'Using read-only chromium-status interface' 256 print 'Using read-only chromium-status interface'
236 pc.context.status = async_push.AsyncPushStore() 257 pc.context.status = async_push.AsyncPushStore()
237 258
259 landmine_path = os.path.join(work_dir,
260 pc.context.checkout.project_name + '.landmine')
238 db_path = os.path.join(work_dir, pc.context.checkout.project_name + '.json') 261 db_path = os.path.join(work_dir, pc.context.checkout.project_name + '.json')
239 if os.path.isfile(db_path): 262 if os.path.isfile(db_path):
240 try: 263 if os.path.isfile(landmine_path):
241 pc.load(db_path) 264 debugging_path = SaveDatabaseCopyForDebugging(db_path)
242 except ValueError:
243 os.remove(db_path) 265 os.remove(db_path)
266 logging.warning(('Deleting database because previous shutdown '
267 'was unclean. The copy of the database is saved '
268 'as %s.') % debugging_path)
269 else:
270 try:
271 pc.load(db_path)
272 except ValueError as e:
273 debugging_path = SaveDatabaseCopyForDebugging(db_path)
274 os.remove(db_path)
275 logging.warning(('Failed to parse database (%r), deleting it. '
276 'The copy of the database is saved as %s.') %
277 (e, debugging_path))
278 raise e
279
280 # Create a file to indicate unclean shutdown.
281 with open(landmine_path, 'w'):
282 pass
244 283
245 sig_handler.installHandlers( 284 sig_handler.installHandlers(
246 signal.SIGINT, 285 signal.SIGINT,
247 signal.SIGHUP 286 signal.SIGHUP
248 ) 287 )
249 288
250 # Sync every 5 minutes. 289 # Sync every 5 minutes.
251 SYNC_DELAY = 5*60 290 SYNC_DELAY = 5*60
252 try: 291 try:
253 if options.query_only: 292 if options.query_only:
254 pc.look_for_new_pending_commit() 293 pc.look_for_new_pending_commit()
255 pc.update_status() 294 pc.update_status()
256 print(str(pc.queue)) 295 print(str(pc.queue))
296 os.remove(landmine_path)
257 return 0 297 return 0
258 298
259 now = time.time() 299 now = time.time()
260 next_loop = now + options.poll_interval 300 next_loop = now + options.poll_interval
261 # First sync is on second loop. 301 # First sync is on second loop.
262 next_sync = now + options.poll_interval * 2 302 next_sync = now + options.poll_interval * 2
263 while True: 303 while True:
264 # In theory, we would gain in performance to parallelize these tasks. In 304 # In theory, we would gain in performance to parallelize these tasks. In
265 # practice I'm not sure it matters. 305 # practice I'm not sure it matters.
266 pc.look_for_new_pending_commit() 306 pc.look_for_new_pending_commit()
267 pc.process_new_pending_commit() 307 pc.process_new_pending_commit()
268 pc.update_status() 308 pc.update_status()
269 pc.scan_results() 309 pc.scan_results()
270 if sig_handler.getTriggeredSignals(): 310 if sig_handler.getTriggeredSignals():
271 raise KeyboardInterrupt() 311 raise SignalInterrupt(signal_set=sig_handler.getTriggeredSignals())
272 # Save the db at each loop. The db can easily be in the 1mb range so 312 # 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 313 # it's slowing down the CQ a tad but it in the 100ms range even for that
274 # size. 314 # size.
275 pc.save(db_path) 315 pc.save(db_path)
276 316
277 # More than a second to wait and due to sync. 317 # More than a second to wait and due to sync.
278 now = time.time() 318 now = time.time()
279 if (next_loop - now) >= 1 and (next_sync - now) <= 0: 319 if (next_loop - now) >= 1 and (next_sync - now) <= 0:
280 if sys.stdout.isatty(): 320 if sys.stdout.isatty():
281 sys.stdout.write('Syncing while waiting \r') 321 sys.stdout.write('Syncing while waiting \r')
282 sys.stdout.flush() 322 sys.stdout.flush()
283 try: 323 try:
284 pc.context.checkout.prepare(None) 324 pc.context.checkout.prepare(None)
285 except subprocess2.CalledProcessError as e: 325 except subprocess2.CalledProcessError as e:
286 # Don't crash, most of the time it's the svn server that is dead. 326 # 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. 327 # How fun. Send a stack trace to annoy the maintainer.
288 errors.send_stack(e) 328 errors.send_stack(e)
289 next_sync = time.time() + SYNC_DELAY 329 next_sync = time.time() + SYNC_DELAY
290 330
291 now = time.time() 331 now = time.time()
292 next_loop = max(now, next_loop) 332 next_loop = max(now, next_loop)
293 while True: 333 while True:
294 # Abort if any signals are set 334 # Abort if any signals are set
295 if sig_handler.getTriggeredSignals(): 335 if sig_handler.getTriggeredSignals():
296 raise KeyboardInterrupt() 336 raise SignalInterrupt(signal_set=sig_handler.getTriggeredSignals())
297 delay = next_loop - now 337 delay = next_loop - now
298 if delay <= 0: 338 if delay <= 0:
299 break 339 break
300 if sys.stdout.isatty(): 340 if sys.stdout.isatty():
301 sys.stdout.write('Sleeping for %1.1f seconds \r' % delay) 341 sys.stdout.write('Sleeping for %1.1f seconds \r' % delay)
302 sys.stdout.flush() 342 sys.stdout.flush()
303 time.sleep(min(delay, 0.1)) 343 time.sleep(min(delay, 0.1))
304 now = time.time() 344 now = time.time()
305 if sys.stdout.isatty(): 345 if sys.stdout.isatty():
306 sys.stdout.write('Running (please do not interrupt) \r') 346 sys.stdout.write('Running (please do not interrupt) \r')
307 sys.stdout.flush() 347 sys.stdout.flush()
308 next_loop = time.time() + options.poll_interval 348 next_loop = time.time() + options.poll_interval
309 except: # Catch all fatal exit conditions. 349 except: # Catch all fatal exit conditions.
310 logging.exception('CQ loop terminating') 350 logging.exception('CQ loop terminating')
311 raise 351 raise
312 finally: 352 finally:
313 logging.warning('Saving db...') 353 logging.warning('Saving db...')
314 pc.save(db_path) 354 pc.save(db_path)
315 pc.close() 355 pc.close()
316 logging.warning('db save successful.') 356 logging.warning('db save successful.')
317 except KeyboardInterrupt as e: 357 except SignalInterrupt:
318 print 'Bye bye' 358 # This is considered a clean shutdown: we only throw this exception
359 # from selected places in the code where the database should be
360 # in a known and consistent state.
361 os.remove(landmine_path)
362
363 print 'By bye (SignalInterrupt)'
iannucci 2013/09/25 22:15:49 'Bye bye' Bye -> abbreviation of 'goodbye' By ->
Paweł Hajdan Jr. 2013/09/25 23:27:17 Done. Actually this was accidental deletion in vim
364 # 23 is an arbitrary value to signal loop.sh that it must stop looping.
365 return 23
366 except KeyboardInterrupt:
367 # This is actually an unclean shutdown. Do not remove the landmine file.
368 # One example of this is user hitting ctrl-c twice at an arbitrary point
369 # inside the CQ loop. There are no guarantees about consistent state
370 # of the database then.
371
372 print 'Bye bye (KeyboardInterrupt - this is considered unclean shutdown)'
319 # 23 is an arbitrary value to signal loop.sh that it must stop looping. 373 # 23 is an arbitrary value to signal loop.sh that it must stop looping.
320 return 23 374 return 23
321 except errors.ConfigurationError as e: 375 except errors.ConfigurationError as e:
322 parser.error(str(e)) 376 parser.error(str(e))
323 return 1 377 return 1
378
379 # CQ generally doesn't exit by itself, but if we ever get here, it looks
380 # like a clean shutdown so remove the landmine file.
381 os.remove(landmine_path)
324 return 0 382 return 0
325 383
326 384
327 if __name__ == '__main__': 385 if __name__ == '__main__':
328 fix_encoding.fix_encoding() 386 fix_encoding.fix_encoding()
329 sys.exit(main()) 387 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