1# Copyright 2015-2021 Espressif Systems (Shanghai) CO LTD
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import threading
16from typing import Optional
17
18
19class StoppableThread(object):
20    """
21    Provide a Thread-like class which can be 'cancelled' via a subclass-provided
22    cancellation method.
23
24    Can be started and stopped multiple times.
25
26    Isn't an instance of type Thread because Python Thread objects can only be run once
27    """
28
29    def __init__(self):
30        #  type: () -> None
31        self._thread = None  # type: Optional[threading.Thread]
32
33    @property
34    def alive(self):
35        #  type: () -> bool
36        """
37        Is 'alive' whenever the internal thread object exists
38        """
39        return self._thread is not None
40
41    def start(self):
42        #  type: () -> None
43        if self._thread is None:
44            self._thread = threading.Thread(target=self._run_outer)
45            self._thread.start()
46
47    def _cancel(self):
48        #  type: () -> None
49        pass  # override to provide cancellation functionality
50
51    def run(self):
52        #  type: () -> None
53        pass  # override for the main thread behaviour
54
55    def _run_outer(self):
56        #  type: () -> None
57        try:
58            self.run()
59        finally:
60            self._thread = None
61
62    def stop(self):
63        #  type: () -> None
64        if self._thread is not None:
65            old_thread = self._thread
66            self._thread = None
67            self._cancel()
68            old_thread.join()
69