openrvdas.logger.readers.timeout_reader

No module-level documentation available.
  1#!/usr/bin/env python3
  2
  3import threading
  4import time
  5
  6from logger.readers.reader import Reader  # noqa: E402
  7
  8
  9##############################
 10class ReaderTimeout(StopIteration):
 11    """A custom exception we can raise when we hit timeout."""
 12    pass
 13
 14
 15################################################################################
 16class TimeoutReader(Reader):
 17    """Instantiated with a client Reader instance (such as a
 18    NetworkReader), an interval, a timeout and optional message. When its
 19    read() method is called, it iteratively calls its passed reader's
 20    read() method every interval seconds, discarding the received
 21    output. It only returns if/when the client reader fails to return a
 22    record within timeout seconds, in which case it returns either the
 23    passed timeout message or a default one, warning that no records have
 24    been received within the specified timeout.
 25
 26    In general, it's better if you can structure your logger configuration
 27    so that it uses TimeoutWriters rather than TimeoutReaders. The former
 28    are more robust and less computationally intensive.
 29    """
 30    ############################
 31
 32    def __init__(self, reader, timeout, message=None, resume_message=None,
 33                 empty_is_okay=False, none_is_okay=False, **kwargs):
 34        """
 35        ```
 36        reader         A client reader instance
 37
 38        timeout        Timeout interval in seconds
 39
 40        message        Message to be returned if client reader fails to return
 41                       a record within the timeout interval
 42
 43        resume_message Message to be returned when client returns a record after
 44                       having timed out
 45
 46        empty_is_okay If True, receiving an empty record is sufficient to reset
 47                      the timer.
 48        none_is_okay  If True, receiving a 'None' record is sufficient to reset
 49                        the timer.
 50        ```
 51        Sample:
 52        ```
 53        gyr1_reader = ComposedReader(NetworkReader(':6224'),
 54                                     RegexFilterTransform('^gyr1'))
 55        reader = TimeoutReader(reader=gyr1_reader,
 56                               timeout=15,
 57                               message='No Gyroscope records received for 15 seconds')
 58        ```
 59        """
 60        super().__init__(**kwargs)
 61
 62        self.reader = reader
 63        self.timeout = timeout
 64        self.message = message or ('Timeout: no %s record received in %d seconds'
 65                                   % (reader, timeout))
 66        self.resume_message = resume_message or ('Timeout: %s record received'
 67                                                 % reader)
 68        self.empty_is_okay = empty_is_okay
 69        self.none_is_okay = none_is_okay
 70
 71        # When we got our last record (or were instantiated)
 72        self.last_record = time.time()
 73
 74        # Keep track of whether we're currently timed out or not
 75        self.timed_out = False
 76
 77        # Protect self.last_record and self.timed_out
 78        self.timeout_lock = threading.Lock()
 79
 80        # To let us cleanly exit _timeout_thread; this gets set to False
 81        # when our read() method is called.
 82        self.keep_reading = False
 83
 84        # Placeholder for the timeout loop we'll run in a separate thread
 85        # when called.
 86        self.timeout_thread = None
 87
 88    ############################
 89    def __del__(self):
 90        self.quit()
 91
 92    ############################
 93    def quit(self):
 94        self.keep_reading = False
 95
 96    ############################
 97    def _timeout_thread(self):
 98        """Repeatedly call the client read() method, and keep track of when we
 99        get records from it.
100        """
101        while self.keep_reading:
102            # Loop until we get a record that matches our standards
103            record = None
104            while not record:
105                record = self.reader.read()
106                if self.empty_is_okay:
107                    break
108                if record is None and self.none_is_okay:
109                    break
110
111            # We've got a record!
112            with self.timeout_lock:
113                self.last_record = time.time()
114                # If we were timed out, we aren't anymore, and the read will
115                # return. Stop trying to read from our client (until we get
116                # another read() request).
117                if self.timed_out:
118                    self.keep_reading = False
119
120    ############################
121    def read(self):
122        """Block and return either a 'timeout' message or a 'resumed' message,
123        only when our client reader either hasn't given us a record in N
124        seconds or has resumed giving us records after having timed out.
125        """
126        # Don't sleep the full interval because we want to quickly catch
127        # if we get a 'resume'
128        max_sleep_interval = 1  # Don't sleep more than one second
129
130        # We want the timeout_thread to start calling records for us.
131        self.keep_reading = True
132
133        # Set our 'last_record' to 'now' so that we start counting toward
134        # the timeout from now
135        with self.timeout_lock:
136            self.last_record = time.time()
137
138        # Start up timeout_thread if it's not running
139        if not self.timeout_thread or not self.timeout_thread.is_alive():
140            self.timeout_thread = threading.Thread(target=self._timeout_thread,
141                                                   name='timeout_thread', daemon=True)
142            self.timeout_thread.start()
143
144        # Loop until 1) we've timed out, 2) we've resumed after a timeout,
145        # or 3) we've been told to quit
146        while self.keep_reading:
147            now = time.time()
148            with self.timeout_lock:
149                time_since_last_record = now - self.last_record
150
151                # If we're timed out and we've seen a record inside our
152                # timeout window, we're not timed out anymore. Return a
153                # 'resumed' message and stop trying to read.
154                if self.timed_out and time_since_last_record < self.timeout:
155                    self.timed_out = False
156                    self.keep_reading = False
157                    return self.resume_message
158
159                # Otherwise, figure how long to sleep before we need to see
160                # our next record.
161                time_to_sleep = self.timeout - time_since_last_record
162
163                # If we're overdue for a record...
164                if time_to_sleep < 0:
165                    # If we weren't already timed out, we are now. Send message.
166                    if not self.timed_out:
167                        self.timed_out = True
168                        self.keep_reading = False
169                        return self.message
170
171                    # Check again in a little while
172                    time_to_sleep = max_sleep_interval
173
174            # Whether or not we're timed out, snooze a bit before checking
175            # again.
176            time.sleep(min(time_to_sleep, max_sleep_interval))
class ReaderTimeout(builtins.StopIteration):
11class ReaderTimeout(StopIteration):
12    """A custom exception we can raise when we hit timeout."""
13    pass

A custom exception we can raise when we hit timeout.

class TimeoutReader(logger.readers.reader.Reader):
 17class TimeoutReader(Reader):
 18    """Instantiated with a client Reader instance (such as a
 19    NetworkReader), an interval, a timeout and optional message. When its
 20    read() method is called, it iteratively calls its passed reader's
 21    read() method every interval seconds, discarding the received
 22    output. It only returns if/when the client reader fails to return a
 23    record within timeout seconds, in which case it returns either the
 24    passed timeout message or a default one, warning that no records have
 25    been received within the specified timeout.
 26
 27    In general, it's better if you can structure your logger configuration
 28    so that it uses TimeoutWriters rather than TimeoutReaders. The former
 29    are more robust and less computationally intensive.
 30    """
 31    ############################
 32
 33    def __init__(self, reader, timeout, message=None, resume_message=None,
 34                 empty_is_okay=False, none_is_okay=False, **kwargs):
 35        """
 36        ```
 37        reader         A client reader instance
 38
 39        timeout        Timeout interval in seconds
 40
 41        message        Message to be returned if client reader fails to return
 42                       a record within the timeout interval
 43
 44        resume_message Message to be returned when client returns a record after
 45                       having timed out
 46
 47        empty_is_okay If True, receiving an empty record is sufficient to reset
 48                      the timer.
 49        none_is_okay  If True, receiving a 'None' record is sufficient to reset
 50                        the timer.
 51        ```
 52        Sample:
 53        ```
 54        gyr1_reader = ComposedReader(NetworkReader(':6224'),
 55                                     RegexFilterTransform('^gyr1'))
 56        reader = TimeoutReader(reader=gyr1_reader,
 57                               timeout=15,
 58                               message='No Gyroscope records received for 15 seconds')
 59        ```
 60        """
 61        super().__init__(**kwargs)
 62
 63        self.reader = reader
 64        self.timeout = timeout
 65        self.message = message or ('Timeout: no %s record received in %d seconds'
 66                                   % (reader, timeout))
 67        self.resume_message = resume_message or ('Timeout: %s record received'
 68                                                 % reader)
 69        self.empty_is_okay = empty_is_okay
 70        self.none_is_okay = none_is_okay
 71
 72        # When we got our last record (or were instantiated)
 73        self.last_record = time.time()
 74
 75        # Keep track of whether we're currently timed out or not
 76        self.timed_out = False
 77
 78        # Protect self.last_record and self.timed_out
 79        self.timeout_lock = threading.Lock()
 80
 81        # To let us cleanly exit _timeout_thread; this gets set to False
 82        # when our read() method is called.
 83        self.keep_reading = False
 84
 85        # Placeholder for the timeout loop we'll run in a separate thread
 86        # when called.
 87        self.timeout_thread = None
 88
 89    ############################
 90    def __del__(self):
 91        self.quit()
 92
 93    ############################
 94    def quit(self):
 95        self.keep_reading = False
 96
 97    ############################
 98    def _timeout_thread(self):
 99        """Repeatedly call the client read() method, and keep track of when we
100        get records from it.
101        """
102        while self.keep_reading:
103            # Loop until we get a record that matches our standards
104            record = None
105            while not record:
106                record = self.reader.read()
107                if self.empty_is_okay:
108                    break
109                if record is None and self.none_is_okay:
110                    break
111
112            # We've got a record!
113            with self.timeout_lock:
114                self.last_record = time.time()
115                # If we were timed out, we aren't anymore, and the read will
116                # return. Stop trying to read from our client (until we get
117                # another read() request).
118                if self.timed_out:
119                    self.keep_reading = False
120
121    ############################
122    def read(self):
123        """Block and return either a 'timeout' message or a 'resumed' message,
124        only when our client reader either hasn't given us a record in N
125        seconds or has resumed giving us records after having timed out.
126        """
127        # Don't sleep the full interval because we want to quickly catch
128        # if we get a 'resume'
129        max_sleep_interval = 1  # Don't sleep more than one second
130
131        # We want the timeout_thread to start calling records for us.
132        self.keep_reading = True
133
134        # Set our 'last_record' to 'now' so that we start counting toward
135        # the timeout from now
136        with self.timeout_lock:
137            self.last_record = time.time()
138
139        # Start up timeout_thread if it's not running
140        if not self.timeout_thread or not self.timeout_thread.is_alive():
141            self.timeout_thread = threading.Thread(target=self._timeout_thread,
142                                                   name='timeout_thread', daemon=True)
143            self.timeout_thread.start()
144
145        # Loop until 1) we've timed out, 2) we've resumed after a timeout,
146        # or 3) we've been told to quit
147        while self.keep_reading:
148            now = time.time()
149            with self.timeout_lock:
150                time_since_last_record = now - self.last_record
151
152                # If we're timed out and we've seen a record inside our
153                # timeout window, we're not timed out anymore. Return a
154                # 'resumed' message and stop trying to read.
155                if self.timed_out and time_since_last_record < self.timeout:
156                    self.timed_out = False
157                    self.keep_reading = False
158                    return self.resume_message
159
160                # Otherwise, figure how long to sleep before we need to see
161                # our next record.
162                time_to_sleep = self.timeout - time_since_last_record
163
164                # If we're overdue for a record...
165                if time_to_sleep < 0:
166                    # If we weren't already timed out, we are now. Send message.
167                    if not self.timed_out:
168                        self.timed_out = True
169                        self.keep_reading = False
170                        return self.message
171
172                    # Check again in a little while
173                    time_to_sleep = max_sleep_interval
174
175            # Whether or not we're timed out, snooze a bit before checking
176            # again.
177            time.sleep(min(time_to_sleep, max_sleep_interval))

Instantiated with a client Reader instance (such as a NetworkReader), an interval, a timeout and optional message. When its read() method is called, it iteratively calls its passed reader's read() method every interval seconds, discarding the received output. It only returns if/when the client reader fails to return a record within timeout seconds, in which case it returns either the passed timeout message or a default one, warning that no records have been received within the specified timeout.

In general, it's better if you can structure your logger configuration so that it uses TimeoutWriters rather than TimeoutReaders. The former are more robust and less computationally intensive.

TimeoutReader( reader, timeout, message=None, resume_message=None, empty_is_okay=False, none_is_okay=False, **kwargs)
33    def __init__(self, reader, timeout, message=None, resume_message=None,
34                 empty_is_okay=False, none_is_okay=False, **kwargs):
35        """
36        ```
37        reader         A client reader instance
38
39        timeout        Timeout interval in seconds
40
41        message        Message to be returned if client reader fails to return
42                       a record within the timeout interval
43
44        resume_message Message to be returned when client returns a record after
45                       having timed out
46
47        empty_is_okay If True, receiving an empty record is sufficient to reset
48                      the timer.
49        none_is_okay  If True, receiving a 'None' record is sufficient to reset
50                        the timer.
51        ```
52        Sample:
53        ```
54        gyr1_reader = ComposedReader(NetworkReader(':6224'),
55                                     RegexFilterTransform('^gyr1'))
56        reader = TimeoutReader(reader=gyr1_reader,
57                               timeout=15,
58                               message='No Gyroscope records received for 15 seconds')
59        ```
60        """
61        super().__init__(**kwargs)
62
63        self.reader = reader
64        self.timeout = timeout
65        self.message = message or ('Timeout: no %s record received in %d seconds'
66                                   % (reader, timeout))
67        self.resume_message = resume_message or ('Timeout: %s record received'
68                                                 % reader)
69        self.empty_is_okay = empty_is_okay
70        self.none_is_okay = none_is_okay
71
72        # When we got our last record (or were instantiated)
73        self.last_record = time.time()
74
75        # Keep track of whether we're currently timed out or not
76        self.timed_out = False
77
78        # Protect self.last_record and self.timed_out
79        self.timeout_lock = threading.Lock()
80
81        # To let us cleanly exit _timeout_thread; this gets set to False
82        # when our read() method is called.
83        self.keep_reading = False
84
85        # Placeholder for the timeout loop we'll run in a separate thread
86        # when called.
87        self.timeout_thread = None
reader         A client reader instance

timeout        Timeout interval in seconds

message        Message to be returned if client reader fails to return
               a record within the timeout interval

resume_message Message to be returned when client returns a record after
               having timed out

empty_is_okay If True, receiving an empty record is sufficient to reset
              the timer.
none_is_okay  If True, receiving a 'None' record is sufficient to reset
                the timer.

Sample:

gyr1_reader = ComposedReader(NetworkReader(':6224'),
                             RegexFilterTransform('^gyr1'))
reader = TimeoutReader(reader=gyr1_reader,
                       timeout=15,
                       message='No Gyroscope records received for 15 seconds')
reader
timeout
message
resume_message
empty_is_okay
none_is_okay
last_record
timed_out
timeout_lock
keep_reading
timeout_thread
def quit(self):
94    def quit(self):
95        self.keep_reading = False
def read(self):
122    def read(self):
123        """Block and return either a 'timeout' message or a 'resumed' message,
124        only when our client reader either hasn't given us a record in N
125        seconds or has resumed giving us records after having timed out.
126        """
127        # Don't sleep the full interval because we want to quickly catch
128        # if we get a 'resume'
129        max_sleep_interval = 1  # Don't sleep more than one second
130
131        # We want the timeout_thread to start calling records for us.
132        self.keep_reading = True
133
134        # Set our 'last_record' to 'now' so that we start counting toward
135        # the timeout from now
136        with self.timeout_lock:
137            self.last_record = time.time()
138
139        # Start up timeout_thread if it's not running
140        if not self.timeout_thread or not self.timeout_thread.is_alive():
141            self.timeout_thread = threading.Thread(target=self._timeout_thread,
142                                                   name='timeout_thread', daemon=True)
143            self.timeout_thread.start()
144
145        # Loop until 1) we've timed out, 2) we've resumed after a timeout,
146        # or 3) we've been told to quit
147        while self.keep_reading:
148            now = time.time()
149            with self.timeout_lock:
150                time_since_last_record = now - self.last_record
151
152                # If we're timed out and we've seen a record inside our
153                # timeout window, we're not timed out anymore. Return a
154                # 'resumed' message and stop trying to read.
155                if self.timed_out and time_since_last_record < self.timeout:
156                    self.timed_out = False
157                    self.keep_reading = False
158                    return self.resume_message
159
160                # Otherwise, figure how long to sleep before we need to see
161                # our next record.
162                time_to_sleep = self.timeout - time_since_last_record
163
164                # If we're overdue for a record...
165                if time_to_sleep < 0:
166                    # If we weren't already timed out, we are now. Send message.
167                    if not self.timed_out:
168                        self.timed_out = True
169                        self.keep_reading = False
170                        return self.message
171
172                    # Check again in a little while
173                    time_to_sleep = max_sleep_interval
174
175            # Whether or not we're timed out, snooze a bit before checking
176            # again.
177            time.sleep(min(time_to_sleep, max_sleep_interval))

Block and return either a 'timeout' message or a 'resumed' message, only when our client reader either hasn't given us a record in N seconds or has resumed giving us records after having timed out.