You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

164 lines
4.7 KiB

#
# Copyright 2021 Splunk Inc.
#
# 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.
#
"""
A timer queue implementation
"""
import queue
import threading
import traceback
import warnings
from time import time
from splunktalib.common import log
from splunktalib.timer import Timer
class TimerQueue:
"""
A timer queue implementation, runs a separate thread to handle timers
"""
import sortedcontainers as sc
def __init__(self):
warnings.warn(
"This class is deprecated. "
"Please see https://github.com/splunk/addonfactory-ta-library-python/issues/38",
DeprecationWarning,
stacklevel=2,
)
self._timers = TimerQueue.sc.SortedSet()
self._cancelling_timers = {}
self._lock = threading.Lock()
self._wakeup_queue = queue.Queue()
self._thr = threading.Thread(target=self._check_and_execute)
self._started = False
def start(self):
"""
Start the timer queue to make it start function
"""
if self._started:
return
self._started = True
self._thr.start()
log.logger.info("TimerQueue started.")
def tear_down(self):
if not self._started:
return
self._started = True
self._wakeup(None)
self._thr.join()
def add_timer(self, callback, when, interval):
"""
Add timer to the queue
"""
timer = Timer(callback, when, interval)
with self._lock:
self._timers.add(timer)
self._wakeup()
return timer
def remove_timer(self, timer):
"""
Remove timer from the queue.
"""
with self._lock:
try:
self._timers.remove(timer)
except ValueError:
log.logger.info(
"Timer=%s is not in queue, move it to cancelling " "list",
timer.ident(),
)
else:
self._cancelling_timers[timer.ident()] = timer
def _check_and_execute(self):
wakeup_queue = self._wakeup_queue
while 1:
(next_expired_time, expired_timers) = self._get_expired_timers()
for timer in expired_timers:
try:
timer()
except Exception:
log.logger.error(traceback.format_exc())
self._reset_timers(expired_timers)
# Calc sleep time
if next_expired_time:
now = time()
if now < next_expired_time:
sleep_time = next_expired_time - now
else:
sleep_time = 0.1
else:
sleep_time = 1
try:
wakeup = wakeup_queue.get(timeout=sleep_time)
if wakeup is None:
break
except queue.Empty:
pass
log.logger.info("TimerQueue stopped.")
def _get_expired_timers(self):
next_expired_time = 0
now = time()
expired_timers = []
with self._lock:
for timer in self._timers:
if timer.get_expiration() <= now:
expired_timers.append(timer)
if expired_timers:
del self._timers[: len(expired_timers)]
if self._timers:
next_expired_time = self._timers[0].get_expiration()
return (next_expired_time, expired_timers)
def _reset_timers(self, expired_timers):
has_new_timer = False
with self._lock:
cancelling_timers = self._cancelling_timers
for timer in expired_timers:
if timer.ident() in cancelling_timers:
log.logger.INFO("Timer=%s has been cancelled", timer.ident())
continue
elif timer.get_interval():
# Repeated timer
timer.update_expiration()
self._timers.add(timer)
has_new_timer = True
cancelling_timers.clear()
if has_new_timer:
self._wakeup()
def _wakeup(self, something="not_None"):
self._wakeup_queue.put(something)