changeset 1796:ff8c57c364a0

Make EventSeries threadsafe
author Chris Cannam
date Mon, 30 Sep 2019 20:28:03 +0100
parents 94b488d4b299
children e8b552549225
files base/EventSeries.cpp base/EventSeries.h
diffstat 2 files changed, 113 insertions(+), 14 deletions(-) [+]
line wrap: on
line diff
--- a/base/EventSeries.cpp	Mon Sep 30 12:36:44 2019 +0100
+++ b/base/EventSeries.cpp	Mon Sep 30 20:28:03 2019 +0100
@@ -14,6 +14,47 @@
 
 #include "EventSeries.h"
 
+#include <QMutexLocker>
+
+EventSeries::EventSeries(const EventSeries &other) :
+    EventSeries(other, QMutexLocker(&other.m_mutex))
+{
+}
+
+EventSeries::EventSeries(const EventSeries &other, const QMutexLocker &) :
+    m_events(other.m_events),
+    m_seams(other.m_seams),
+    m_finalDurationlessEventFrame(other.m_finalDurationlessEventFrame)
+{
+}
+
+EventSeries &
+EventSeries::operator=(const EventSeries &other)
+{
+    QMutexLocker locker(&m_mutex), otherLocker(&other.m_mutex);
+    m_events = other.m_events;
+    m_seams = other.m_seams;
+    m_finalDurationlessEventFrame = other.m_finalDurationlessEventFrame;
+    return *this;
+}
+
+EventSeries &
+EventSeries::operator=(EventSeries &&other)
+{
+    QMutexLocker locker(&m_mutex), otherLocker(&other.m_mutex);
+    m_events = std::move(other.m_events);
+    m_seams = std::move(other.m_seams);
+    m_finalDurationlessEventFrame = std::move(other.m_finalDurationlessEventFrame);
+    return *this;
+}
+
+bool
+EventSeries::operator==(const EventSeries &other) const
+{
+    QMutexLocker locker(&m_mutex);
+    return m_events == other.m_events;
+}
+
 EventSeries
 EventSeries::fromEvents(const EventVector &v)
 {
@@ -27,12 +68,14 @@
 bool
 EventSeries::isEmpty() const
 {
+    QMutexLocker locker(&m_mutex);
     return m_events.empty();
 }
 
 int
 EventSeries::count() const
 {
+    QMutexLocker locker(&m_mutex);
     if (m_events.size() > INT_MAX) {
         throw std::logic_error("too many events");
     }
@@ -42,6 +85,8 @@
 void
 EventSeries::add(const Event &p)
 {
+    QMutexLocker locker(&m_mutex);
+
     bool isUnique = true;
 
     auto pitr = lower_bound(m_events.begin(), m_events.end(), p);
@@ -87,6 +132,8 @@
 void
 EventSeries::remove(const Event &p)
 {
+    QMutexLocker locker(&m_mutex);
+
     // If we are removing the last (unique) example of an event,
     // then we also need to remove it from the seam map. If this
     // is only one of multiple identical events, then we don't.
@@ -198,12 +245,14 @@
 bool
 EventSeries::contains(const Event &p) const
 {
+    QMutexLocker locker(&m_mutex);
     return binary_search(m_events.begin(), m_events.end(), p);
 }
 
 void
 EventSeries::clear()
 {
+    QMutexLocker locker(&m_mutex);
     m_events.clear();
     m_seams.clear();
     m_finalDurationlessEventFrame = 0;
@@ -212,6 +261,7 @@
 sv_frame_t
 EventSeries::getStartFrame() const
 {
+    QMutexLocker locker(&m_mutex);
     if (m_events.empty()) return 0;
     return m_events.begin()->getFrame();
 }
@@ -219,6 +269,8 @@
 sv_frame_t
 EventSeries::getEndFrame() const
 {
+    QMutexLocker locker(&m_mutex);
+
     sv_frame_t latest = 0;
 
     if (m_events.empty()) return latest;
@@ -239,6 +291,8 @@
 EventSeries::getEventsSpanning(sv_frame_t frame,
                                sv_frame_t duration) const
 {
+    QMutexLocker locker(&m_mutex);
+
     EventVector span;
     
     const sv_frame_t start = frame;
@@ -286,6 +340,8 @@
                              sv_frame_t duration,
                              int overspill) const
 {
+    QMutexLocker locker(&m_mutex);
+
     EventVector span;
     
     const sv_frame_t start = frame;
@@ -336,6 +392,8 @@
 EventSeries::getEventsStartingWithin(sv_frame_t frame,
                                      sv_frame_t duration) const
 {
+    QMutexLocker locker(&m_mutex);
+
     EventVector span;
     
     const sv_frame_t start = frame;
@@ -358,6 +416,8 @@
 EventVector
 EventSeries::getEventsCovering(sv_frame_t frame) const
 {
+    QMutexLocker locker(&m_mutex);
+
     EventVector cover;
 
     // first find any zero-duration events
@@ -400,12 +460,16 @@
 EventVector
 EventSeries::getAllEvents() const
 {
+    QMutexLocker locker(&m_mutex);
+
     return m_events;
 }
 
 bool
 EventSeries::getEventPreceding(const Event &e, Event &preceding) const
 {
+    QMutexLocker locker(&m_mutex);
+
     auto pitr = lower_bound(m_events.begin(), m_events.end(), e);
     if (pitr == m_events.end() || *pitr != e) {
         return false;
@@ -421,6 +485,8 @@
 bool
 EventSeries::getEventFollowing(const Event &e, Event &following) const
 {
+    QMutexLocker locker(&m_mutex);
+
     auto pitr = lower_bound(m_events.begin(), m_events.end(), e);
     if (pitr == m_events.end() || *pitr != e) {
         return false;
@@ -441,6 +507,8 @@
                                      Direction direction,
                                      Event &found) const
 {
+    QMutexLocker locker(&m_mutex);
+
     auto pitr = lower_bound(m_events.begin(), m_events.end(),
                             Event(startSearchAt));
 
@@ -475,6 +543,8 @@
 Event
 EventSeries::getEventByIndex(int index) const
 {
+    QMutexLocker locker(&m_mutex);
+
     if (index < 0 || index >= count()) {
         throw std::logic_error("index out of range");
     }
@@ -484,6 +554,8 @@
 int
 EventSeries::getIndexForEvent(const Event &e) const
 {
+    QMutexLocker locker(&m_mutex);
+
     auto pitr = lower_bound(m_events.begin(), m_events.end(), e);
     auto d = distance(m_events.begin(), pitr);
     if (d < 0 || d > INT_MAX) return 0;
@@ -495,7 +567,17 @@
                    QString indent,
                    QString extraAttributes) const
 {
-    toXml(out, indent, extraAttributes, Event::ExportNameOptions());
+    QMutexLocker locker(&m_mutex);
+
+    out << indent << QString("<dataset id=\"%1\" %2>\n")
+        .arg(getExportId())
+        .arg(extraAttributes);
+    
+    for (const auto &p: m_events) {
+        p.toXml(out, indent + "  ", "", {});
+    }
+    
+    out << indent << "</dataset>\n";
 }
 
 void
@@ -504,6 +586,8 @@
                    QString extraAttributes,
                    Event::ExportNameOptions options) const
 {
+    QMutexLocker locker(&m_mutex);
+
     out << indent << QString("<dataset id=\"%1\" %2>\n")
         .arg(getExportId())
         .arg(extraAttributes);
@@ -524,6 +608,8 @@
                                    sv_frame_t resolution,
                                    Event fillEvent) const
 {
+    QMutexLocker locker(&m_mutex);
+
     QString s;
 
     const sv_frame_t end = startFrame + duration;
--- a/base/EventSeries.h	Mon Sep 30 12:36:44 2019 +0100
+++ b/base/EventSeries.h	Mon Sep 30 20:28:03 2019 +0100
@@ -21,6 +21,8 @@
 #include <set>
 #include <functional>
 
+#include <QMutex>
+
 //#define DEBUG_EVENT_SERIES 1
 
 /**
@@ -40,7 +42,7 @@
  * does work, and should be acceptable in interactive use, but it is
  * very slow in bulk.
  *
- * EventSeries is not thread-safe.
+ * EventSeries is thread-safe.
  */
 class EventSeries : public XmlExportable
 {
@@ -48,14 +50,12 @@
     EventSeries() : m_finalDurationlessEventFrame(0) { }
     ~EventSeries() =default;
 
-    EventSeries(const EventSeries &) =default;
+    EventSeries(const EventSeries &);
 
-    EventSeries &operator=(const EventSeries &) =delete;
-    EventSeries &operator=(EventSeries &&) =delete;
+    EventSeries &operator=(const EventSeries &);
+    EventSeries &operator=(EventSeries &&);
     
-    bool operator==(const EventSeries &other) const {
-        return m_events == other.m_events;
-    }
+    bool operator==(const EventSeries &other) const;
 
     static EventSeries fromEvents(const EventVector &ee);
     
@@ -233,6 +233,10 @@
                                   Event fillEvent) const;
     
 private:
+    mutable QMutex m_mutex;
+
+    EventSeries::EventSeries(const EventSeries &other, const QMutexLocker &);
+    
     /**
      * This vector contains all events in the series, in the normal
      * sort order. For backward compatibility we must support series
@@ -278,9 +282,12 @@
      */
     sv_frame_t m_finalDurationlessEventFrame;
     
-    /** Create a seam at the given frame, copying from the prior seam
-     *  if there is one. If a seam already exists at the given frame,
-     *  leave it untouched.
+    /** 
+     * Create a seam at the given frame, copying from the prior seam
+     * if there is one. If a seam already exists at the given frame,
+     * leave it untouched.
+     *
+     * Call with m_mutex locked.
      */
     void createSeam(sv_frame_t frame) {
         auto itr = m_seams.lower_bound(frame);
@@ -298,6 +305,15 @@
         }
     }
 
+    /** 
+     * Return true if the two seam map entries contain the same set of
+     * events.
+     *
+     * Precondition: no duplicates, i.e. no event appears more than
+     * once in s1 or more than once in s2.
+     *
+     * Call with m_mutex locked.
+     */
     bool seamsEqual(const std::vector<Event> &s1,
                     const std::vector<Event> &s2) const {
         
@@ -305,9 +321,6 @@
             return false;
         }
 
-        // precondition: no event appears more than once in s1 or more
-        // than once in s2
-
 #ifdef DEBUG_EVENT_SERIES
         for (int i = 0; in_range_for(s1, i); ++i) {
             for (int j = i + 1; in_range_for(s1, j); ++j) {