2017-10-04 06:28:44 +00:00
|
|
|
# Copyright 2017 Mycroft AI 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.
|
|
|
|
#
|
2017-08-30 17:19:06 +00:00
|
|
|
import json
|
2017-09-18 19:14:21 +00:00
|
|
|
import time
|
Fix named event scheduling/deleting (#1705)
While working on the Alarm skill I discovered several issues with the
event scheduler. This PR cleans up those findings and resolves several
other potential issues:
1) To avoid thread synchronization issues, the EventScheduler had several
queues which independently held objects to be added/deleted/updated. However, the order of the events was undefined and got mixed since they were all batched together. So, for instance, if skill code performed:
self.add_event("foo", self.handle_foo)
if SomeReason:
self.cancel_event("foo")
The actual order of queue handling would perform Remove first, then Add which resulted in "foo" not being found for delete, but then added and left as an active event.
Now the EventScheduler protects the list using a Lock and the queues have been removed. Modifications to the list happen immediately after obtaining the lock and are not batched up.
2) One-time events were triggered while the event was still in the EventScheduler list. Now the entry is removed before invoking the handler.
3) Within the MycroftSkill.add_event(name, handler) is a local 'wrapper' method that actually makes the callback. The MycroftSkill.remove_event(name) method attempted to find entries in the events list and the associated handler entries in the self.emitter to remove. However, the emitter actually held the wrapper(handler), not the handler itself. So the emitter handlers were left behind.
This was a quiet bug until the next time you scheduled an event of the same name. When that second event finally triggered, it would fire off both the new and the old handler -- which snowballed in the 'skill-alarm:Beep' case, doubling and redoubling with every beep.
Now this cancels all the emitter listeners by name. There is a very slim chance that someone has registered a listener with the same name, but since it is namespaced to "skill-name:Event" I think this is pretty safe.
Not technically related, but a failure that has been lurking for
some time and is a French unit test that doesn't work depending
on the time of day when the test is run.
2018-07-30 20:08:13 +00:00
|
|
|
from threading import Thread, Lock
|
2017-08-30 17:19:06 +00:00
|
|
|
|
2018-05-23 20:03:01 +00:00
|
|
|
from os.path import isfile, join, expanduser
|
2017-08-30 17:19:06 +00:00
|
|
|
|
2018-05-15 16:46:44 +00:00
|
|
|
from mycroft.configuration import Configuration
|
2017-09-18 19:14:21 +00:00
|
|
|
from mycroft.messagebus.message import Message
|
|
|
|
from mycroft.util.log import LOG
|
2017-08-30 17:19:06 +00:00
|
|
|
|
|
|
|
|
2018-02-27 22:37:29 +00:00
|
|
|
def repeat_time(sched_time, repeat):
|
Fix named event scheduling/deleting (#1705)
While working on the Alarm skill I discovered several issues with the
event scheduler. This PR cleans up those findings and resolves several
other potential issues:
1) To avoid thread synchronization issues, the EventScheduler had several
queues which independently held objects to be added/deleted/updated. However, the order of the events was undefined and got mixed since they were all batched together. So, for instance, if skill code performed:
self.add_event("foo", self.handle_foo)
if SomeReason:
self.cancel_event("foo")
The actual order of queue handling would perform Remove first, then Add which resulted in "foo" not being found for delete, but then added and left as an active event.
Now the EventScheduler protects the list using a Lock and the queues have been removed. Modifications to the list happen immediately after obtaining the lock and are not batched up.
2) One-time events were triggered while the event was still in the EventScheduler list. Now the entry is removed before invoking the handler.
3) Within the MycroftSkill.add_event(name, handler) is a local 'wrapper' method that actually makes the callback. The MycroftSkill.remove_event(name) method attempted to find entries in the events list and the associated handler entries in the self.emitter to remove. However, the emitter actually held the wrapper(handler), not the handler itself. So the emitter handlers were left behind.
This was a quiet bug until the next time you scheduled an event of the same name. When that second event finally triggered, it would fire off both the new and the old handler -- which snowballed in the 'skill-alarm:Beep' case, doubling and redoubling with every beep.
Now this cancels all the emitter listeners by name. There is a very slim chance that someone has registered a listener with the same name, but since it is namespaced to "skill-name:Event" I think this is pretty safe.
Not technically related, but a failure that has been lurking for
some time and is a French unit test that doesn't work depending
on the time of day when the test is run.
2018-07-30 20:08:13 +00:00
|
|
|
"""Next scheduled time for repeating event. Guarantees that the
|
|
|
|
time is not in the past (but could skip interim events)
|
2018-02-27 22:37:29 +00:00
|
|
|
|
|
|
|
Args:
|
|
|
|
sched_time (float): Scheduled unix time for the event
|
|
|
|
repeat (float): Repeat period in seconds
|
|
|
|
|
|
|
|
Returns: (float) time for next event
|
|
|
|
"""
|
|
|
|
next_time = sched_time + repeat
|
Fix named event scheduling/deleting (#1705)
While working on the Alarm skill I discovered several issues with the
event scheduler. This PR cleans up those findings and resolves several
other potential issues:
1) To avoid thread synchronization issues, the EventScheduler had several
queues which independently held objects to be added/deleted/updated. However, the order of the events was undefined and got mixed since they were all batched together. So, for instance, if skill code performed:
self.add_event("foo", self.handle_foo)
if SomeReason:
self.cancel_event("foo")
The actual order of queue handling would perform Remove first, then Add which resulted in "foo" not being found for delete, but then added and left as an active event.
Now the EventScheduler protects the list using a Lock and the queues have been removed. Modifications to the list happen immediately after obtaining the lock and are not batched up.
2) One-time events were triggered while the event was still in the EventScheduler list. Now the entry is removed before invoking the handler.
3) Within the MycroftSkill.add_event(name, handler) is a local 'wrapper' method that actually makes the callback. The MycroftSkill.remove_event(name) method attempted to find entries in the events list and the associated handler entries in the self.emitter to remove. However, the emitter actually held the wrapper(handler), not the handler itself. So the emitter handlers were left behind.
This was a quiet bug until the next time you scheduled an event of the same name. When that second event finally triggered, it would fire off both the new and the old handler -- which snowballed in the 'skill-alarm:Beep' case, doubling and redoubling with every beep.
Now this cancels all the emitter listeners by name. There is a very slim chance that someone has registered a listener with the same name, but since it is namespaced to "skill-name:Event" I think this is pretty safe.
Not technically related, but a failure that has been lurking for
some time and is a French unit test that doesn't work depending
on the time of day when the test is run.
2018-07-30 20:08:13 +00:00
|
|
|
while next_time < time.time():
|
2018-02-27 22:37:29 +00:00
|
|
|
# Schedule at an offset to assure no doubles
|
Fix named event scheduling/deleting (#1705)
While working on the Alarm skill I discovered several issues with the
event scheduler. This PR cleans up those findings and resolves several
other potential issues:
1) To avoid thread synchronization issues, the EventScheduler had several
queues which independently held objects to be added/deleted/updated. However, the order of the events was undefined and got mixed since they were all batched together. So, for instance, if skill code performed:
self.add_event("foo", self.handle_foo)
if SomeReason:
self.cancel_event("foo")
The actual order of queue handling would perform Remove first, then Add which resulted in "foo" not being found for delete, but then added and left as an active event.
Now the EventScheduler protects the list using a Lock and the queues have been removed. Modifications to the list happen immediately after obtaining the lock and are not batched up.
2) One-time events were triggered while the event was still in the EventScheduler list. Now the entry is removed before invoking the handler.
3) Within the MycroftSkill.add_event(name, handler) is a local 'wrapper' method that actually makes the callback. The MycroftSkill.remove_event(name) method attempted to find entries in the events list and the associated handler entries in the self.emitter to remove. However, the emitter actually held the wrapper(handler), not the handler itself. So the emitter handlers were left behind.
This was a quiet bug until the next time you scheduled an event of the same name. When that second event finally triggered, it would fire off both the new and the old handler -- which snowballed in the 'skill-alarm:Beep' case, doubling and redoubling with every beep.
Now this cancels all the emitter listeners by name. There is a very slim chance that someone has registered a listener with the same name, but since it is namespaced to "skill-name:Event" I think this is pretty safe.
Not technically related, but a failure that has been lurking for
some time and is a French unit test that doesn't work depending
on the time of day when the test is run.
2018-07-30 20:08:13 +00:00
|
|
|
next_time = time.time() + abs(repeat)
|
2018-02-27 22:37:29 +00:00
|
|
|
return next_time
|
|
|
|
|
|
|
|
|
2017-08-30 17:19:06 +00:00
|
|
|
class EventScheduler(Thread):
|
2018-08-22 01:50:50 +00:00
|
|
|
def __init__(self, bus, schedule_file='schedule.json'):
|
2017-10-31 09:50:12 +00:00
|
|
|
"""
|
|
|
|
Create an event scheduler thread. Will send messages at a
|
|
|
|
predetermined time to the registered targets.
|
|
|
|
|
|
|
|
Args:
|
2018-08-22 01:50:50 +00:00
|
|
|
bus: Mycroft messagebus (mycroft.messagebus)
|
2017-10-31 09:50:12 +00:00
|
|
|
schedule_file: File to store pending events to on shutdown
|
|
|
|
"""
|
2017-08-30 17:19:06 +00:00
|
|
|
super(EventScheduler, self).__init__()
|
2018-05-23 20:03:01 +00:00
|
|
|
data_dir = expanduser(Configuration.get()['data_dir'])
|
2018-05-15 16:46:44 +00:00
|
|
|
|
2017-08-30 17:19:06 +00:00
|
|
|
self.events = {}
|
Fix named event scheduling/deleting (#1705)
While working on the Alarm skill I discovered several issues with the
event scheduler. This PR cleans up those findings and resolves several
other potential issues:
1) To avoid thread synchronization issues, the EventScheduler had several
queues which independently held objects to be added/deleted/updated. However, the order of the events was undefined and got mixed since they were all batched together. So, for instance, if skill code performed:
self.add_event("foo", self.handle_foo)
if SomeReason:
self.cancel_event("foo")
The actual order of queue handling would perform Remove first, then Add which resulted in "foo" not being found for delete, but then added and left as an active event.
Now the EventScheduler protects the list using a Lock and the queues have been removed. Modifications to the list happen immediately after obtaining the lock and are not batched up.
2) One-time events were triggered while the event was still in the EventScheduler list. Now the entry is removed before invoking the handler.
3) Within the MycroftSkill.add_event(name, handler) is a local 'wrapper' method that actually makes the callback. The MycroftSkill.remove_event(name) method attempted to find entries in the events list and the associated handler entries in the self.emitter to remove. However, the emitter actually held the wrapper(handler), not the handler itself. So the emitter handlers were left behind.
This was a quiet bug until the next time you scheduled an event of the same name. When that second event finally triggered, it would fire off both the new and the old handler -- which snowballed in the 'skill-alarm:Beep' case, doubling and redoubling with every beep.
Now this cancels all the emitter listeners by name. There is a very slim chance that someone has registered a listener with the same name, but since it is namespaced to "skill-name:Event" I think this is pretty safe.
Not technically related, but a failure that has been lurking for
some time and is a French unit test that doesn't work depending
on the time of day when the test is run.
2018-07-30 20:08:13 +00:00
|
|
|
self.event_lock = Lock()
|
|
|
|
|
2018-08-22 01:50:50 +00:00
|
|
|
self.bus = bus
|
2017-08-30 17:19:06 +00:00
|
|
|
self.isRunning = True
|
2018-05-15 16:46:44 +00:00
|
|
|
self.schedule_file = join(data_dir, schedule_file)
|
2017-08-30 17:19:06 +00:00
|
|
|
if self.schedule_file:
|
|
|
|
self.load()
|
|
|
|
|
2018-08-22 01:50:50 +00:00
|
|
|
self.bus.on('mycroft.scheduler.schedule_event',
|
|
|
|
self.schedule_event_handler)
|
|
|
|
self.bus.on('mycroft.scheduler.remove_event',
|
|
|
|
self.remove_event_handler)
|
|
|
|
self.bus.on('mycroft.scheduler.update_event',
|
|
|
|
self.update_event_handler)
|
|
|
|
self.bus.on('mycroft.scheduler.get_event',
|
|
|
|
self.get_event_handler)
|
2017-08-30 17:19:06 +00:00
|
|
|
self.start()
|
|
|
|
|
|
|
|
def load(self):
|
|
|
|
"""
|
2017-09-01 20:41:06 +00:00
|
|
|
Load json data with active events from json file.
|
2017-08-30 17:19:06 +00:00
|
|
|
"""
|
|
|
|
if isfile(self.schedule_file):
|
2017-09-25 12:15:12 +00:00
|
|
|
json_data = {}
|
2017-08-30 17:19:06 +00:00
|
|
|
with open(self.schedule_file) as f:
|
|
|
|
try:
|
|
|
|
json_data = json.load(f)
|
|
|
|
except Exception as e:
|
2018-05-15 16:46:44 +00:00
|
|
|
LOG.error(e)
|
2017-08-30 17:19:06 +00:00
|
|
|
current_time = time.time()
|
Fix named event scheduling/deleting (#1705)
While working on the Alarm skill I discovered several issues with the
event scheduler. This PR cleans up those findings and resolves several
other potential issues:
1) To avoid thread synchronization issues, the EventScheduler had several
queues which independently held objects to be added/deleted/updated. However, the order of the events was undefined and got mixed since they were all batched together. So, for instance, if skill code performed:
self.add_event("foo", self.handle_foo)
if SomeReason:
self.cancel_event("foo")
The actual order of queue handling would perform Remove first, then Add which resulted in "foo" not being found for delete, but then added and left as an active event.
Now the EventScheduler protects the list using a Lock and the queues have been removed. Modifications to the list happen immediately after obtaining the lock and are not batched up.
2) One-time events were triggered while the event was still in the EventScheduler list. Now the entry is removed before invoking the handler.
3) Within the MycroftSkill.add_event(name, handler) is a local 'wrapper' method that actually makes the callback. The MycroftSkill.remove_event(name) method attempted to find entries in the events list and the associated handler entries in the self.emitter to remove. However, the emitter actually held the wrapper(handler), not the handler itself. So the emitter handlers were left behind.
This was a quiet bug until the next time you scheduled an event of the same name. When that second event finally triggered, it would fire off both the new and the old handler -- which snowballed in the 'skill-alarm:Beep' case, doubling and redoubling with every beep.
Now this cancels all the emitter listeners by name. There is a very slim chance that someone has registered a listener with the same name, but since it is namespaced to "skill-name:Event" I think this is pretty safe.
Not technically related, but a failure that has been lurking for
some time and is a French unit test that doesn't work depending
on the time of day when the test is run.
2018-07-30 20:08:13 +00:00
|
|
|
with self.event_lock:
|
|
|
|
for key in json_data:
|
|
|
|
event_list = json_data[key]
|
|
|
|
# discard non repeating events that has already happened
|
|
|
|
self.events[key] = [tuple(e) for e in event_list
|
|
|
|
if e[0] > current_time or e[1]]
|
|
|
|
|
|
|
|
def run(self):
|
|
|
|
while self.isRunning:
|
|
|
|
self.check_state()
|
|
|
|
time.sleep(0.5)
|
2017-08-30 17:19:06 +00:00
|
|
|
|
Fix named event scheduling/deleting (#1705)
While working on the Alarm skill I discovered several issues with the
event scheduler. This PR cleans up those findings and resolves several
other potential issues:
1) To avoid thread synchronization issues, the EventScheduler had several
queues which independently held objects to be added/deleted/updated. However, the order of the events was undefined and got mixed since they were all batched together. So, for instance, if skill code performed:
self.add_event("foo", self.handle_foo)
if SomeReason:
self.cancel_event("foo")
The actual order of queue handling would perform Remove first, then Add which resulted in "foo" not being found for delete, but then added and left as an active event.
Now the EventScheduler protects the list using a Lock and the queues have been removed. Modifications to the list happen immediately after obtaining the lock and are not batched up.
2) One-time events were triggered while the event was still in the EventScheduler list. Now the entry is removed before invoking the handler.
3) Within the MycroftSkill.add_event(name, handler) is a local 'wrapper' method that actually makes the callback. The MycroftSkill.remove_event(name) method attempted to find entries in the events list and the associated handler entries in the self.emitter to remove. However, the emitter actually held the wrapper(handler), not the handler itself. So the emitter handlers were left behind.
This was a quiet bug until the next time you scheduled an event of the same name. When that second event finally triggered, it would fire off both the new and the old handler -- which snowballed in the 'skill-alarm:Beep' case, doubling and redoubling with every beep.
Now this cancels all the emitter listeners by name. There is a very slim chance that someone has registered a listener with the same name, but since it is namespaced to "skill-name:Event" I think this is pretty safe.
Not technically related, but a failure that has been lurking for
some time and is a French unit test that doesn't work depending
on the time of day when the test is run.
2018-07-30 20:08:13 +00:00
|
|
|
def check_state(self):
|
|
|
|
"""
|
|
|
|
Check if an event should be triggered.
|
2017-08-30 17:19:06 +00:00
|
|
|
"""
|
Fix named event scheduling/deleting (#1705)
While working on the Alarm skill I discovered several issues with the
event scheduler. This PR cleans up those findings and resolves several
other potential issues:
1) To avoid thread synchronization issues, the EventScheduler had several
queues which independently held objects to be added/deleted/updated. However, the order of the events was undefined and got mixed since they were all batched together. So, for instance, if skill code performed:
self.add_event("foo", self.handle_foo)
if SomeReason:
self.cancel_event("foo")
The actual order of queue handling would perform Remove first, then Add which resulted in "foo" not being found for delete, but then added and left as an active event.
Now the EventScheduler protects the list using a Lock and the queues have been removed. Modifications to the list happen immediately after obtaining the lock and are not batched up.
2) One-time events were triggered while the event was still in the EventScheduler list. Now the entry is removed before invoking the handler.
3) Within the MycroftSkill.add_event(name, handler) is a local 'wrapper' method that actually makes the callback. The MycroftSkill.remove_event(name) method attempted to find entries in the events list and the associated handler entries in the self.emitter to remove. However, the emitter actually held the wrapper(handler), not the handler itself. So the emitter handlers were left behind.
This was a quiet bug until the next time you scheduled an event of the same name. When that second event finally triggered, it would fire off both the new and the old handler -- which snowballed in the 'skill-alarm:Beep' case, doubling and redoubling with every beep.
Now this cancels all the emitter listeners by name. There is a very slim chance that someone has registered a listener with the same name, but since it is namespaced to "skill-name:Event" I think this is pretty safe.
Not technically related, but a failure that has been lurking for
some time and is a French unit test that doesn't work depending
on the time of day when the test is run.
2018-07-30 20:08:13 +00:00
|
|
|
with self.event_lock:
|
|
|
|
# Check all events
|
|
|
|
pending_messages = []
|
|
|
|
for event in self.events:
|
|
|
|
current_time = time.time()
|
|
|
|
e = self.events[event]
|
|
|
|
# Get scheduled times that has passed
|
|
|
|
passed = [(t, r, d) for (t, r, d) in e if t <= current_time]
|
|
|
|
# and remaining times that we're still waiting for
|
|
|
|
remaining = [(t, r, d) for t, r, d in e if t > current_time]
|
|
|
|
# Trigger registered methods
|
|
|
|
for sched_time, repeat, data in passed:
|
|
|
|
pending_messages.append(Message(event, data))
|
|
|
|
# if this is a repeated event add a new trigger time
|
|
|
|
if repeat:
|
|
|
|
next_time = repeat_time(sched_time, repeat)
|
|
|
|
remaining.append((next_time, repeat, data))
|
|
|
|
# update list of events
|
|
|
|
self.events[event] = remaining
|
|
|
|
|
|
|
|
# Remove events have are now completed
|
|
|
|
self.clear_empty()
|
|
|
|
|
|
|
|
# Finally, emit the queued up events that triggered
|
|
|
|
for msg in pending_messages:
|
2018-08-22 01:50:50 +00:00
|
|
|
self.bus.emit(msg)
|
Fix named event scheduling/deleting (#1705)
While working on the Alarm skill I discovered several issues with the
event scheduler. This PR cleans up those findings and resolves several
other potential issues:
1) To avoid thread synchronization issues, the EventScheduler had several
queues which independently held objects to be added/deleted/updated. However, the order of the events was undefined and got mixed since they were all batched together. So, for instance, if skill code performed:
self.add_event("foo", self.handle_foo)
if SomeReason:
self.cancel_event("foo")
The actual order of queue handling would perform Remove first, then Add which resulted in "foo" not being found for delete, but then added and left as an active event.
Now the EventScheduler protects the list using a Lock and the queues have been removed. Modifications to the list happen immediately after obtaining the lock and are not batched up.
2) One-time events were triggered while the event was still in the EventScheduler list. Now the entry is removed before invoking the handler.
3) Within the MycroftSkill.add_event(name, handler) is a local 'wrapper' method that actually makes the callback. The MycroftSkill.remove_event(name) method attempted to find entries in the events list and the associated handler entries in the self.emitter to remove. However, the emitter actually held the wrapper(handler), not the handler itself. So the emitter handlers were left behind.
This was a quiet bug until the next time you scheduled an event of the same name. When that second event finally triggered, it would fire off both the new and the old handler -- which snowballed in the 'skill-alarm:Beep' case, doubling and redoubling with every beep.
Now this cancels all the emitter listeners by name. There is a very slim chance that someone has registered a listener with the same name, but since it is namespaced to "skill-name:Event" I think this is pretty safe.
Not technically related, but a failure that has been lurking for
some time and is a French unit test that doesn't work depending
on the time of day when the test is run.
2018-07-30 20:08:13 +00:00
|
|
|
|
|
|
|
def schedule_event(self, event, sched_time, repeat=None, data=None):
|
|
|
|
""" Add event to pending event schedule using thread safe queue.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
event (str): Handler for the event
|
|
|
|
sched_time ([type]): [description]
|
|
|
|
repeat ([type], optional): Defaults to None. [description]
|
|
|
|
data ([type], optional): Defaults to None. [description]
|
2017-08-30 17:19:06 +00:00
|
|
|
"""
|
Fix named event scheduling/deleting (#1705)
While working on the Alarm skill I discovered several issues with the
event scheduler. This PR cleans up those findings and resolves several
other potential issues:
1) To avoid thread synchronization issues, the EventScheduler had several
queues which independently held objects to be added/deleted/updated. However, the order of the events was undefined and got mixed since they were all batched together. So, for instance, if skill code performed:
self.add_event("foo", self.handle_foo)
if SomeReason:
self.cancel_event("foo")
The actual order of queue handling would perform Remove first, then Add which resulted in "foo" not being found for delete, but then added and left as an active event.
Now the EventScheduler protects the list using a Lock and the queues have been removed. Modifications to the list happen immediately after obtaining the lock and are not batched up.
2) One-time events were triggered while the event was still in the EventScheduler list. Now the entry is removed before invoking the handler.
3) Within the MycroftSkill.add_event(name, handler) is a local 'wrapper' method that actually makes the callback. The MycroftSkill.remove_event(name) method attempted to find entries in the events list and the associated handler entries in the self.emitter to remove. However, the emitter actually held the wrapper(handler), not the handler itself. So the emitter handlers were left behind.
This was a quiet bug until the next time you scheduled an event of the same name. When that second event finally triggered, it would fire off both the new and the old handler -- which snowballed in the 'skill-alarm:Beep' case, doubling and redoubling with every beep.
Now this cancels all the emitter listeners by name. There is a very slim chance that someone has registered a listener with the same name, but since it is namespaced to "skill-name:Event" I think this is pretty safe.
Not technically related, but a failure that has been lurking for
some time and is a French unit test that doesn't work depending
on the time of day when the test is run.
2018-07-30 20:08:13 +00:00
|
|
|
data = data or {}
|
|
|
|
with self.event_lock:
|
2017-08-30 17:19:06 +00:00
|
|
|
# get current list of scheduled times for event, [] if missing
|
|
|
|
event_list = self.events.get(event, [])
|
2018-03-15 11:39:25 +00:00
|
|
|
|
|
|
|
# Don't schedule if the event is repeating and already scheduled
|
|
|
|
if repeat and event in self.events:
|
|
|
|
LOG.debug('Repeating event {} is already scheduled, discarding'
|
|
|
|
.format(event))
|
|
|
|
else:
|
|
|
|
# add received event and time
|
|
|
|
event_list.append((sched_time, repeat, data))
|
|
|
|
self.events[event] = event_list
|
2017-08-30 17:19:06 +00:00
|
|
|
|
|
|
|
def schedule_event_handler(self, message):
|
|
|
|
"""
|
|
|
|
Messagebus interface to the schedule_event method.
|
|
|
|
Required data in the message envelope is
|
|
|
|
event: event to emit
|
|
|
|
time: time to emit the event
|
|
|
|
|
|
|
|
optional data is
|
|
|
|
repeat: repeat interval
|
|
|
|
data: data to send along with the event
|
|
|
|
|
|
|
|
"""
|
|
|
|
event = message.data.get('event')
|
|
|
|
sched_time = message.data.get('time')
|
|
|
|
repeat = message.data.get('repeat')
|
|
|
|
data = message.data.get('data')
|
|
|
|
if event and sched_time:
|
|
|
|
self.schedule_event(event, sched_time, repeat, data)
|
|
|
|
elif not event:
|
2017-09-18 18:55:58 +00:00
|
|
|
LOG.error('Scheduled event name not provided')
|
2017-08-30 17:19:06 +00:00
|
|
|
else:
|
2017-09-18 18:55:58 +00:00
|
|
|
LOG.error('Scheduled event time not provided')
|
2017-08-30 17:19:06 +00:00
|
|
|
|
|
|
|
def remove_event(self, event):
|
Fix named event scheduling/deleting (#1705)
While working on the Alarm skill I discovered several issues with the
event scheduler. This PR cleans up those findings and resolves several
other potential issues:
1) To avoid thread synchronization issues, the EventScheduler had several
queues which independently held objects to be added/deleted/updated. However, the order of the events was undefined and got mixed since they were all batched together. So, for instance, if skill code performed:
self.add_event("foo", self.handle_foo)
if SomeReason:
self.cancel_event("foo")
The actual order of queue handling would perform Remove first, then Add which resulted in "foo" not being found for delete, but then added and left as an active event.
Now the EventScheduler protects the list using a Lock and the queues have been removed. Modifications to the list happen immediately after obtaining the lock and are not batched up.
2) One-time events were triggered while the event was still in the EventScheduler list. Now the entry is removed before invoking the handler.
3) Within the MycroftSkill.add_event(name, handler) is a local 'wrapper' method that actually makes the callback. The MycroftSkill.remove_event(name) method attempted to find entries in the events list and the associated handler entries in the self.emitter to remove. However, the emitter actually held the wrapper(handler), not the handler itself. So the emitter handlers were left behind.
This was a quiet bug until the next time you scheduled an event of the same name. When that second event finally triggered, it would fire off both the new and the old handler -- which snowballed in the 'skill-alarm:Beep' case, doubling and redoubling with every beep.
Now this cancels all the emitter listeners by name. There is a very slim chance that someone has registered a listener with the same name, but since it is namespaced to "skill-name:Event" I think this is pretty safe.
Not technically related, but a failure that has been lurking for
some time and is a French unit test that doesn't work depending
on the time of day when the test is run.
2018-07-30 20:08:13 +00:00
|
|
|
with self.event_lock:
|
|
|
|
if event in self.events:
|
|
|
|
self.events.pop(event)
|
2017-08-30 17:19:06 +00:00
|
|
|
|
|
|
|
def remove_event_handler(self, message):
|
2017-09-01 20:41:06 +00:00
|
|
|
""" Messagebus interface to the remove_event method. """
|
2017-08-30 17:19:06 +00:00
|
|
|
event = message.data.get('event')
|
|
|
|
self.remove_event(event)
|
|
|
|
|
|
|
|
def update_event(self, event, data):
|
Fix named event scheduling/deleting (#1705)
While working on the Alarm skill I discovered several issues with the
event scheduler. This PR cleans up those findings and resolves several
other potential issues:
1) To avoid thread synchronization issues, the EventScheduler had several
queues which independently held objects to be added/deleted/updated. However, the order of the events was undefined and got mixed since they were all batched together. So, for instance, if skill code performed:
self.add_event("foo", self.handle_foo)
if SomeReason:
self.cancel_event("foo")
The actual order of queue handling would perform Remove first, then Add which resulted in "foo" not being found for delete, but then added and left as an active event.
Now the EventScheduler protects the list using a Lock and the queues have been removed. Modifications to the list happen immediately after obtaining the lock and are not batched up.
2) One-time events were triggered while the event was still in the EventScheduler list. Now the entry is removed before invoking the handler.
3) Within the MycroftSkill.add_event(name, handler) is a local 'wrapper' method that actually makes the callback. The MycroftSkill.remove_event(name) method attempted to find entries in the events list and the associated handler entries in the self.emitter to remove. However, the emitter actually held the wrapper(handler), not the handler itself. So the emitter handlers were left behind.
This was a quiet bug until the next time you scheduled an event of the same name. When that second event finally triggered, it would fire off both the new and the old handler -- which snowballed in the 'skill-alarm:Beep' case, doubling and redoubling with every beep.
Now this cancels all the emitter listeners by name. There is a very slim chance that someone has registered a listener with the same name, but since it is namespaced to "skill-name:Event" I think this is pretty safe.
Not technically related, but a failure that has been lurking for
some time and is a French unit test that doesn't work depending
on the time of day when the test is run.
2018-07-30 20:08:13 +00:00
|
|
|
with self.event_lock:
|
|
|
|
# if there is an active event with this name
|
|
|
|
if len(self.events.get(event, [])) > 0:
|
|
|
|
time, repeat, _ = self.events[event][0]
|
|
|
|
self.events[event][0] = (time, repeat, data)
|
2017-08-30 17:19:06 +00:00
|
|
|
|
|
|
|
def update_event_handler(self, message):
|
2017-09-01 20:41:06 +00:00
|
|
|
""" Messagebus interface to the update_event method. """
|
2017-08-30 17:19:06 +00:00
|
|
|
event = message.data.get('event')
|
2017-09-12 05:58:41 +00:00
|
|
|
data = message.data.get('data')
|
|
|
|
self.update_event(event, data)
|
2017-08-30 17:19:06 +00:00
|
|
|
|
2017-11-01 00:27:58 +00:00
|
|
|
def get_event_handler(self, message):
|
2017-11-01 17:37:07 +00:00
|
|
|
"""
|
|
|
|
Messagebus interface to get_event.
|
|
|
|
Emits another event sending event status
|
|
|
|
"""
|
2017-11-01 00:27:58 +00:00
|
|
|
event_name = message.data.get("name")
|
|
|
|
event = None
|
Fix named event scheduling/deleting (#1705)
While working on the Alarm skill I discovered several issues with the
event scheduler. This PR cleans up those findings and resolves several
other potential issues:
1) To avoid thread synchronization issues, the EventScheduler had several
queues which independently held objects to be added/deleted/updated. However, the order of the events was undefined and got mixed since they were all batched together. So, for instance, if skill code performed:
self.add_event("foo", self.handle_foo)
if SomeReason:
self.cancel_event("foo")
The actual order of queue handling would perform Remove first, then Add which resulted in "foo" not being found for delete, but then added and left as an active event.
Now the EventScheduler protects the list using a Lock and the queues have been removed. Modifications to the list happen immediately after obtaining the lock and are not batched up.
2) One-time events were triggered while the event was still in the EventScheduler list. Now the entry is removed before invoking the handler.
3) Within the MycroftSkill.add_event(name, handler) is a local 'wrapper' method that actually makes the callback. The MycroftSkill.remove_event(name) method attempted to find entries in the events list and the associated handler entries in the self.emitter to remove. However, the emitter actually held the wrapper(handler), not the handler itself. So the emitter handlers were left behind.
This was a quiet bug until the next time you scheduled an event of the same name. When that second event finally triggered, it would fire off both the new and the old handler -- which snowballed in the 'skill-alarm:Beep' case, doubling and redoubling with every beep.
Now this cancels all the emitter listeners by name. There is a very slim chance that someone has registered a listener with the same name, but since it is namespaced to "skill-name:Event" I think this is pretty safe.
Not technically related, but a failure that has been lurking for
some time and is a French unit test that doesn't work depending
on the time of day when the test is run.
2018-07-30 20:08:13 +00:00
|
|
|
with self.event_lock:
|
|
|
|
if event_name in self.events:
|
|
|
|
event = self.events[event_name]
|
2017-11-01 00:27:58 +00:00
|
|
|
emitter_name = 'mycroft.event_status.callback.{}'.format(event_name)
|
2018-08-22 01:50:50 +00:00
|
|
|
self.bus.emit(message.reply(emitter_name, data=event))
|
2017-11-01 00:27:58 +00:00
|
|
|
|
2017-08-30 17:19:06 +00:00
|
|
|
def store(self):
|
|
|
|
"""
|
2017-09-01 20:41:06 +00:00
|
|
|
Write current schedule to disk.
|
2017-08-30 17:19:06 +00:00
|
|
|
"""
|
Fix named event scheduling/deleting (#1705)
While working on the Alarm skill I discovered several issues with the
event scheduler. This PR cleans up those findings and resolves several
other potential issues:
1) To avoid thread synchronization issues, the EventScheduler had several
queues which independently held objects to be added/deleted/updated. However, the order of the events was undefined and got mixed since they were all batched together. So, for instance, if skill code performed:
self.add_event("foo", self.handle_foo)
if SomeReason:
self.cancel_event("foo")
The actual order of queue handling would perform Remove first, then Add which resulted in "foo" not being found for delete, but then added and left as an active event.
Now the EventScheduler protects the list using a Lock and the queues have been removed. Modifications to the list happen immediately after obtaining the lock and are not batched up.
2) One-time events were triggered while the event was still in the EventScheduler list. Now the entry is removed before invoking the handler.
3) Within the MycroftSkill.add_event(name, handler) is a local 'wrapper' method that actually makes the callback. The MycroftSkill.remove_event(name) method attempted to find entries in the events list and the associated handler entries in the self.emitter to remove. However, the emitter actually held the wrapper(handler), not the handler itself. So the emitter handlers were left behind.
This was a quiet bug until the next time you scheduled an event of the same name. When that second event finally triggered, it would fire off both the new and the old handler -- which snowballed in the 'skill-alarm:Beep' case, doubling and redoubling with every beep.
Now this cancels all the emitter listeners by name. There is a very slim chance that someone has registered a listener with the same name, but since it is namespaced to "skill-name:Event" I think this is pretty safe.
Not technically related, but a failure that has been lurking for
some time and is a French unit test that doesn't work depending
on the time of day when the test is run.
2018-07-30 20:08:13 +00:00
|
|
|
with self.event_lock:
|
|
|
|
with open(self.schedule_file, 'w') as f:
|
|
|
|
json.dump(self.events, f)
|
2017-08-30 17:19:06 +00:00
|
|
|
|
2017-09-24 14:57:09 +00:00
|
|
|
def clear_repeating(self):
|
|
|
|
"""
|
|
|
|
Remove repeating events from events dict.
|
|
|
|
"""
|
Fix named event scheduling/deleting (#1705)
While working on the Alarm skill I discovered several issues with the
event scheduler. This PR cleans up those findings and resolves several
other potential issues:
1) To avoid thread synchronization issues, the EventScheduler had several
queues which independently held objects to be added/deleted/updated. However, the order of the events was undefined and got mixed since they were all batched together. So, for instance, if skill code performed:
self.add_event("foo", self.handle_foo)
if SomeReason:
self.cancel_event("foo")
The actual order of queue handling would perform Remove first, then Add which resulted in "foo" not being found for delete, but then added and left as an active event.
Now the EventScheduler protects the list using a Lock and the queues have been removed. Modifications to the list happen immediately after obtaining the lock and are not batched up.
2) One-time events were triggered while the event was still in the EventScheduler list. Now the entry is removed before invoking the handler.
3) Within the MycroftSkill.add_event(name, handler) is a local 'wrapper' method that actually makes the callback. The MycroftSkill.remove_event(name) method attempted to find entries in the events list and the associated handler entries in the self.emitter to remove. However, the emitter actually held the wrapper(handler), not the handler itself. So the emitter handlers were left behind.
This was a quiet bug until the next time you scheduled an event of the same name. When that second event finally triggered, it would fire off both the new and the old handler -- which snowballed in the 'skill-alarm:Beep' case, doubling and redoubling with every beep.
Now this cancels all the emitter listeners by name. There is a very slim chance that someone has registered a listener with the same name, but since it is namespaced to "skill-name:Event" I think this is pretty safe.
Not technically related, but a failure that has been lurking for
some time and is a French unit test that doesn't work depending
on the time of day when the test is run.
2018-07-30 20:08:13 +00:00
|
|
|
with self.event_lock:
|
|
|
|
for e in self.events:
|
|
|
|
self.events[e] = [i for i in self.events[e] if i[1] is None]
|
2017-09-24 14:57:09 +00:00
|
|
|
|
|
|
|
def clear_empty(self):
|
|
|
|
"""
|
|
|
|
Remove empty event entries from events dict
|
|
|
|
"""
|
Fix named event scheduling/deleting (#1705)
While working on the Alarm skill I discovered several issues with the
event scheduler. This PR cleans up those findings and resolves several
other potential issues:
1) To avoid thread synchronization issues, the EventScheduler had several
queues which independently held objects to be added/deleted/updated. However, the order of the events was undefined and got mixed since they were all batched together. So, for instance, if skill code performed:
self.add_event("foo", self.handle_foo)
if SomeReason:
self.cancel_event("foo")
The actual order of queue handling would perform Remove first, then Add which resulted in "foo" not being found for delete, but then added and left as an active event.
Now the EventScheduler protects the list using a Lock and the queues have been removed. Modifications to the list happen immediately after obtaining the lock and are not batched up.
2) One-time events were triggered while the event was still in the EventScheduler list. Now the entry is removed before invoking the handler.
3) Within the MycroftSkill.add_event(name, handler) is a local 'wrapper' method that actually makes the callback. The MycroftSkill.remove_event(name) method attempted to find entries in the events list and the associated handler entries in the self.emitter to remove. However, the emitter actually held the wrapper(handler), not the handler itself. So the emitter handlers were left behind.
This was a quiet bug until the next time you scheduled an event of the same name. When that second event finally triggered, it would fire off both the new and the old handler -- which snowballed in the 'skill-alarm:Beep' case, doubling and redoubling with every beep.
Now this cancels all the emitter listeners by name. There is a very slim chance that someone has registered a listener with the same name, but since it is namespaced to "skill-name:Event" I think this is pretty safe.
Not technically related, but a failure that has been lurking for
some time and is a French unit test that doesn't work depending
on the time of day when the test is run.
2018-07-30 20:08:13 +00:00
|
|
|
with self.event_lock:
|
|
|
|
self.events = {k: self.events[k] for k in self.events
|
|
|
|
if self.events[k] != []}
|
2017-09-24 14:57:09 +00:00
|
|
|
|
2017-08-30 17:19:06 +00:00
|
|
|
def shutdown(self):
|
2017-09-01 20:41:06 +00:00
|
|
|
""" Stop the running thread. """
|
2017-08-30 17:19:06 +00:00
|
|
|
self.isRunning = False
|
|
|
|
# Remove listeners
|
2018-08-22 01:50:50 +00:00
|
|
|
self.bus.remove_all_listeners('mycroft.scheduler.schedule_event')
|
|
|
|
self.bus.remove_all_listeners('mycroft.scheduler.remove_event')
|
|
|
|
self.bus.remove_all_listeners('mycroft.scheduler.update_event')
|
2017-08-30 17:19:06 +00:00
|
|
|
# Wait for thread to finish
|
|
|
|
self.join()
|
2017-09-24 14:57:09 +00:00
|
|
|
# Prune event list in preparation for saving
|
|
|
|
self.clear_repeating()
|
|
|
|
self.clear_empty()
|
2017-08-30 17:19:06 +00:00
|
|
|
# Store all pending scheduled events
|
|
|
|
self.store()
|