-
Notifications
You must be signed in to change notification settings - Fork 0
Singleton Services
The Singleton template class provides a thread-safe singleton pattern implementation with optional threading support and lifecycle management. This class is part of the springtail namespace and is designed to ensure that only one instance of a derived class exists throughout the application's lifetime.
-
Thread-safe initialization using
std::call_once - Optional threading support with automatic thread naming
-
Service registration integration with centralized shutdown management through
ServiceRegistry - Protected lifecycle hooks for derived class customization
- Deleted copy/move semantics to prevent duplication
- Atomic shutdown flag for safe thread termination
template <typename T>
class SingletonThe template parameter T should be the derived class that inherits from Singleton<T> (CRTP pattern).
static T* get_instance()Returns the singleton instance of type T. Creates the instance on first call using thread-safe initialization.
Returns: Pointer to the singleton instance
Thread Safety: Yes
Example:
MyService* service = MyService::get_instance();static void shutdown()Performs cleanup and destruction of the singleton instance. Can be called multiple times safely (only executes once). This method:
- Sets the shutdown flag
- Signals thread termination if a thread exists
- Joins the thread
- Calls internal cleanup
- Deletes the instance
Thread Safety: Yes
void start_thread()Starts a dedicated thread for the singleton instance. The thread executes the _internal_run() method. Thread name is automatically set based on the service ID or type name.
Example:
MyService::get_instance()->start_thread();Derived classes should override these methods to customize behavior:
virtual void _internal_run()Main execution function for the singleton's thread. Must be overridden if start_thread() is used. Should periodically check _is_shutting_down() to enable graceful termination.
Example:
void _internal_run() override {
while (!_is_shutting_down()) {
// Perform work
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}virtual void _internal_thread_shutdown()Called during shutdown before the thread is joined. Use this to wake up blocking operations (e.g., notify condition variables).
Example:
void _internal_thread_shutdown() override {
_condition.notify_all();
}virtual void _internal_shutdown()Called during shutdown after the thread has been joined. Use this for cleanup of resources.
Example:
void _internal_shutdown() override {
_connection.close();
}bool _is_shutting_down() constReturns whether the singleton is in the shutdown process.
Returns: true if shutting down, false otherwise
explicit Singleton(ServiceId service_id = ServiceId::ServiceInvalidId)Protected constructor that can only be called by derived classes. Optionally registers the service for centralized shutdown management.
Parameters:
-
service_id: Service identifier for registration (default:ServiceId::ServiceInvalidId)
static void _assert_instance()Asserts that the singleton instance has been created. Terminates if not.
static bool _has_instance()Checks if the singleton instance exists.
Returns: true if instance exists, false otherwise
ServiceId uniquely identifies a logical service within the Springtail system.
These identifiers are used for:
- Dependency ordering
- Thread naming
- Coordinated shutdown
- Service registration
The ServiceId enum defines service identifiers for various components in the system:
enum class ServiceId : int32_t {
ServiceInvalidId = -1,
ServiceRegisterId = 0,
DatabaseMgrId,
UserMgrId,
ProxyServerId,
// ... additional service IDs
ServiceCountId
}ServiceInvalidId is reserved for non-registered or internal services.
template <typename T>
std::string get_type_name()Extracts the type name from compiler-generated strings.
Returns: String representation of type T
void springtail_register_service(ServiceId service_id, ShutdownFunc fn)
const std::string& springtail_get_service_name(ServiceId id)Functions for registering services and retrieving service names.
Registers a service shutdown callback with the global service registry.
- Services are shut down in dependency order.
- Registration is idempotent and enforced per service ID.
- Typically invoked automatically by
Singleton.
Registered shutdown functions are executed during springtail_shutdown().
Returns a human-readable name for a given ServiceId.
Used for:
- Logging
- Thread naming
- Debug output
If a service ID is invalid, "Invalid" is returned.
The Singleton<T> template provides a thread-safe, lifecycle-aware singleton implementation designed for long-lived system services.
- Lazy exactly-once initialization
- Exactly-once shutdown
- Optional background thread
- Dependency-aware cleanup
- Integrated with service registry
- The instance is created on first call to
get_instance(). - Creation is guarded by
std::call_once. - The derived class must have a default constructor.
Singletons may optionally run a dedicated thread.
To enable threading:
- Call
start_thread(). - Implement
_internal_run().
Thread behavior:
- Thread name is derived from
ServiceId. - If no valid
ServiceIdexists, the type name is used. - Names are truncated to 15 characters to satisfy pthread limits.
Shutdown is global and idempotent.
When shutdown() is called:
-
_shutting_downis set to true. -
_internal_thread_shutdown()is invoked. - The thread is joined (if started).
-
_internal_shutdown()is invoked. - The instance is destroyed.
Shutdown occurs exactly once, even if called multiple times.
Derived classes may need to implement overrides for the following hooks. Some of these functions are for running and shutting down the internal thread, while another is simply for internal shutdown.
Executed in the background thread.
Must be overridden if start_thread() is used.
Used to wake or unblock the background thread prior to joining.
Typical use cases:
- Notifying condition variables
- Closing file descriptors
- Signaling event loops
It does not have to be overriden, but if it is, it should signal termination for the thread run by this singleton service.
Final cleanup hook.
Used for:
- Releasing resources
- Stopping dependent components
- Final state updates
If there are other threads owned by this service, this is the place to signal their termination and join them.
Returns true if shutdown has been initiated.
Intended for use inside _internal_run() loops to allow cooperative termination.
The Singleton constructor accepts an optional ServiceId.
If a valid ServiceId is provided:
- The service is automatically registered for shutdown.
- The shutdown order is dependency-aware and is a part of the initialization framework.
Default value for service id is ServiceInvalidId. This value ensures that the service won't be registered with initialization framework. It will be up to the user to figure out when and how to invoke shutdown() function.
Returns a demangled type name extracted from __PRETTY_FUNCTION__.
Used internally for:
- Thread naming fallback
- Diagnostics
Not intended as a stable ABI feature.
- Define a service class inheriting from
Singleton<T>. - If you want this class to be a part of the global shutdown sequence, then you need to do the following:
- create a new service id in
ServiceIdenum - in the constructor pass this new service id to
Singletonconstructor - in
init.ccfile, setup dependencies for this service on the other existing services, for the very least it should depend onServiceRegisterIdbecause this is the id of the service that provides initialization and shutdown for Springtail base functionality - in
init.ccfile, add the name of the new service to service names mapdependencies_names
- Optionally start a background thread.
- Let the framework manage lifecycle and shutdown if the service id is assigned, otherwise manage it yourself.
Shutdown is coordinated globally via springtail_shutdown().
Here is the usage example.
class MyService : public springtail::Singleton<MyService> {
friend class springtail::Singleton<MyService>;
protected:
MyService() : Singleton(springtail::ServiceId::MyServiceId) {
// Initialize your service
}
void _internal_run() override {
while (!_is_shutting_down()) {
// Process work
process_requests();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
void _internal_thread_shutdown() override {
// Wake up any blocking operations
_request_queue.notify_all();
}
void _internal_shutdown() override {
// Cleanup resources
_cleanup();
}
public:
void process_request(const Request& req) {
// Public API
}
private:
void process_requests() {
// Internal processing
}
};
// Usage
int main() {
springtail_init(false);
// Get instance and start thread
MyService::get_instance()->start_thread();
// Use the service
MyService::get_instance()->process_request(request);
// Shutdown (typically called from a signal handler)
springtail_shutdown();
}- Instance creation is thread-safe via
std::call_once - Shutdown is thread-safe via
std::call_once - The
_shutting_downflag is atomic for safe cross-thread access - Derived classes are responsible for their own thread-safety
- No double initialization
- No double shutdown
- Deterministic shutdown order
- Thread-safe lifecycle transitions
- No dependency cycles allowed
- Uses the Curiously Recurring Template Pattern (CRTP) for static polymorphism
- Derived classes should friend
Singleton<T>to allow private constructor access - All copy and move operations are explicitly deleted
- Instance is created on first
get_instance()call - Services can self-register for centralized shutdown
This framework is designed for:
- Long-lived system services
- Daemons and infrastructure components
- Deterministic startup/shutdown