Skip to content

Commit

Permalink
Merge pull request home-assistant#2113 from home-assistant/dev
Browse files Browse the repository at this point in the history
0.20
  • Loading branch information
balloob committed May 21, 2016
2 parents fd82402 + ab60b32 commit b78765a
Show file tree
Hide file tree
Showing 90 changed files with 3,161 additions and 403 deletions.
12 changes: 12 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ omit =
homeassistant/components/octoprint.py
homeassistant/components/*/octoprint.py

homeassistant/components/qwikswitch.py
homeassistant/components/*/qwikswitch.py

homeassistant/components/rpi_gpio.py
homeassistant/components/*/rpi_gpio.py

Expand Down Expand Up @@ -111,18 +114,24 @@ omit =
homeassistant/components/media_player/cast.py
homeassistant/components/media_player/denon.py
homeassistant/components/media_player/firetv.py
homeassistant/components/media_player/gpmdp.py
homeassistant/components/media_player/itunes.py
homeassistant/components/media_player/kodi.py
homeassistant/components/media_player/lg_netcast.py
homeassistant/components/media_player/mpd.py
homeassistant/components/media_player/onkyo.py
homeassistant/components/media_player/panasonic_viera.py
homeassistant/components/media_player/pioneer.py
homeassistant/components/media_player/plex.py
homeassistant/components/media_player/roku.py
homeassistant/components/media_player/samsungtv.py
homeassistant/components/media_player/snapcast.py
homeassistant/components/media_player/sonos.py
homeassistant/components/media_player/squeezebox.py
homeassistant/components/media_player/yamaha.py
homeassistant/components/notify/aws_lambda.py
homeassistant/components/notify/aws_sns.py
homeassistant/components/notify/aws_sqs.py
homeassistant/components/notify/free_mobile.py
homeassistant/components/notify/gntp.py
homeassistant/components/notify/googlevoice.py
Expand All @@ -138,6 +147,7 @@ omit =
homeassistant/components/notify/smtp.py
homeassistant/components/notify/syslog.py
homeassistant/components/notify/telegram.py
homeassistant/components/notify/twilio_sms.py
homeassistant/components/notify/twitter.py
homeassistant/components/notify/xmpp.py
homeassistant/components/scene/hunterdouglas_powerview.py
Expand All @@ -153,6 +163,7 @@ omit =
homeassistant/components/sensor/glances.py
homeassistant/components/sensor/google_travel_time.py
homeassistant/components/sensor/gtfs.py
homeassistant/components/sensor/lastfm.py
homeassistant/components/sensor/loopenergy.py
homeassistant/components/sensor/netatmo.py
homeassistant/components/sensor/neurio_energy.py
Expand All @@ -163,6 +174,7 @@ omit =
homeassistant/components/sensor/sabnzbd.py
homeassistant/components/sensor/speedtest.py
homeassistant/components/sensor/steam_online.py
homeassistant/components/sensor/supervisord.py
homeassistant/components/sensor/swiss_public_transport.py
homeassistant/components/sensor/systemmonitor.py
homeassistant/components/sensor/temper.py
Expand Down
5 changes: 5 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@

**Related issue (if applicable):** #

**Pull request in [home-assistant.io](https://github.com/home-assistant/home-assistant.io) with documentation (if applicable):** home-assistant/home-assistant.io#

**Example entry for `configuration.yaml` (if applicable):**
```yaml

```

**Checklist:**

If user exposed functionality or configuration variables are added/changed:
- [ ] Documentation added/updated in [home-assistant.io](https://github.com/home-assistant/home-assistant.io)

If code communicates with devices:
- [ ] Local tests with `tox` run successfully. **Your PR cannot be merged unless tests pass**
- [ ] New dependencies have been added to the `REQUIREMENTS` variable ([example][ex-requir]).
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,5 @@ venv
# vimmy stuff
*.swp
*.swo

ctags.tmp
8 changes: 8 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ RUN script/build_python_openzwave && \
COPY requirements_all.txt requirements_all.txt
RUN pip3 install --no-cache-dir -r requirements_all.txt

RUN wget http://www.openssl.org/source/openssl-1.0.2h.tar.gz && \
tar -xvzf openssl-1.0.2h.tar.gz && \
cd openssl-1.0.2h && \
./config --prefix=/usr/ && \
make && \
make install && \
rm -rf openssl-1.0.2h*

# Copy source
COPY . .

Expand Down
179 changes: 125 additions & 54 deletions homeassistant/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@

import argparse
import os
import platform
import signal
import subprocess
import sys
import threading
import time
from multiprocessing import Process

from homeassistant.const import (
__version__,
Expand Down Expand Up @@ -87,8 +88,7 @@ def get_arguments():
parser.add_argument(
'--debug',
action='store_true',
help='Start Home Assistant in debug mode. Runs in single process to '
'enable use of interactive debuggers.')
help='Start Home Assistant in debug mode')
parser.add_argument(
'--open-ui',
action='store_true',
Expand Down Expand Up @@ -123,15 +123,20 @@ def get_arguments():
'--restart-osx',
action='store_true',
help='Restarts on OS X.')
if os.name != "nt":
parser.add_argument(
'--runner',
action='store_true',
help='On restart exit with code {}'.format(RESTART_EXIT_CODE))
if os.name == "posix":
parser.add_argument(
'--daemon',
action='store_true',
help='Run Home Assistant as daemon')

arguments = parser.parse_args()
if os.name == "nt":
if os.name != "posix" or arguments.debug or arguments.runner:
arguments.daemon = False

return arguments


Expand All @@ -144,13 +149,21 @@ def daemonize():

# Decouple fork
os.setsid()
os.umask(0)

# Create second fork
pid = os.fork()
if pid > 0:
sys.exit(0)

# redirect standard file descriptors to devnull
infd = open(os.devnull, 'r')
outfd = open(os.devnull, 'a+')
sys.stdout.flush()
sys.stderr.flush()
os.dup2(infd.fileno(), sys.stdin.fileno())
os.dup2(outfd.fileno(), sys.stdout.fileno())
os.dup2(outfd.fileno(), sys.stderr.fileno())


def check_pid(pid_file):
"""Check that HA is not already running."""
Expand All @@ -161,6 +174,10 @@ def check_pid(pid_file):
# PID File does not exist
return

# If we just restarted, we just found our own pidfile.
if pid == os.getpid():
return

try:
os.kill(pid, 0)
except OSError:
Expand Down Expand Up @@ -220,29 +237,61 @@ def uninstall_osx():
print("Home Assistant has been uninstalled.")


def setup_and_run_hass(config_dir, args, top_process=False):
"""Setup HASS and run.
def closefds_osx(min_fd, max_fd):
"""Make sure file descriptors get closed when we restart.
Block until stopped. Will assume it is running in a subprocess unless
top_process is set to true.
We cannot call close on guarded fds, and we cannot easily test which fds
are guarded. But we can set the close-on-exec flag on everything we want to
get rid of.
"""
from fcntl import fcntl, F_GETFD, F_SETFD, FD_CLOEXEC

for _fd in range(min_fd, max_fd):
try:
val = fcntl(_fd, F_GETFD)
if not val & FD_CLOEXEC:
fcntl(_fd, F_SETFD, val | FD_CLOEXEC)
except IOError:
pass


def cmdline():
"""Collect path and arguments to re-execute the current hass instance."""
if sys.argv[0].endswith('/__main__.py'):
modulepath = os.path.dirname(sys.argv[0])
os.environ['PYTHONPATH'] = os.path.dirname(modulepath)
return [sys.executable] + [arg for arg in sys.argv if arg != '--daemon']


def setup_and_run_hass(config_dir, args):
"""Setup HASS and run."""
from homeassistant import bootstrap

# Run a simple daemon runner process on Windows to handle restarts
if os.name == 'nt' and '--runner' not in sys.argv:
args = cmdline() + ['--runner']
while True:
try:
subprocess.check_call(args)
sys.exit(0)
except subprocess.CalledProcessError as exc:
if exc.returncode != RESTART_EXIT_CODE:
sys.exit(exc.returncode)

if args.demo_mode:
config = {
'frontend': {},
'demo': {}
}
hass = bootstrap.from_config_dict(
config, config_dir=config_dir, daemon=args.daemon,
verbose=args.verbose, skip_pip=args.skip_pip,
log_rotate_days=args.log_rotate_days)
config, config_dir=config_dir, verbose=args.verbose,
skip_pip=args.skip_pip, log_rotate_days=args.log_rotate_days)
else:
config_file = ensure_config_file(config_dir)
print('Config directory:', config_dir)
hass = bootstrap.from_config_file(
config_file, daemon=args.daemon, verbose=args.verbose,
skip_pip=args.skip_pip, log_rotate_days=args.log_rotate_days)
config_file, verbose=args.verbose, skip_pip=args.skip_pip,
log_rotate_days=args.log_rotate_days)

if hass is None:
return
Expand All @@ -256,42 +305,68 @@ def open_browser(event):

hass.bus.listen_once(EVENT_HOMEASSISTANT_START, open_browser)

print('Starting Home-Assistant')
hass.start()
exit_code = int(hass.block_till_stopped())

if not top_process:
sys.exit(exit_code)
return exit_code


def run_hass_process(hass_proc):
"""Run a child hass process. Returns True if it should be restarted."""
requested_stop = threading.Event()
hass_proc.daemon = True
def try_to_restart():
"""Attempt to clean up state and start a new homeassistant instance."""
# Things should be mostly shut down already at this point, now just try
# to clean up things that may have been left behind.
sys.stderr.write('Home Assistant attempting to restart.\n')

# Count remaining threads, ideally there should only be one non-daemonized
# thread left (which is us). Nothing we really do with it, but it might be
# useful when debugging shutdown/restart issues.
nthreads = sum(thread.isAlive() and not thread.isDaemon()
for thread in threading.enumerate())
if nthreads > 1:
sys.stderr.write("Found {} non-daemonic threads.\n".format(nthreads))

# Send terminate signal to all processes in our process group which
# should be any children that have not themselves changed the process
# group id. Don't bother if couldn't even call setpgid.
if hasattr(os, 'setpgid'):
sys.stderr.write("Signalling child processes to terminate...\n")
os.kill(0, signal.SIGTERM)

# wait for child processes to terminate
try:
while True:
time.sleep(1)
if os.waitpid(0, os.WNOHANG) == (0, 0):
break
except OSError:
pass

def request_stop(*args):
"""Request hass stop, *args is for signal handler callback."""
requested_stop.set()
hass_proc.terminate()
elif os.name == 'nt':
# Maybe one of the following will work, but how do we indicate which
# processes are our children if there is no process group?
# os.kill(0, signal.CTRL_C_EVENT)
# os.kill(0, signal.CTRL_BREAK_EVENT)
pass

# Try to not leave behind open filedescriptors with the emphasis on try.
try:
signal.signal(signal.SIGTERM, request_stop)
max_fd = os.sysconf("SC_OPEN_MAX")
except ValueError:
print('Could not bind to SIGTERM. Are you running in a thread?')
max_fd = 256

hass_proc.start()
try:
hass_proc.join()
except KeyboardInterrupt:
request_stop()
try:
hass_proc.join()
except KeyboardInterrupt:
return False
if platform.system() == 'Darwin':
closefds_osx(3, max_fd)
else:
os.closerange(3, max_fd)

return (not requested_stop.isSet() and
hass_proc.exitcode == RESTART_EXIT_CODE,
hass_proc.exitcode)
# Now launch into a new instance of Home-Assistant. If this fails we
# fall through and exit with error 100 (RESTART_EXIT_CODE) in which case
# systemd will restart us when RestartForceExitStatus=100 is set in the
# systemd.service file.
sys.stderr.write("Restarting Home-Assistant\n")
args = cmdline()
os.execv(args[0], args)


def main():
Expand Down Expand Up @@ -325,21 +400,17 @@ def main():
if args.pid_file:
write_pid(args.pid_file)

# Run hass in debug mode if requested
if args.debug:
sys.stderr.write('Running in debug mode. '
'Home Assistant will not be able to restart.\n')
exit_code = setup_and_run_hass(config_dir, args, top_process=True)
if exit_code == RESTART_EXIT_CODE:
sys.stderr.write('Home Assistant requested a '
'restart in debug mode.\n')
return exit_code

# Run hass as child process. Restart if necessary.
keep_running = True
while keep_running:
hass_proc = Process(target=setup_and_run_hass, args=(config_dir, args))
keep_running, exit_code = run_hass_process(hass_proc)
# Create new process group if we can
if hasattr(os, 'setpgid'):
try:
os.setpgid(0, 0)
except PermissionError:
pass

exit_code = setup_and_run_hass(config_dir, args)
if exit_code == RESTART_EXIT_CODE and not args.runner:
try_to_restart()

return exit_code


Expand Down
Loading

0 comments on commit b78765a

Please sign in to comment.