Skip to content

Freeze registries at startup, when everything has been registered #10388

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
62 changes: 39 additions & 23 deletions lib/base/registry.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@
#define REGISTRY_H

#include "base/i2-base.hpp"
#include "base/atomic.hpp"
#include "base/exception.hpp"
#include "base/string.hpp"
#include <boost/signals2.hpp>
#include <map>
#include "base/singleton.hpp"
#include <shared_mutex>
#include <stdexcept>
#include <unordered_map>
#include <mutex>

namespace icinga
Expand All @@ -21,18 +25,27 @@ template<typename U, typename T>
class Registry
{
public:
typedef std::map<String, T> ItemMap;
typedef std::unordered_map<String, T> ItemMap;

static Registry* GetInstance()
{
return Singleton<Registry>::GetInstance();
}

void Register(const String& name, const T& item)
{
std::unique_lock<std::mutex> lock(m_Mutex);
std::unique_lock lock (m_Mutex);

if (m_Frozen) {
BOOST_THROW_EXCEPTION(std::logic_error("Registry is read-only and must not be modified."));
}
Comment on lines +39 to +41
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can also perform this check before even locking the mutex. I don't understand this, if you need the mutex for m_Frozen as well, why make it atomic?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't answer the questions! And no, you didn’t do exactly the same.

if (m_Frozen) {
BOOST_THROW_EXCEPTION(ScriptError("Namespace is read-only and must not be modified.", debugInfo));
}
std::unique_lock<decltype(m_DataMutex)> dlock (m_DataMutex);

if (m_Frozen) {
BOOST_THROW_EXCEPTION(ScriptError("Namespace is read-only and must not be modified."));
}
std::unique_lock<decltype(m_DataMutex)> dlock (m_DataMutex);

ObjectLock olock(this);
m_Frozen = true;

Copy link
Member Author

@Al2Klimov Al2Klimov Apr 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, it IS the same: Register/Freeze

  1. locks a mutex
  2. operates on m_Frozen

It's critical to synchronize them! Imagine:

  1. Register sees not frozen instance
  2. Freeze freezes it
  3. ReadLockUnlessFrozen doesn't lock it
  4. Register modifies it
  5. 🤯

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, it IS the same

No, it's not!

It's critical to synchronize them! Imagine:

I don't get it! It's for a reason atomic and if you can't synchronise those without a mutex why make it atomic then?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, it IS the same

No, it's not!

We could argue for centuries...

I don't get it! It's for a reason atomic and if you can't synchronise those without a mutex why make it atomic then?

I can't synchronise write ops without a mutex. m_Frozen is atomic to speed up read ops.


RegisterInternal(name, item, lock);
m_Items[name] = item;
}

T GetItem(const String& name) const
{
std::unique_lock<std::mutex> lock(m_Mutex);
auto lock (ReadLockUnlessFrozen());

auto it = m_Items.find(name);

Expand All @@ -44,33 +57,36 @@ class Registry

ItemMap GetItems() const
{
std::unique_lock<std::mutex> lock(m_Mutex);
auto lock (ReadLockUnlessFrozen());

return m_Items; /* Makes a copy of the map. */
}

boost::signals2::signal<void (const String&, const T&)> OnRegistered;
boost::signals2::signal<void (const String&)> OnUnregistered;
/**
* Freeze the registry, preventing further updates.
*
* This only prevents inserting, replacing or deleting values from the registry.
* This operation has no effect on objects referenced by the values, these remain mutable if they were before.
*/
void Freeze()
{
std::unique_lock lock (m_Mutex);

m_Frozen.store(true);
}

private:
mutable std::mutex m_Mutex;
mutable std::shared_mutex m_Mutex;
Atomic<bool> m_Frozen {false};
typename Registry<U, T>::ItemMap m_Items;

void RegisterInternal(const String& name, const T& item, std::unique_lock<std::mutex>& lock)
std::shared_lock<std::shared_mutex> ReadLockUnlessFrozen() const
{
bool old_item = false;

if (m_Items.erase(name) > 0)
old_item = true;

m_Items[name] = item;

lock.unlock();

if (old_item)
OnUnregistered(name);
if (m_Frozen.load(std::memory_order_relaxed)) {
return {};
}

OnRegistered(name, item);
return std::shared_lock(m_Mutex);
}
Comment on lines +83 to 90
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

};

Expand Down
9 changes: 4 additions & 5 deletions lib/db_ido/dbtype.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@

using namespace icinga;

INITIALIZE_ONCE_WITH_PRIORITY([]{
DbTypeRegistry::GetInstance()->Freeze();
}, InitializePriority::FreezeNamespaces);

DbType::DbType(String name, String table, long tid, String idcolumn, DbType::ObjectFactory factory)
: m_Name(std::move(name)), m_Table(std::move(table)), m_TypeID(tid), m_IDColumn(std::move(idcolumn)), m_ObjectFactory(std::move(factory))
{ }
Expand Down Expand Up @@ -133,8 +137,3 @@ std::set<DbType::Ptr> DbType::GetAllTypes()

return result;
}

DbTypeRegistry *DbTypeRegistry::GetInstance()
{
return Singleton<DbTypeRegistry>::GetInstance();
}
3 changes: 0 additions & 3 deletions lib/db_ido/dbtype.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
#include "db_ido/i2-db_ido.hpp"
#include "base/object.hpp"
#include "base/registry.hpp"
#include "base/singleton.hpp"
#include <set>

namespace icinga
Expand Down Expand Up @@ -64,8 +63,6 @@ class DbType final : public Object
*/
class DbTypeRegistry : public Registry<DbTypeRegistry, DbType::Ptr>
{
public:
static DbTypeRegistry *GetInstance();
};

/**
Expand Down
10 changes: 4 additions & 6 deletions lib/remote/apiaction.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */

#include "remote/apiaction.hpp"
#include "base/singleton.hpp"

using namespace icinga;

INITIALIZE_ONCE_WITH_PRIORITY([]{
ApiActionRegistry::GetInstance()->Freeze();
}, InitializePriority::FreezeNamespaces);

ApiAction::ApiAction(std::vector<String> types, Callback action)
: m_Types(std::move(types)), m_Callback(std::move(action))
{ }
Expand All @@ -28,8 +31,3 @@ void ApiAction::Register(const String& name, const ApiAction::Ptr& action)
{
ApiActionRegistry::GetInstance()->Register(name, action);
}

ApiActionRegistry *ApiActionRegistry::GetInstance()
{
return Singleton<ApiActionRegistry>::GetInstance();
}
2 changes: 0 additions & 2 deletions lib/remote/apiaction.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ class ApiAction final : public Object
*/
class ApiActionRegistry : public Registry<ApiActionRegistry, ApiAction::Ptr>
{
public:
static ApiActionRegistry *GetInstance();
};

#define REGISTER_APIACTION(name, types, callback) \
Expand Down
10 changes: 4 additions & 6 deletions lib/remote/apifunction.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */

#include "remote/apifunction.hpp"
#include "base/singleton.hpp"

using namespace icinga;

INITIALIZE_ONCE_WITH_PRIORITY([]{
ApiFunctionRegistry::GetInstance()->Freeze();
}, InitializePriority::FreezeNamespaces);

ApiFunction::ApiFunction(Callback function)
: m_Callback(std::move(function))
{ }
Expand All @@ -23,8 +26,3 @@ void ApiFunction::Register(const String& name, const ApiFunction::Ptr& function)
{
ApiFunctionRegistry::GetInstance()->Register(name, function);
}

ApiFunctionRegistry *ApiFunctionRegistry::GetInstance()
{
return Singleton<ApiFunctionRegistry>::GetInstance();
}
2 changes: 0 additions & 2 deletions lib/remote/apifunction.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,6 @@ class ApiFunction final : public Object
*/
class ApiFunctionRegistry : public Registry<ApiFunctionRegistry, ApiFunction::Ptr>
{
public:
static ApiFunctionRegistry *GetInstance();
};

#define REGISTER_APIFUNCTION(name, ns, callback) \
Expand Down
6 changes: 0 additions & 6 deletions lib/remote/eventqueue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
#include "remote/eventqueue.hpp"
#include "remote/filterutility.hpp"
#include "base/io-engine.hpp"
#include "base/singleton.hpp"
#include "base/logger.hpp"
#include "base/utility.hpp"
#include <boost/asio/spawn.hpp>
Expand Down Expand Up @@ -127,11 +126,6 @@ void EventQueue::Register(const String& name, const EventQueue::Ptr& function)
EventQueueRegistry::GetInstance()->Register(name, function);
}

EventQueueRegistry *EventQueueRegistry::GetInstance()
{
return Singleton<EventQueueRegistry>::GetInstance();
}

std::mutex EventsInbox::m_FiltersMutex;
std::map<String, EventsInbox::Filter> EventsInbox::m_Filters ({{"", EventsInbox::Filter{1, Expression::Ptr()}}});

Expand Down
2 changes: 0 additions & 2 deletions lib/remote/eventqueue.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,6 @@ class EventQueue final : public Object
*/
class EventQueueRegistry : public Registry<EventQueueRegistry, EventQueue::Ptr>
{
public:
static EventQueueRegistry *GetInstance();
};

enum class EventType : uint_fast8_t
Expand Down
Loading