NVMeContext: Rework sensor removal concurrent to polling

Concurrent removal of a sensor's configuration while the sensor list is
being iterated for polling can lead to undefined behaviour via access
through a deleted iterator:

    Program terminated with signal SIGSEGV, Segmentation fault.
    #0  std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_count (__r=..., this=<optimised out>, this=<optimised out>, __r=...)
        at /usr/include/c++/11.2.0/bits/stl_list.h:224
    224     /usr/include/c++/11.2.0/bits/stl_list.h: No such file or directory.
    [Current thread is 1 (LWP 6649)]
    #0  std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_count (__r=..., this=<optimised out>, this=<optimised out>, __r=...)
        at /usr/include/c++/11.2.0/bits/stl_list.h:224
    #1  std::__shared_ptr<NVMeSensor, (__gnu_cxx::_Lock_policy)2>::__shared_ptr (this=<optimised out>, this=<optimised out>)
        at /usr/include/c++/11.2.0/bits/shared_ptr_base.h:1152
    #2  std::shared_ptr<NVMeSensor>::shared_ptr (this=<optimised out>, this=<optimised out>) at /usr/include/c++/11.2.0/bits/shared_ptr.h:150
    #3  NVMeBasicContext::readAndProcessNVMeSensor (this=0x1ac8a90, iter=non-dereferenceable iterator for std::list) at ../git/src/NVMeBasicContext.cpp:299
    #4  0x004dd8b8 in NVMeBasicContext::readAndProcessNVMeSensor (this=0x1ac8a90, iter=non-dereferenceable iterator for std::list) at ../git/src/NVMeBasicContext.cpp:312
    #5  0x004dd8b8 in NVMeBasicContext::readAndProcessNVMeSensor (this=0x1ac8a90, iter=std::shared_ptr<NVMeSensor> (use count 26, weak count 28153871) = {get() = 0x1ad8db0})
        at ../git/src/NVMeBasicContext.cpp:312

Rework polling and sensor removal to uphold the requirement that the
iterator remains valid.

Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
Change-Id: I69b005fe3dad7ddf21d1762731f9cdfd2408cae1
diff --git a/include/NVMeContext.hpp b/include/NVMeContext.hpp
index 2489815..230ca74 100644
--- a/include/NVMeContext.hpp
+++ b/include/NVMeContext.hpp
@@ -12,7 +12,7 @@
 {
   public:
     NVMeContext(boost::asio::io_service& io, int rootBus) :
-        scanTimer(io), rootBus(rootBus)
+        scanTimer(io), rootBus(rootBus), pollCursor(sensors.end())
     {
         if (rootBus < 0)
         {
@@ -45,9 +45,44 @@
         return std::nullopt;
     }
 
+    // Post-condition: The sensor list does not contain the provided sensor
+    // Post-condition: pollCursor is a valid iterator for the sensor list
     void removeSensor(const std::shared_ptr<NVMeSensor>& sensor)
     {
-        sensors.remove(sensor);
+        // Locate the sensor that we're removing in the sensor list
+        auto found = std::find(sensors.begin(), sensors.end(), sensor);
+
+        // If we failed to find the sensor in the list the post-condition is
+        // already satisfied
+        if (found == sensors.end())
+        {
+            return;
+        }
+
+        // We've found the sensor in the list
+
+        // If we're not actively polling the sensor list, then remove the sensor
+        if (pollCursor == sensors.end())
+        {
+            sensors.erase(found);
+            return;
+        }
+
+        // We're actively polling the sensor list
+
+        // If we're not polling the specific sensor that has been removed, then
+        // remove the sensor
+        if (*pollCursor != *found)
+        {
+            sensors.erase(found);
+            return;
+        }
+
+        // We're polling the sensor that is being removed
+
+        // Remove the sensor and update the poll cursor so the cursor remains
+        // valid
+        pollCursor = sensors.erase(found);
     }
 
     virtual void close()
@@ -57,16 +92,16 @@
 
     virtual void pollNVMeDevices() = 0;
 
-    virtual void readAndProcessNVMeSensor(
-        std::list<std::shared_ptr<NVMeSensor>>::iterator iter) = 0;
+    virtual void readAndProcessNVMeSensor() = 0;
 
     virtual void processResponse(std::shared_ptr<NVMeSensor>& sensor, void* msg,
                                  size_t len) = 0;
 
   protected:
     boost::asio::deadline_timer scanTimer;
-    int rootBus;                                    // Root bus for this drive
-    std::list<std::shared_ptr<NVMeSensor>> sensors; // used as a poll queue
+    int rootBus; // Root bus for this drive
+    std::list<std::shared_ptr<NVMeSensor>> sensors;
+    std::list<std::shared_ptr<NVMeSensor>>::iterator pollCursor;
 };
 
 using NVMEMap = boost::container::flat_map<int, std::shared_ptr<NVMeContext>>;