Opened at 2017-03-04T23:43:50Z
Closed at 2018-04-03T07:07:21Z
#1086 closed planned (done)
unify Depend singleton and instance management
| Reported by: | Ichthyostega | Owned by: | Ichthyostega |
|---|---|---|---|
| Priority: | lesser | Milestone: | 2beta |
| Component: | lumieraSteam | Keywords: | QA refactor cleanup |
| Sub Tickets: | #934, #1133 | Parent Tickets: | #276, #283, #1090, #1126 |
Description
We use the lib::Depend template to express dependency to a service, and in fact, those services are created as singletons on demand. This approach basically works well for us. Moreover, we've established the policy that the application shutdown phase must not do anything of significance -- which means it is not important when and in which order the singleton instances are destroyed.
Several concerns remain though
- just from looking at the
lib::Dependtemplate and the associatedDependencyFactory, the whole implementation still looks quite complicated and possibly a bit overingeneered. Not sure if this is actually the case - some doubts regarding the double checked locking do remain. It can't be ruled out that an overly zealous optimiser might move parts of the instance creation point beyond the assignment of the instance variable. And while, accoriding to the POSIX standard, the mutex_unlock ensures that there is a memory barrier, i.e. it ensures that other threads can see any changes done within the mutex protected section, we can not rule out that another thread might see the assignment to the instance variable immediately and thus before all of the instance creation is done. This remains a contentious topic, though. We cannot expect
lib::Dependto be used in a performance critical region, when we can not be sure that there is no significant penalty compared to using a plain flat pointer. Which would counterfeit the whole purpose of using marked and managed dependencies to reduce coupling. - at several occasions we depend on a service with explicit lifecycle. lib::Depend can not be used for such, and thus the configurability of the factory function looks pretty much moot.
Thus, at some point it would be desirable to rewrite the whole compound again (for the third time, but why not, this is how solutions become mature). The new solution should still default to a lazy initialised singleton placed into static memory, but it should seamlessly integrate the option to depend on a service with actively managed lifecycle. We use two different, separate (and unfortunately quite contrieved) solutions for this task in the Session and for the interface proxies. It would be desirable to have lib::Depend as the unified front-end to any dependency on other services, with the option to configure a more elaborate instance management at those (rare) occasions where it is really needed.
Change history (9)
comment:1 by , at 2017-03-11T00:27:02Z
| blockedby: | 934 → 934, 1090 |
|---|
comment:2 by , at 2018-03-10T21:04:28Z
| blocking: | 276, 283 → 276, 283, 1126 |
|---|
comment:3 by , at 2018-03-10T21:24:51Z
| blockedby: | 934, 1090 → 934 |
|---|---|
| blocking: | 276, 283, 1126 → 276, 283, 1090, 1126 |
comment:4 by , at 2018-03-10T21:33:40Z
| Status: | new → accepted |
|---|
comment:5 by , at 2018-03-23T22:56:27Z
A totally reworked version of our dependency factory is basically finished, tested and and ready to be swapped in to replace the existing implementation.
With one omission: we need to "fix" the Double Checked Locking -- which was broken, at least in theory. In practice it was not, since the x86/64 platform is known have strong meomory coherency. But if we accidentally want to target ARM or similar in the future, we should close the known loophole and use the new C++11 feature std::atomic. Moreover, at this point I also want to conduct some micro benchmarks to get a feeling about the proportions of the performance impact
comment:6 by , at 2018-03-30T15:10:28Z
Developed state-of-the-art soluton for access, based on Double Checked Locking and a std::atomic.
Conducted a series of microbenchmarks, which indicate this solution still performs very well. It is 4 times slower on average than a direct unprotected call to a shared object. DCL without Atomics is 3 times slower, a naive mutex locked solution is 100 - 1000 times slower, depending on contention. Meyer's Singleton is implemented on contemporary C++ runtimes with a similar approach and is comparable in speed to Lumiera's library solution. So basically our solution is now a tiny bit slower, but state-of-the art and considered airtight even in theoretical corner cases on other architectures like ARM or PowerPC, which offer lesser memory coherency than the x86/64 architecture.
Besides that, I consider the design of the new solution much better, finally. Well, it is the third attempt. And we cover new ground now, since I've integrated a configuration option to attach to a service with lifecycle. So the next step would be to swap this new version into place, and then to replace the previously hand-written access to various services and layer separation interfaces by this new unified dependency access.
comment:7 by , at 2018-03-31T00:36:44Z
| blockedby: | 934 → 934, 1133 |
|---|
comment:8 by , at 2018-04-03T07:07:21Z
| Resolution: | → done |
|---|---|
| Status: | accepted → closed |
- replaced old implementation and adjusted all usages
- replaced various ad hoc written solutions for service dependencies
- reworked the convoluted proxy generation for the Interface / Plug-in system.
lib::Dependis now also the client-side front-end to talk to services in other layers and to plug-ins
comment:9 by , at 2025-12-25T00:00:00Z
| blockedby: | 934, 1133 |
|---|---|
| blocking: | 276, 283, 1090, 1126 |
| Parent Tickets: | → 276, 283, 1090, 1126 |
| Sub Tickets: | → 934, 1133 |
Migration MasterTickets ⟼ Subtickets-plugin

It is now the second time within a rather short time period that I run into this problem as an impediment: we are lacking a standard mechanism how to access some service by name -- especially when this service as an active lifecycle and is known to shut down eventually.
So it's time to pick up this topic (which was postponed in the very early days of Lumiera development). We are not going to use a DI-container, and we will most definitvely not implement our own DI-container framework. Rather we'll be expanding on the idea of a singleton factory -- because just that tiny step from a stingleton to a singleton factory (which we made during the initial design already) is sufficient to resolve pretty much everything considered "evil" and problematic with singletons.