Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright (c) 2015 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2015 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 import errno | 5 import errno |
| 6 import json | 6 import json |
| 7 import logging | 7 import logging |
| 8 import os.path | 8 import os.path |
| 9 import platform | 9 import platform |
| 10 import signal | 10 import signal |
| (...skipping 163 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 174 """Controls a running service process. | 174 """Controls a running service process. |
| 175 | 175 |
| 176 Starts and stops the process, checks whether it is running, and creates and | 176 Starts and stops the process, checks whether it is running, and creates and |
| 177 deletes its state file in the state_directory. | 177 deletes its state file in the state_directory. |
| 178 | 178 |
| 179 State files are like UNIX PID files, except they also contain the starttime | 179 State files are like UNIX PID files, except they also contain the starttime |
| 180 (read from /proc/$PID/stat) of the process to protect against the race | 180 (read from /proc/$PID/stat) of the process to protect against the race |
| 181 condition of a different process reusing the same PID. | 181 condition of a different process reusing the same PID. |
| 182 """ | 182 """ |
| 183 | 183 |
| 184 def __init__(self, state_directory, service_config, time_fn=time.time, | 184 def __init__(self, state_directory, service_config, cloudtail_path, |
| 185 sleep_fn=time.sleep): | 185 time_fn=time.time, sleep_fn=time.sleep): |
| 186 """ | 186 """ |
| 187 Args: | 187 Args: |
| 188 state_directory: A file will be created in this directory (with the same | 188 state_directory: A file will be created in this directory (with the same |
| 189 name as the service) when it is running containing its PID and | 189 name as the service) when it is running containing its PID and |
| 190 starttime. | 190 starttime. |
| 191 service_config: A dictionary containing the service's config. See README | 191 service_config: A dictionary containing the service's config. See README |
| 192 for a description of the fields. | 192 for a description of the fields. |
| 193 cloudtail_path: Path to the cloudtail binary to use for logging, or None | |
| 194 if logging is disabled. | |
| 193 """ | 195 """ |
| 194 | 196 |
| 195 self.config = service_config | 197 self.config = service_config |
| 196 self.name = service_config['name'] | 198 self.name = service_config['name'] |
| 197 self.root_directory = service_config['root_directory'] | 199 self.root_directory = service_config['root_directory'] |
| 198 self.tool = service_config['tool'] | 200 self.tool = service_config['tool'] |
| 199 | 201 |
| 200 self.args = service_config.get('args', []) | 202 self.args = service_config.get('args', []) |
| 201 self.stop_time = int(service_config.get('stop_time', 10)) | 203 self.stop_time = int(service_config.get('stop_time', 10)) |
| 202 | 204 |
| 205 if cloudtail_path is None: | |
| 206 self.cloudtail_args = None | |
| 207 else: | |
| 208 self.cloudtail_args = [ | |
| 209 cloudtail_path, 'pipe', | |
| 210 '--log-id', self.name, | |
|
Vadim Sh.
2015/09/23 17:40:21
they have limit on number of these, as far as I un
dsansome
2015/09/24 04:34:13
Yep yep
| |
| 211 '--local-log-level', 'info', | |
| 212 ] | |
| 213 | |
| 203 self._state_file = os.path.join(state_directory, self.name) | 214 self._state_file = os.path.join(state_directory, self.name) |
| 204 self._time_fn = time_fn | 215 self._time_fn = time_fn |
| 205 self._sleep_fn = sleep_fn | 216 self._sleep_fn = sleep_fn |
| 206 | 217 |
| 207 self._process_creator = ProcessCreator.for_platform(self) | 218 self._process_creator = ProcessCreator.for_platform(self) |
| 208 | 219 |
| 209 def get_running_process_state(self): | 220 def get_running_process_state(self): |
| 210 """Returns a ProcessState object about about the process. | 221 """Returns a ProcessState object about about the process. |
| 211 | 222 |
| 212 Raises some subclass of ProcessStateError if the process is not running. | 223 Raises some subclass of ProcessStateError if the process is not running. |
| (...skipping 119 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 332 | 343 |
| 333 Makes the get_running_process_state() functionality available for the | 344 Makes the get_running_process_state() functionality available for the |
| 334 service_manager process. | 345 service_manager process. |
| 335 """ | 346 """ |
| 336 | 347 |
| 337 def __init__(self, state_directory, root_directory, **kwargs): | 348 def __init__(self, state_directory, root_directory, **kwargs): |
| 338 super(OwnService, self).__init__(state_directory, { | 349 super(OwnService, self).__init__(state_directory, { |
| 339 'name': 'service_manager', | 350 'name': 'service_manager', |
| 340 'tool': '', | 351 'tool': '', |
| 341 'root_directory': root_directory, | 352 'root_directory': root_directory, |
| 342 }, **kwargs) | 353 }, None, '', **kwargs) |
| 343 self._state_directory = state_directory | 354 self._state_directory = state_directory |
| 344 | 355 |
| 345 def start(self): | 356 def start(self): |
| 346 """Writes a state file, returns False if we're already running.""" | 357 """Writes a state file, returns False if we're already running.""" |
| 347 | 358 |
| 348 # Use a flock here to avoid a race condition where two service_managers | 359 # Use a flock here to avoid a race condition where two service_managers |
| 349 # start at the same time and both write their own state file. | 360 # start at the same time and both write their own state file. |
| 350 try: | 361 try: |
| 351 with daemon.flock('service_manager.flock', self._state_directory): | 362 with daemon.flock('service_manager.flock', self._state_directory): |
| 352 try: | 363 try: |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 365 raise NotImplementedError | 376 raise NotImplementedError |
| 366 | 377 |
| 367 | 378 |
| 368 class UnixProcessCreator(ProcessCreator): # pragma: no cover | 379 class UnixProcessCreator(ProcessCreator): # pragma: no cover |
| 369 """Implementation of ProcessCreator for Unix systems. | 380 """Implementation of ProcessCreator for Unix systems. |
| 370 | 381 |
| 371 Forks twice and sends the PID of the service over a pipe to the parent. | 382 Forks twice and sends the PID of the service over a pipe to the parent. |
| 372 """ | 383 """ |
| 373 | 384 |
| 374 def start(self): | 385 def start(self): |
| 375 pipe_r, pipe_w = os.pipe() | 386 control_r, control_w = os.pipe() |
| 376 child_pid = os.fork() | 387 child_pid = os.fork() |
| 377 if child_pid == 0: | 388 if child_pid == 0: |
| 378 os.close(pipe_r) | 389 os.close(control_r) |
| 379 try: | 390 try: |
| 380 self._start_child(os.fdopen(pipe_w, 'w')) | 391 self._start_child(os.fdopen(control_w, 'w')) |
| 381 finally: | 392 finally: |
| 382 os._exit(1) | 393 os._exit(1) |
| 383 else: | 394 else: |
| 384 os.close(pipe_w) | 395 os.close(control_w) |
| 385 return self._start_parent(os.fdopen(pipe_r, 'r'), child_pid) | 396 return self._start_parent(os.fdopen(control_r, 'r'), child_pid) |
| 386 | 397 |
| 387 def _start_child(self, pipe): | 398 def _start_child(self, control_fh): |
| 388 """The part of start() that runs in the child process. | 399 """The part of start() that runs in the child process. |
| 389 | 400 |
| 390 Daemonises the current process, writes the new PID to the pipe, and execs | 401 Daemonises the current process, writes the new PID to the pipe, and execs |
| 391 the service executable. | 402 the service executable. |
| 392 """ | 403 """ |
| 393 | 404 |
| 394 # Detach from the parent and close all FDs except that of the pipe. | 405 # Detach from the parent but keep FDs open so we can write to the log still. |
| 395 daemon.become_daemon(keep_fds={pipe.fileno()}) | 406 daemon.become_daemon(keep_fds=True) |
| 396 | 407 |
| 397 # Write our new PID to the pipe and close it. | 408 # Write our new PID to the pipe and close it. |
| 398 json.dump({'pid': os.getpid()}, pipe) | 409 json.dump({'pid': os.getpid()}, control_fh) |
| 399 pipe.close() | 410 control_fh.close() |
| 411 | |
| 412 # Start cloudtail. This needs to be done after become_daemon so it's a | |
| 413 # child of the service itself, not service_manager. | |
| 414 # Cloudtail will inherit service_manager's stdout and stderr, so its errors | |
| 415 # will go to service_manager's log. | |
| 416 if self.service.cloudtail_args is not None: | |
| 417 log_r, log_w = os.pipe() | |
| 418 try: | |
| 419 handle = subprocess.Popen( | |
| 420 self.service.cloudtail_args, stdin=log_r, close_fds=True) | |
| 421 except OSError: | |
| 422 log_w = None | |
| 423 logging.exception('Failed to start cloudtail with args %s', | |
| 424 self.service.cloudtail_args) | |
| 425 else: | |
| 426 logging.info('Started cloudtail for %s with PID %d', | |
| 427 self.service.name, handle.pid) | |
| 428 else: | |
| 429 log_w = None | |
| 430 | |
| 431 # Pipe our stdout and stderr to cloudtail if configured. Close all other | |
| 432 # FDs. | |
| 433 if log_w is not None: | |
| 434 os.dup2(log_w, 1) | |
| 435 os.dup2(log_w, 2) | |
| 436 daemon.close_all_fds(keep_fds={1, 2}) | |
| 437 else: | |
| 438 daemon.close_all_fds(keep_fds=None) | |
| 400 | 439 |
| 401 # Exec the service. | 440 # Exec the service. |
| 402 runpy = os.path.join(self.service.root_directory, 'run.py') | 441 runpy = os.path.join(self.service.root_directory, 'run.py') |
| 403 os.execv(runpy, [runpy, self.service.tool] + self.service.args) | 442 os.execv(runpy, [runpy, self.service.tool] + self.service.args) |
| 404 | 443 |
| 405 def _start_parent(self, pipe, child_pid): | 444 def _start_parent(self, pipe, child_pid): |
| 406 """The part of start() that runs in the parent process. | 445 """The part of start() that runs in the parent process. |
| 407 | 446 |
| 408 Waits for the child to start and writes a state file for the process. | 447 Waits for the child to start and writes a state file for the process. |
| 409 """ | 448 """ |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 448 stdout=null_fh, | 487 stdout=null_fh, |
| 449 args=[ | 488 args=[ |
| 450 os.path.join(self.service.root_directory, | 489 os.path.join(self.service.root_directory, |
| 451 'ENV/Scripts/python.exe'), | 490 'ENV/Scripts/python.exe'), |
| 452 os.path.join(self.service.root_directory, 'run.py'), | 491 os.path.join(self.service.root_directory, 'run.py'), |
| 453 self.service.tool, | 492 self.service.tool, |
| 454 ] + self.service.args, | 493 ] + self.service.args, |
| 455 ) | 494 ) |
| 456 | 495 |
| 457 return handle.pid | 496 return handle.pid |
| OLD | NEW |