Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 Loading... | |
| 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 Loading... | |
| 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()) |
| OLD | NEW |