#ifndef OSRM_ENGINE_DATA_WATCHDOG_HPP #define OSRM_ENGINE_DATA_WATCHDOG_HPP #include "engine/datafacade/shared_memory_datafacade.hpp" #include "storage/shared_barriers.hpp" #include "storage/shared_datatype.hpp" #include "storage/shared_memory.hpp" #include #include #include #include #include namespace osrm { namespace engine { // This class monitors the shared memory region that contains the pointers to // the data and layout regions that should be used. This region is updated // once a new dataset arrives. // // TODO: This also needs a shared memory reader lock with other clients and // possibly osrm-datastore since updating the CURRENT_REGIONS data is not atomic. // Currently we enfore this by waiting that all queries have finished before // osrm-datastore writes to this section. class DataWatchdog { public: DataWatchdog() : shared_barriers{std::make_shared()}, shared_regions(storage::makeSharedMemory(storage::CURRENT_REGIONS)), current_timestamp{storage::LAYOUT_NONE, storage::DATA_NONE, 0} { } // Tries to connect to the shared memory containing the regions table static bool TryConnect() { return storage::SharedMemory::RegionExists(storage::CURRENT_REGIONS); } using RegionsLock = boost::interprocess::sharable_lock; using LockAndFacade = std::pair>; // This will either update the contens of facade or just leave it as is // if the update was already done by another thread LockAndFacade GetDataFacade() { const boost::interprocess::sharable_lock lock( shared_barriers->current_regions_mutex); const auto shared_timestamp = static_cast(shared_regions->Ptr()); const auto get_locked_facade = [this, shared_timestamp]() { if (current_timestamp.data == storage::DATA_1) { BOOST_ASSERT(current_timestamp.layout == storage::LAYOUT_1); return std::make_pair(RegionsLock(shared_barriers->regions_1_mutex), facade); } else { BOOST_ASSERT(current_timestamp.layout == storage::LAYOUT_2); BOOST_ASSERT(current_timestamp.data == storage::DATA_2); return std::make_pair(RegionsLock(shared_barriers->regions_2_mutex), facade); } }; // this blocks handle the common case when there is no data update -> we will only need a // shared lock { boost::shared_lock facade_lock(facade_mutex); if (shared_timestamp->timestamp == current_timestamp.timestamp) { BOOST_ASSERT(shared_timestamp->layout == current_timestamp.layout); BOOST_ASSERT(shared_timestamp->data == current_timestamp.data); return get_locked_facade(); } } // if we reach this code there is a data update to be made. multiple // requests can reach this, but only ever one goes through at a time. boost::upgrade_lock facade_lock(facade_mutex); // we might get overtaken before we actually do the writing // in that case we don't modify anthing if (shared_timestamp->timestamp == current_timestamp.timestamp) { BOOST_ASSERT(shared_timestamp->layout == current_timestamp.layout); BOOST_ASSERT(shared_timestamp->data == current_timestamp.data); return get_locked_facade(); } // this thread has won and can update the data boost::upgrade_to_unique_lock unique_facade_lock(facade_lock); current_timestamp = *shared_timestamp; facade = std::make_shared(shared_barriers, current_timestamp.layout, current_timestamp.data, current_timestamp.timestamp); return get_locked_facade(); } private: // mutexes should be mutable even on const objects: This enables // marking functions as logical const and thread-safe. std::shared_ptr shared_barriers; // shared memory table containing pointers to all shared regions std::unique_ptr shared_regions; mutable boost::shared_mutex facade_mutex; std::shared_ptr facade; storage::SharedDataTimestamp current_timestamp; }; } } #endif