Skip to content

Commit 4d8cbb6

Browse files
authored
add process_utils.py (#35)
Co-authored-by: jarbasai <jarbasai@mailfence.com>
1 parent ba0e229 commit 4d8cbb6

File tree

1 file changed

+143
-0
lines changed

1 file changed

+143
-0
lines changed

ovos_utils/process_utils.py

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import sys
2+
from collections import namedtuple
3+
from enum import IntEnum
4+
5+
6+
class ProcessState(IntEnum):
7+
"""Oredered enum to make state checks easy.
8+
9+
For example Alive can be determined using >= ProcessState.ALIVE,
10+
which will return True if the state is READY as well as ALIVE.
11+
"""
12+
NOT_STARTED = 0
13+
STARTED = 1
14+
ERROR = 2
15+
STOPPING = 3
16+
ALIVE = 4
17+
READY = 5
18+
19+
20+
# Process state change callback mappings.
21+
_STATUS_CALLBACKS = [
22+
'on_started',
23+
'on_alive',
24+
'on_ready',
25+
'on_error',
26+
'on_stopping',
27+
]
28+
29+
# namedtuple defaults only available on 3.7 and later python versions
30+
if sys.version_info < (3, 7):
31+
StatusCallbackMap = namedtuple('CallbackMap', _STATUS_CALLBACKS)
32+
StatusCallbackMap.__new__.__defaults__ = (None,) * 5
33+
else:
34+
StatusCallbackMap = namedtuple(
35+
'CallbackMap',
36+
_STATUS_CALLBACKS,
37+
defaults=(None,) * len(_STATUS_CALLBACKS),
38+
)
39+
40+
41+
class ProcessStatus:
42+
"""Process status tracker.
43+
44+
The class tracks process status and execute callback methods on
45+
state changes as well as replies to messagebus queries of the
46+
process status.
47+
48+
Args:
49+
name (str): process name, will be used to create the messagebus
50+
messagetype "mycroft.{name}...".
51+
bus (MessageBusClient): Connection to the Mycroft messagebus.
52+
callback_map (StatusCallbackMap): optionally, status callbacks for the
53+
various status changes.
54+
"""
55+
56+
def __init__(self, name, bus=None, callback_map=None, namespace="mycroft"):
57+
self.name = name
58+
self._namespace = namespace
59+
self.callbacks = callback_map or StatusCallbackMap()
60+
self.state = ProcessState.NOT_STARTED
61+
62+
# Messagebus connection
63+
self.bus = None
64+
if bus:
65+
self.bind(bus)
66+
67+
def bind(self, bus):
68+
self.bus = bus
69+
self._register_handlers()
70+
71+
def _register_handlers(self):
72+
"""Register messagebus handlers for status queries."""
73+
self.bus.on(f'{self._namespace}.{self.name}.is_alive', self.check_alive)
74+
self.bus.on(f'{self._namespace}.{self.name}.is_ready', self.check_ready)
75+
76+
# The next one is for backwards compatibility
77+
self.bus.on(f'mycroft.{self.name}.all_loaded', self.check_ready)
78+
79+
def check_alive(self, message=None):
80+
"""Respond to is_alive status request.
81+
82+
Args:
83+
message: Optional message to respond to, if omitted no message
84+
is sent.
85+
86+
Returns:
87+
bool, True if process is alive.
88+
"""
89+
is_alive = self.state >= ProcessState.ALIVE
90+
91+
if message:
92+
status = {'status': is_alive}
93+
self.bus.emit(message.response(data=status))
94+
95+
return is_alive
96+
97+
def check_ready(self, message=None):
98+
"""Respond to all_loaded status request.
99+
100+
Args:
101+
message: Optional message to respond to, if omitted no message
102+
is sent.
103+
104+
Returns:
105+
bool, True if process is ready.
106+
"""
107+
is_ready = self.state >= ProcessState.READY
108+
if message:
109+
status = {'status': is_ready}
110+
self.bus.emit(message.response(data=status))
111+
112+
return is_ready
113+
114+
def set_started(self):
115+
"""Process is started."""
116+
self.state = ProcessState.STARTED
117+
if self.callbacks.on_started:
118+
self.callbacks.on_started()
119+
120+
def set_alive(self):
121+
"""Basic loading is done."""
122+
self.state = ProcessState.ALIVE
123+
if self.callbacks.on_alive:
124+
self.callbacks.on_alive()
125+
126+
def set_ready(self):
127+
"""All loading is done."""
128+
self.state = ProcessState.READY
129+
if self.callbacks.on_ready:
130+
self.callbacks.on_ready()
131+
132+
def set_stopping(self):
133+
"""Process shutdown has started."""
134+
self.state = ProcessState.STOPPING
135+
if self.callbacks.on_stopping:
136+
self.callbacks.on_stopping()
137+
138+
def set_error(self, err=''):
139+
"""An error has occured and the process is non-functional."""
140+
# Intentionally leave is_started True
141+
self.state = ProcessState.ERROR
142+
if self.callbacks.on_error:
143+
self.callbacks.on_error(err)

0 commit comments

Comments
 (0)