#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::Depend template and the associated DependencyFactory, 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::Depend to 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 Ichthyostega, at 2017-03-11T00:27:02Z

blockedby: 934934, 1090

comment:2 by Ichthyostega, at 2018-03-10T21:04:28Z

blocking: 276, 283276, 283, 1126

comment:3 by Ichthyostega, at 2018-03-10T21:24:51Z

blockedby: 934, 1090934
blocking: 276, 283, 1126276, 283, 1090, 1126

comment:4 by Ichthyostega, at 2018-03-10T21:33:40Z

Status: newaccepted

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.

  • obviously this is "the" most relevant istance of the dependency injection problem
  • singletons are just another instance of the same problem, in cases where lazy initialisation is sufficient

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.

comment:5 by Ichthyostega, 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 Ichthyostega, 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 Ichthyostega, at 2018-03-31T00:36:44Z

blockedby: 934934, 1133

comment:8 by Ichthyostega, at 2018-04-03T07:07:21Z

Resolution: done
Status: acceptedclosed
  • 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::Depend is now also the client-side front-end to talk to services in other layers and to plug-ins

comment:9 by Undercover Agent, 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

Note: See TracTickets for help on using tickets.