CriticalSection crashes with VS 2022

This was discussed very recently in the forum.

  1. Even though hosts are likely to launch other plugins in different process, they’re very unlikely to launch different instances of the same plugin in a different process.
    (It still might be a different process than the host).

  2. Singletons work just fine if you load the plugin in an isolated process. It just won’t be shared, but still work.

As a plugin developer you want to save user resources at any junction you can, and the memory for shared resources is a easy win that would greatly benefit your users in projects with many plugins, don’t prevent them from having it. :slight_smile:

We probably need to discuss the design that leads you to such problems. :slight_smile:

Those are surely solvable, I’ve used many singleton LnF using the JUCE singleton class + DeletedAtShutDown and it works really well.

This is true. When you set up it the right way, you can eliminate the singleton lifetime problems.

There is one problem I wasn’t able to fix:

It looks like it is not possible to set a different LookAndFeel with other typefaces to a specific component. Some weird singleton magic happens in the background.

I agree :slight_smile:

A short resume:
Singletons are useful in the plug-in world when you share resources and solve technical problems. But they are a bad design choice otherwise.

The singleton design pattern is ages old and has very many legitimate and time proven use cases. Limiting the discussion to a specific idea of audio plug-ins is not helpful. Singletons are very common and they should “just work”.

Sometimes singletons are needed to cope with the fact that C++ doesn’t support class-side polymorphism, i.e. you can’t have classes with “static virtual” methods, so you need to implement your hierarchy (e.g. of policies) on the instance side of a singleton. And that’s only one example.

It’s a C++ thing then… Where I come from (C#, Java), static singleton classes are considered a bad design. I don’t speak about the state sharing optimisation mentioned above.

Fortunately, I have always found a way around static states and singletons, even in C++. I also avoid inheritance and try to live the SOLID principle as much as possible in the C++ world and keep things decoupled and testable.

I think there is a misconception here: the singleton design patter is to be avoided. It has major downsides with C++ (initialization and destruction order), JUCE (leak detector), general program principles (side affects not communicated in the interface/header and no capsulation) and the impossibility for proper unit tests (for obvious reasons).

The way to go here (IMO) is passing references (or using other modern object oriented design patterns) and create the singleton instance at the very outside of the application (possibly the Main.cpp of your project).

You then don’t have that „singleton code smell“ and still can share static resources between instances if you want.

1 Like

It’s true that you shouldn’t be randomly pick a static/singleton whenever you need to pass a variable into a class - that would just be bad design and it’s good to be explicit about your resources.

However - Singletons are a legit design pattern.
Whenever you’re doing std::cout - cout is a static object! Logging is implemented with operator overloading on that object.

You could pass the cout object directly whenever you need to log - it just makes no sense.
Drivers/system handles/thread handles/etc are almost always pointers to static or singleton objects even when they’re hidden behind RAII objects for the user.

malloc is also a global object BTW. Not only that - there’s also a big (also global) lock around it.

JUCE also creates many singletons. When hosting plugins, the dll is cached in a singleton. The message thread is a singleton, typeface for fonts are stored in a singleton…

All those examples you are giving, lack the exact problems I outlined:

std::cout does not really qualify for any sophisticated logging mechanism. It is not possible to properly distinguish between severity nor is it possible to direct the log stream to a destination without leaking that implementation detail to all log clients.

There are a variety of scenarios where this makes perfect sense. One of my application for example has to make a very strong guarantee of logging runtime problems for me to notice configuration mistakes. The fact that these problems are caught and logged is being tested. Wouldn’t be possible when using std::cout. std::cout is also not semantically thread-safe and you should not write to it on your realtime threads anyway.

Since this is a c standard library function, the options for possible designs are limited. And it comes with problems. Logic for example (I believe) has a very tacky solution adding a hook to malloc, to be able to display a warning when RAM gets full. Would be breeze feature to implement, when you don’t really on a global function for the entire memory management.

I think there are possible contra points to be made for every one of them. Especially the message thread handling of JUCE makes writing unit tests for UI components relatively hard. I also keep getting assertions when trying to pre-create a UI or window on a different thread and then pass it to the message thread for synchronised rendering.

1 Like

And yet, this is the standard logger in C++. the JUCE Logger is also global BTW - and pretty much any logger for most languages (JS’s console.log) would be global both from the user’s perspective (no one passes the console object into a function…) and the implementation.

If you write your own logger, I assume you won’t be passing it around.

There are a few solutions trying to do the alternative. The C++ STL classes take an allocator argument (that usually resolves to the global object anyway), PMR allocators, etc.

The result is very, very difficult and error prone to use and much more problematic than the global allocator solution. I think some problems are just canonically solved with a singleton/global and other solutions are worse (at least for most use cases).

Yes, but imagine the alternative. You would have extremely difficult and verbose interfaces, passing the message thread explicitly to every single class and function.

Making the JUCE design more unit test friendly BTW is not in competition with the message thread being global. In other frameworks you can just create your own dummy message thread (just like you can do with juce::Logger, replacing it globally) and that would work well for unit tests.

What’s usually done for unit tests is decoupling the logical part of the class from the part that interacts with threads, and then you test it separately.

But again this has nothing to do with Singletons - anything to do with system resources like the message thread pretty much has to be global, it’s just makes no sense to implement otherwise.

I won’t be arguing this any longer, there are numerous resources, papers and learning material on why singletons are to be avoided and how to do it better. That there is not one good solution for every case is obvious.

It’s easy to argue that it cannot be replaced with something different when you are giving examples of cases, were the software design was bad to begin with many years ago and changing it now is of course not possible.

1 Like

I’m aware of many of them. Usually those don’t refer to system-wide resources situations.

Most of such papers/blogs/etc refer to a high level system design.
For example, say you have this code:

void doSomething(Object& object)
{
    object.doSomething();
    GlobalLogger::log(object);
}

This code has side effects, as it interacts with system resources.
You could pass the logger of course:

void doSomething(Object& object, Logger& logger);

But it would still be a bit difficult to unit test the logging bit, as the logger itself writes to some global system memory outside of the program.

What is usually recommended to do is to instead not pass the logger, and do something like:

std::string doSomething(Object& object)
{
   object.doSomething();
   return object.toString();
}
//On use:
GlobalLogger::log(doSomething(object));

This gives you a fully unit testable code with no side effects other than the object you passed in. The singleton is only used to interact with the system resource.

I believe most blogs, etc, would also recommend this - and would not recommend passing the entire global system resources like allocators, logs, threads, for each function.

Can you show an example from any language or framework where doing things like message thread or logging is not done with singletons?

Just for reference - in languages like Javascript, when you use async or await, there’s an invisible global message thread object… It’s extremely difficult to implement otherwise in a way that’s usable.

This whole discussion reeks of “Stack Overflow” style elitism/arrogance.

Q: Hello, I have problem X. How to fix it?
A: You should never use X. Find another way to do what you want.

There are several legitimate use cases for global variables. Just because you don’t have a use case doesn’t mean other people don’t.

If you have nothing helpful to say, I would appreciate it if you would at least refrain from distracting from the discussion.

3 Likes

Sole instances of some class with global/namespace/class scope are found everywhere. It is impossible to run anything without at least a few singletons getting involved. Even if you don’t call them this way or don’t code them yourself, they are used everywhere in libraries and operating systems.

The topic of this thread was how to simplify the initialization of Juce singletons (whether you like them or not) by means of declarative code (i.e. define something only in a single place in order to support easy maintenance and future extensions – a thing much more consequential than any design pattern purity test).

As a solution I suggested to use inline static in headers to keep everything in one place. I didn’t mean to start a philosophical debate about singletons.

2 Likes

I think the only reason was historical. We’ve now added some new macros to allow declaring singletons inline, which means that it’s no longer necessary to add a matching IMPLEMENT macro in a .cpp file.

2 Likes

Wow. That’s wonderful news. Thanks for taking the time to address this. I will give it a test.

Equally important now is to use these new macros for all built-in JUCE singletons, like the font cache.

To name an example, I’m using Font Awesome 6 Pro icons defined as global variables holding an AttributedString each. A convenient way to add some nice scalable vector icons to your app that are much easier to integrate than SVG (you can use them anywhere as part of, or instead of, text). The font cache currently crashes the app at startup for the reasons explained above.