blob: ace2767dad27d7e5b79ba4cefd0bce985a523f24 [file] [log] [blame]
#
# Copyright 2017 Google Inc. All Rights Reserved.
#
# 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.
"""Linux implementation of Starboard launcher abstraction."""
import os
import signal
import socket
import subprocess
import sys
import time
import traceback
import _env # pylint: disable=unused-import
from starboard.tools import abstract_launcher
STATUS_CHANGE_TIMEOUT = 15
def GetProcessStatus(pid):
"""Returns process running status given its pid, or empty string if not found.
Args:
pid: process id of specified cobalt instance.
"""
output = subprocess.check_output(
["ps -o state= -p {}".format(pid)], shell=True)
return output
class Launcher(abstract_launcher.AbstractLauncher):
"""Class for launching Cobalt/tools on Linux."""
def __init__(self, platform, target_name, config, device_id, **kwargs):
super(Launcher, self).__init__(platform, target_name, config, device_id,
**kwargs)
if not self.device_id:
if socket.has_ipv6: # If the device supports IPv6:
self.device_id = "::1" # Use the only IPv6 loopback address
else:
self.device_id = socket.gethostbyname("localhost") # pylint: disable=W6503
self.executable = self.GetTargetPath()
env = os.environ.copy()
env.update(self.env_variables)
self.full_env = env
self.proc = None
self.pid = None
def Run(self):
"""Runs launcher's executable."""
self.proc = subprocess.Popen(
[self.executable] + self.target_command_line_params,
stdout=self.output_file,
stderr=self.output_file,
env=self.full_env,
close_fds=True)
self.pid = self.proc.pid
self.proc.wait()
return self.proc.returncode
def Kill(self):
sys.stderr.write("\n***Killing Launcher***\n")
if self.pid:
try:
self.proc.kill()
except OSError:
sys.stderr.write("Error killing launcher with SIGKILL:\n")
traceback.print_exc(file=sys.stderr)
else:
sys.stderr.write("Kill() called before Run(), cannot kill.\n")
def SupportsSuspendResume(self):
return True
def SendResume(self):
"""Sends continue to the launcher's executable."""
sys.stderr.write("\n***Sending continue signal to executable***\n")
if self.proc:
self.proc.send_signal(signal.SIGCONT)
# Wait for process status change in Linux system.
self.WaitForProcessStatus("R", STATUS_CHANGE_TIMEOUT)
else:
sys.stderr.write("Cannot send continue to executable; it is closed.\n")
def SendSuspend(self):
"""Sends suspend to the launcher's executable."""
sys.stderr.write("\n***Sending suspend signal to executable***\n")
if self.proc:
self.proc.send_signal(signal.SIGUSR1)
# Wait for process status change in Linux system.
self.WaitForProcessStatus("T", STATUS_CHANGE_TIMEOUT)
else:
sys.stderr.write("Cannot send suspend to executable; it is closed.\n")
def WaitForProcessStatus(self, target_status, timeout):
"""Wait for Cobalt to turn to target status within specified timeout limit.
Args:
target_status: A character representing application status:
R-running;
T-stopped/suspended;
S-sleep/paused;
timeout: Time limit in unit of seconds.
"""
elapsed_time = 0
while not GetProcessStatus(pid=self.pid).startswith(target_status):
if elapsed_time >= timeout:
return
else:
elapsed_time += .005
time.sleep(.005)