-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
logger object is not ensured to outlive all static local variables that it uses #2113
Comments
The lifetime of the spdlog logger is managed by |
the
the
the
and many other local static varaibles declared in a function scope, these variables may destruct ealier than our self-defined object, then in our object's destructor we can't use spdlog safely as I said. |
|
Unfortunately that's exactly our case :-) In our project , we use a static container to hold most of our objects as members. So it's somehow a special case, if spdlog support that all basic things outlive spd logger object , then there still will be no problem in our project. Our project is a library , so using static container to hold all whole lifetime objects is convenient. |
Actually, force all basic things to live longer than spd logger is viable and will be more safe and save lots of exceptions, but it's annoying which I understand . |
Hmmm, this must be a problem with the library design. Making the lifetime of other objects shorter than that of spdlog is probably the only workaround for the problem at this point. |
Making the lifetime of other objects shorter than spdlog is easy, but making the lifetime of other objects shorter than all the basic things that spdlog relies on is difficult and hard to ensure. I think out a way that can settle the lifetime issue of spdlog and won't break the library API: Create a common header file like
in spd logger's constructor, we call this |
@gabime Are the proposed changes acceptable? |
spdlog already has the details/registry singleton class to store globals, so maybe those mutexes can be placed in this class as well. |
According to these pages: It is illustrated that static variables will destruct in the reverse order of construction, so what we can do is to controll the construction order ourselves. static objects that construct earlier will destruct later automatically. (this is tested locally) According to the below page which refers to the very approach I mentioned: Finding C++ static initialization order problems If we declare a Since there is already a |
I dont understand what would be difference from current situation where all static object are local function statics and would destructed after the logger that created them. |
All local static objects declared in a function scope will construct when the first time we call this function, so it's hard to say these variables will initialize when program begins , it may initialize anytime when program is running. As they are static objects , they will destruct at end of program, but in the reverse order of initilization, so they will destruct ealier than any other static variables. If we are using spd logger in some static variables's destructor (this is exact our case, we are developing a library which use a static container to hold all variables inside), this may cause problem, because some variables that spd logger relies on may destruct already. |
Some static local varaibles that spd logger relies on may be not used when initilizing spd logger, they may be first used when we first log, or may be first used when first exception raises , or some other conditions to trigger than function. As they are initialized after spd logger initialized, so when program ends, they will destruct ealier than spd logger. I must admit this is a special condition, because we are logging in a static varaible's destructor. |
I've recently encountered the exact same issues as wangjunZeroZeroSeven. I was unable to come up with a perfectly safe solution (outside of removing any spdlog calls from local variable destruction). |
I second this, having encountered this exact problem too. Could we do both @wangjunZeroZeroSeven 's suggestion of a class that contains all the currently-static variables, but in doing so create a "spdlog instance" (pSpdlog->get(), rather than spdlog::get()) which:
This should maintain the existing API via the static instance, but provide the option of a user-managed instance of the "global" (but really only instance-scoped) object. |
@SirWhiteHat I have to point out that it's not absolutely safe either, as you can controll the life cycle of They may live shorter than spdlog instance and can cause problems. We have to force developers do not use spd logger in any destructor that executes at program end. |
@wangjunZeroZeroSeven I'm proposing keeping the existing API using the static spdlog instance, but we also allow use of a lifetime-managed spdlog instance (which contains the same as those existing static variables, but non-static). I admit that this wouldn't make the existing API safe for use in other static objects, but it would provide a separate lifetime-managed instance that could be used in such a scenario! |
Related: #1738 (comment) |
I am having the same problem as others, I know that a lot of this is duplicating what others said, but I really wanted to make sure why the current code has a problem. Statics are a minor code smell in application code and downright stinky in library code. A library has no control over when its dependent statics gets destroyed. Since spdlog statics are being created in spdlog modules, there is no deterministic way to order the statics so that the logging API is initialized first and destroyed last. Add to this mess that some of the statics are created lazily (on first use). That pretty much guarantees crashes if you are logging in a static destructor, because the statics created by an application will certainly have been created before spdlog's were created which means spdlog's statics will be destroyed before the application's. Which means that the static destructor is calling spdlog's destroyed logger. @wangjunZeroZeroSeven is 100% correct and it is completely normal to log on destruction within static variables. It is an unusual library that can't be called from a static object's destructor deterministically and safely. A common solution is to have a global startup / shutdown function for "C" APIs (I saw the solutions up there mention this as an alternative). Everything that needs to be created happens in there. Well designed C++ APIs that need startup / shutdown should include an RAII class that does the same startup / shutdown on construction / destruction. Users of libraries that provide either are responsible if they chose to use a static objects that call the library API in their destructors. A common solution for that issue is to put all the statics instances in one place in the order you want them created (logger RAII first, then all the objects that need the logger). That way, even in the face of exceptions, the logger is the last static destroyed. |
Is there already a solution to this problem? |
@weihhh Not that I have seen. |
Maybe, the logger simply should not rely on other static objects that can potentially be created after the logger? Those objects should be created on the free store and be managed by the logger object (possible via |
@adah1972 If you are saying that the logging library should not have static variables, then we are in agreement. If the library needs some static state, then it should offer the applications the opportunity to initialize and release them. |
I was saying that other global/static objects should not have lifetime issues with the logger object per se. And I believe it is doable. |
How I solved it without altering spdlog code, I have logging statements in the constructors and destructors, via pseudocode: all of this is in main:
This isn't ideal, the point of using smart pointers is that we shouldn't have to explicitly release them. But, because of the static state in spdlog, and the C++ rules surrounding static object destruction, this kind of solution is the only way I am aware of, right now, to deal with this. The application programmer should be able to use a library without being forced to manage statics in libraries they are using. |
I created a branch that attempts to address this issue:
Please share your thoughts. #include "spdlog/spdlog.h"
struct my_struct {
my_struct() {
spdlog::info("my_struct ctor");
}
~my_struct() {
spdlog::info("my_struct dtor");
//spdlog::shutdown(); // call this if you are sure spdlog would never be called again
}
};
my_struct global_struct;
int main(int, char *[]) {
spdlog::info("In main");
//spdlog::shutdown(); // call this if you are sure spdlog would never be called again
} |
For static objects, the order of destruction is in reverse of the order of construction, but the latter is hard to control except among static objects defined within the same translation unit. This means that spdlog may be destructed before spdlog::drop() is invoked in the destructor, which leads to a segmentation fault due to memory use-after-free. Move call of spdlog::drop() from destructor to main() function, to ensure the logger may be re-registered with the same name in tests. Use Scope Guard pattern to implement RAII for logger registration. Link: ros2/rclcpp#933 Link: gabime/spdlog#1738 Link: gabime/spdlog#2113 Link: https://en.wikibooks.org/wiki/More_C++_Idioms/Scope_Guard Closes: https://hsdes.intel.com/appstore/article/#/22019839238 Signed-off-by: Peter Colberg <[email protected]>
For static objects, the order of destruction is in reverse of the order of construction, but the latter is hard to control except among static objects defined within the same translation unit. This means that spdlog may be destructed before spdlog::drop() is invoked in the destructor, which leads to a segmentation fault due to memory use-after-free. Move call of spdlog::drop() from destructor to main() function, to ensure the logger may be re-registered with the same name in tests. Use Scope Guard pattern to implement RAII for logger registration. Link: ros2/rclcpp#933 Link: gabime/spdlog#1738 Link: gabime/spdlog#2113 Link: https://en.wikibooks.org/wiki/More_C++_Idioms/Scope_Guard Closes: https://hsdes.intel.com/appstore/article/#/22019839238 Signed-off-by: Peter Colberg <[email protected]>
For static objects, the order of destruction is in reverse of the order of construction, but the latter is hard to control except among static objects defined within the same translation unit. This means that spdlog may be destructed before spdlog::drop() is invoked in the destructor, which leads to a segmentation fault due to memory use-after-free. Move call of spdlog::drop() from destructor to main() function, to ensure the logger may be re-registered with the same name in tests. Use Scope Guard pattern to implement RAII for logger registration. Link: ros2/rclcpp#933 Link: gabime/spdlog#1738 Link: gabime/spdlog#2113 Link: https://en.wikibooks.org/wiki/More_C++_Idioms/Scope_Guard Closes: https://hsdes.intel.com/appstore/article/#/22019839238 Signed-off-by: Peter Colberg <[email protected]>
I have just hit this issue. I'm creating a wrapper object around |
This probably does not work, as people encountering this issue may have many static objects, or, worse, they use several different libraries, each of which wants to use a logger in its destructor.... I think a reference-counted method is probably necessary. The "current" static object should decrease the global reference count, normally shutting down the logger when the program exits. All (static) objects that need to use the logger in its destructor should call This will not change the current behaviour, while providing a way to people who need to control the logger lifetime. Of course, the refactoring to change all static objects that the logger depends on are still necessary. |
I think this issue could be solved the same way as #3003 , if the user can provide an instance of the registry that they allocated, they will also be able to control it's lifetime (as long as all mutable global state is contained in the registry). |
Right, this and also having spdlog dynamically allocate the default registry instance would solve this. |
In this library, it is preferred that local static variables defines in a function scope, in this way this static variable will construct the first time we call this function .
Since static variables will destruct in the inverted order of construction, if the function is called after program starts, the static varaible inside the function will construct when program is running and it will destruct earlier than any normal destructors which execute at the end of program.
As a result, we can not safely use logger object in normal destructors since it may call some static variables that already destruct ! I met this program in my project , could you
force
all static local variables outlivelogger
object ?The text was updated successfully, but these errors were encountered: