blob: 19761d3c777ec723d4d1e83f85dcd51f4d933dc7 [file] [log] [blame]
# 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()