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

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