| # Copyright (C) 2020 The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http:#www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| """Process object that integrates into modernmp""" |
| import os |
| import logging |
| import asyncio |
| import signal |
| from functools import partial |
| |
| from .util import ( |
| ChildExitWatcher, |
| current_event_loop, |
| die_due_to_fatal_exception, |
| the, |
| tls, |
| ) |
| |
| from .apartment import ( |
| ApartmentFactory, |
| ) |
| |
| from .shm import ( |
| SharedObject, |
| get_process_resmanc, |
| ) |
| |
| log = logging.getLogger(__name__) |
| |
| def _run_worker_child(factory): |
| loop = asyncio.new_event_loop() |
| with current_event_loop(loop): |
| with factory.make_apartment().attached() as apartment: |
| from setproctitle import setproctitle |
| setproctitle(apartment.name) |
| loop.run_forever() |
| |
| class WorkerProcess(SharedObject): |
| """Worker process resource object""" |
| __exit_status = None |
| __subprocess = None |
| |
| def __init__(self): |
| # TODO(dancol): mode that spawns from resman |
| name = "resman worker" |
| process_resmanc_factory = get_process_resmanc()\ |
| .make_connection_factory(name) |
| apartment_factory = ApartmentFactory(name, process_resmanc_factory.id) |
| with ChildExitWatcher.reaping_blocked(): |
| self.__pid = get_process_resmanc().fork( |
| partial(_run_worker_child, apartment_factory), |
| process_resmanc_factory) |
| try: |
| assert self.__pid # Child case does not return |
| process_resmanc_factory.close() |
| self.__call_gate = apartment_factory.call_gate |
| # Various Python bugs can keep our frame alive after we |
| # return, so make sure we drop the last reference to |
| # apartment_factory. |
| del apartment_factory |
| ChildExitWatcher.register(self.__pid, self.__on_reaped) |
| except: |
| die_due_to_fatal_exception("registering child cleanups") |
| |
| @property |
| def pid(self): |
| """PID of worker process""" |
| return self.__pid |
| |
| @property |
| def dead(self): |
| """Return whether this process has died""" |
| assert tls.resman_server |
| return self.__exit_status is not None |
| |
| def kill(self): |
| """Destroy a process |
| |
| The process is destroyed asynchronously. |
| """ |
| assert tls.resman_server |
| if not self.dead: |
| with ChildExitWatcher.reaping_blocked(): |
| if not self.dead: |
| os.kill(self.pid, signal.SIGTERM) |
| |
| def close(self): |
| """Eagerly kill a process even from outside resman""" |
| get_process_resmanc().explicit_resource_close(self.oid) |
| |
| def __on_reaped(self, exit_status: int): |
| """Called from SIGCHLD handler""" |
| assert self.__exit_status is None |
| self.__exit_status = the(int, exit_status) |
| |
| def _resman_get_process_status(self): |
| assert tls.resman_server |
| return self.__exit_status |
| |
| @property |
| def call_gate(self): |
| """Call gate for sending work to process's main thread""" |
| return self.__call_gate |
| |
| def _resman_explicit_close(self): |
| self.kill() |
| |
| def _resman_destroy(self): |
| self.kill() |