3

I have a logging system, which basically uses a thread-local buffer to log. This helps in reducing locking. A bunch of message can be written into the thread-local buffer and flushed in one shot. And also since it is thread-local, we can avoid allocating buffer per log message.

Anyway the issue is during the process exit. We are seeing crash while accessing the thread-local buffer.

The thread-local object I have is something like std::vector<Buffer>. [vector because there are multiple buffers]. A representative code is something like this.

Buffer* getBuffer (int index)
{
    static thread_local auto buffers =    std::make_unique<std::vector<Buffer>>();
    return buffers ? buffers->at(index) : nullptr;
}

Now when the program exits and the global destructors are called and unfortunately some of them logs. The destructors are called from main thread (which otherwise does nothing). So when the first global object is destroyed and it calls the logger, the thread_local buffer is created, but it is immediately destroyed, because the objects are destroyed in reverse order of creation and this is the last static object created. When the next global object destructor calls logger, it is effectively accessing destroyed object, which I think is the problem.

But I looked at the unique_ptr destructor and it does set the pointer inside it to nullptr [or atleast it sets the pointer to default constructed pointer - which I believe is value initialized to zero??]. So my return buffers ? buffers->at(index) : nullptr; check should have prevented the access to freed object isn't it?

I created a toy program to try this out and I see that the buffers ? check does prevent the access. But in the real code base that is not happening. At the point of crash the vector is accessed at it is already toast.

Now if someone can tell me a magic solution, it will make my life easy :-). Otherwise any idea why the bool operator of unique_ptr does not return false. Is it because it is classic undefined behavior accessing a destroyed object.

I read in stack-overflow that if the object has a trivial destructor, it is okay to access it after destruction. In that case would my problem is solved if I create a thread-local bool just above the unique_ptr, and set it to true in the destructor of a wrapper class containing unique_ptr?

xaxxon
  • 19,189
  • 5
  • 50
  • 80
MGH
  • 475
  • 7
  • 19
  • 2
    welcome to the static initialization order fiasco. https://isocpp.org/wiki/faq/ctors#static-init-order There are approaches here to make a system that is guaranteed to work regardless of what order the compiler/linker puts things together. Hrmm, I guess they never wrote that part. Also here; https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Nifty_Counter#Also_Known_As – xaxxon Oct 16 '18 at 22:46
  • @xaxxon: That's not a real problem (its only a problem if you don't know about it in the first place). There is an easy solution to the ordering problem. https://stackoverflow.com/a/335746/14065 – Martin York Oct 16 '18 at 23:04
  • 1
    @MartinYork and the most-vexing parse isn't vexing if you know about it, either. But clearly these things boggle enough people to have gotten their own fancy names :) – xaxxon Oct 16 '18 at 23:42
  • While there are solutions to this problem, I think that generally it is preferable to do the cleanup before exiting `main` as you have more control with explicit code. – Phil1970 Oct 16 '18 at 23:42
  • I would echo @Phil1970 comment. The problem is that you have static storage duration objects in global scope. Thus code is executed after `main()` is exited. It is much preferable to do this in main. Have a log object created in main. Then pass it to any objects that need to log. – Martin York Oct 16 '18 at 23:48

3 Answers3

3

But I looked at the unique_ptr destructor and it does set the pointer inside it to nullptr

Does not matter. Once the object's lifespan is over accessing the object in any way is UB. So this will not work.

Looking at the issue in terms of lifespan

The Issue.

Your global log is going out of scope before some of your local buffers.

The solution

The global log must live longer than your local buffers.

How to achieve it

If the global log must live longer than the buffers it must be created first. To force this make sure your local buffer ask for a reference to the global buffer while they are being constructed. This will force the global log to be created first and thus be alive when the local buffer is destroyed.

Example Solution

Something like this:

class Log
{
    public:
        static Log& getLog()
        {
            static Log theOneAndOnlyLog;
            return theOneAndOnlyLog;
        }
    }
};

class BufferFrame
{
    std::vector<Buffer>   buffer;
    BufferFrame()
    {
        Log::getLog();   // Force the log to be created first.
                         // Note: Order of destruction is guranteed
                         //       for static storage duration objects
                         //       to be the exact reverse of the order of
                         //       creation.
                         //
                         // This means if A is created before B
                         // Then B must be destroyed before A
                         //
                         // Here we know that `theOneAndOnlyLog`
                         // has been constructed (fully) thus `this`
                         // object is created after it. Thus this object
                         // will be destroyed before `theOneAndOnlyLog`.
                         //
                         // This means you can safely accesses `theOneAndOnlyLog`
                         // from the destructor of this object.
    }
    ~BufferFrame()
    {
        // We know the log has been created first
        // So we know it is still alive now.
        foreach(Buffer& buf: buffer) {
             Log::getLog() << buf; // Dump Buffer
        }
    }
    Buffer& at(std::size_t index)
    {
        return buffer.at(index);
    }
};
Buffer& getBuffer(int index)
{
    static thread_local BufferFrame buffers;
    return buffers.at(index);  // Note this will throw if index is out of range.
}

class MyObjectThatLogsToBuffer
{
    public:
        MyObjectThatLogsToBuffer()
        {
            getBuffer(0);   // Have created the FramBuffer
                            // It is created first. So it will be
                            // destroyed after me. So it is safe to
                            // access in destructor.
        }
        ~MyObjectThatLogsToBuffer()
        {
            log("I am destroyed");  // assume this calls getBuffer()
        }                           // while logging. Then it will work.
};
Martin York
  • 257,169
  • 86
  • 333
  • 562
  • You can always pass the `Log` object to the `BufferFrame` constructor as an argument if you want to fully create the first object before starting the creation of the second one. – Phil1970 Oct 16 '18 at 23:24
  • @xaxxon: An object is not fully constructed until its constructor is complete. Thus if another object 'X' is created while you are in the constructor it 'X' is created first and thus will be inserted into the destruction list first. – Martin York Oct 16 '18 at 23:25
  • @Phil1970: That is another perfectly valid way to achieve the same result. – Martin York Oct 16 '18 at 23:25
  • @xaxxon: The `Schwartz` counter is good for situations where you can not force the order of creation explicitly. Note in my code I wrap globals inside functions so to get them you need to call the function thus enforcing an order. Things like `std::cout` (another global) does not have this advantage so could potentially be implemented by `Schwartz` counters. – Martin York Oct 16 '18 at 23:27
  • @MartinYork The issue I have is not the global log object getting destroyed before the buffer. The global Log object destruction is taken care of by simply dynamically allocating and leaking it. My issue is thread-local buffers are gone, when I am trying to log something into it. If 'some' global object destructor calls `log("I am destroyed")`, inside the `log` function when I access the thread-local buffer it is already destroyed. And as I explained above, it is destroyed as soon as it is created [because it is created in the global destruction context] – MGH Oct 16 '18 at 23:34
  • @MGH The same rule applies. If the object doing the logging needs to accesses something during its destructor, then all it needs to do is access the object during its constructor to force the lifespan to be as long as required. – Martin York Oct 16 '18 at 23:36
  • @MGH Added a logging object concept. – Martin York Oct 16 '18 at 23:40
  • @MartinYork Okay got it. But this being logging, there is no way to enforce it. Since logging is so generic, some destructors might call some function without even knowing that it does some logging. It is basic issue with using global / static variables. But I will have to come up with a solution where the order of creation cannot be enforced. – MGH Oct 16 '18 at 23:40
  • 1
    @MGH: Don't leak the logging object. That will just cause you uses down the road. Have you ever tried de-bugging an application looking for a leak when the application already leaks. – Martin York Oct 16 '18 at 23:42
  • @MGH You can wrap any static object that may log with `MyObjectThatLogsToBuffer` (or just make it inherit from it). Or you can use `Schwarz` counters. – Martin York Oct 16 '18 at 23:44
  • @MartinYork Hmm we could make it work that way. [ though inheriting from Log make it sounds like a misuse] . But that is not a solution in my case, as it is a large code base and I cant convince everyone follow this approach for all the statics / globals they create. – MGH Oct 17 '18 at 00:27
2

The Schwarz counter or Nifty Counter Idiom can do what you want, but it's not "magic". You may be able to come up with a macro to make it less cumbersome to use (check out non-standard __COUNTER__), but the gist of it is:

In each compilation unit (.cpp file) at the very top, you put an instance of a variable that increments/decrements a static counter and a pointer to a real object of the logger type.

When the counter goes from 0 to 1, the "goal" object is dynamically created. When the counter goes from 1 to 0, the "goal" object is destroyed. Otherwise the constructor/destructor of this manager object don't do anything.

This guarantees creation before first use and destruction after last use.

https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Nifty_Counter#Also_Known_As

xaxxon
  • 19,189
  • 5
  • 50
  • 80
  • 1
    If it's sufficient for your use case, I suggest using @martinyork's answer instead, as it is simpler. I'm reproducing his comment about why you may want to use the Schwarz/Nifty counter I answered with instead: "The Schwartz counter is good for situations where you can not force the order of creation explicitly. Note in my code I wrap globals inside functions so to get them you need to call the function thus enforcing an order. Things like std::cout (another global) does not have this advantage so could potentially be implemented by Schwartz counters." – xaxxon Oct 16 '18 at 23:29
-3

you could use std::weak_ptr there to track stuff gone out of scope in other thread.
i dont have an easy sample on that. not easy one is there:
https://github.com/alexeyneu/bitcoin/commit/bbd5aa3e36cf303779d888764e1ebb3bd2242a4a

key lines on that:

    std::weak_ptr<int> com_r;
...
   bhr->SetProgressValue(hwnd , com_r.expired() == 0 ? reserve = *com_r.lock() : reserve, 190);

and

extern  std::weak_ptr<int> com_r;
...
//inside a class
   std::shared_ptr<int> sp_tray;

   com_r = sp_tray;

  *sp_tray = nVerificationProgress*190;

and this is testcase(updated)

#include "stdafx.h"
#include "bay.h"
#include <condition_variable>
#include <thread>
#include <atomic>
#include <memory>
#include <sstream>
#include <string>
#include <vector>
#include <iostream>

#define MAX_LOADSTRING 100

// Global Variables:
HINSTANCE hInst;                                // current instance
wchar_t szTitle[MAX_LOADSTRING];                    // The title bar text
wchar_t szWindowClass[MAX_LOADSTRING];          // the main window class name

// Forward declarations of functions included in this code module:
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPTSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // TODO: Place code here.
    MSG msg;
    HACCEL hAccelTable;

    // Initialize global strings
    LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadString(hInstance, IDC_BAY, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    // Perform application initialization:
    if (!InitInstance (hInstance, nCmdShow))
    {
        return FALSE;
    }

    hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_BAY));

    // Main message loop:
    while (GetMessage(&msg, NULL, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return (int) msg.wParam;
}

ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEX wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_BAY));
    wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName   = MAKEINTRESOURCE(IDC_BAY);
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    return RegisterClassEx(&wcex);
}

HWND hWnd;



BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
    hInst = hInstance; // Store instance handle in our global variable

   hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}
    std::thread u,u2;
    UINT CALLBACK hammer(VOID *c);
    UINT CALLBACK hammersmith(VOID *c);
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    int wmId, wmEvent;
    HDC hdc;

    switch (message)
    {
    case WM_COMMAND:
        wmId = LOWORD(wParam);
        wmEvent = HIWORD(wParam);
        // Parse the menu selections:
        switch (wmId)
        {
        case IDM_EXIT:
            break;
        case IDM_LETSGO:
            u = std::thread(&hammer,(LPVOID)NULL);
            u2 = std::thread(&hammersmith,(LPVOID)NULL);
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
        break;

    case WM_CLOSE:
        DefWindowProc(hWnd, message, wParam, lParam);
        break;          
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
return 0;
}
std::shared_ptr<int> sp_tray;
std::weak_ptr<int> com_r;
std::mutex com_m;   
UINT CALLBACK hammer(VOID *c)
{
    int reserve = 0;
    AllocConsole();
    freopen("CON", "w", stdout);
    while (1)
    {
    std::unique_lock<std::mutex> lb(com_m);
    reserve = com_r.expired() == 0 ? *com_r.lock(): 5;
    lb.unlock();
    std::cout << reserve;   
    }

    return 0;

}
UINT CALLBACK hammersmith(VOID *c)
{   
    while (1)
    {   
        std::unique_lock<std::mutex> lb(com_m);
        sp_tray = std::shared_ptr<int>(new int(7));
        com_r = sp_tray;
        lb.unlock();    
        sp_tray.reset();
    }

    return 0;

}
  • 1
    You've got a race condition between the expired and the lock call. – rubenvb Oct 17 '18 at 09:26
  • no, you wouldn't .Make testcase and you'll see it. Ofc check and lock should be stacked together for that. – Алексей Неудачин Oct 17 '18 at 11:31
  • 2
    A non-failing testcase does not prove you don't have a race condition. What do you mean by "stacked together"? I'm unfamiliar with that concept in the context of the C++ abstract machine. To be clear: I mean that between your `expired` call and the subsequent `lock` call, someone could have called `clear` on the underlying `shared_ptr` (in another thread), making `lock` return a `nullptr` even though moments before, it wouldn't have. The [documentation for `lock`](https://en.cppreference.com/w/cpp/memory/weak_ptr/lock) says it does this sequence atomically, which your code does not. – rubenvb Oct 17 '18 at 14:31
  • call of `std::shared_ptr::clear()` is not an atomic operation.First of all it zeroes out shared_ptr's weak reference counter so `expired` will return `1` even though lock address is still valid. in other words : we have two cores ,threads are on own core each.Both instructions(read and wipe-out) are prefetched to cpu cache in situation you're talkin about. Idk details here but it handled right either by os or by hardware(memory controller / cpu). Page you'd post link to is not a documentation and there's nothing about this there. – Алексей Неудачин Oct 17 '18 at 15:38
  • So if `expired` returns 0 you have enough time to grab lock result. – Алексей Неудачин Oct 18 '18 at 14:29
  • "enough time" sounds hand-waving to me. I still don't believe this is guaranteed by the Standard. – rubenvb Oct 18 '18 at 14:52
  • I am not sure how this is relevant to my question. Fundamentally my requirement is to either detect if a static thread-local is destroyed. Or prevent it from being destroyed until after the final use. Introducing a global `weak_ptr<>` IMHO cannot do that, because it raises the question of how I would know if the `weak_ptr<>` itself is destroyed or not. Accessing a destroyed `weak_ptr<>` is UB as it has a non-trivial destructor. – MGH Oct 18 '18 at 20:25
  • 1
    Also as @rubenvb pointed out. I think `com_r.expired() == 0 ? reserve = *com_r.lock() : reserve` should be changed to `com_r.lock() ? reserve = *com_r.lock() : reserve`. In the latter case the lifetime of the temporary `shared_ptr` created by first lock, will be extended till end of statement making the second `lock()` return consistent result. Or basically lock before the conditional - with an explicit shared_ptr variable. Which will be clean and efficient? – MGH Oct 18 '18 at 20:29
  • first - no it cant be destroyed. second - no, `expired` check is atomic and "latter case" is different thing . This all is not about to prevent smth – Алексей Неудачин Oct 18 '18 at 21:21
  • yeah `com_r.lock() ?` seems to work. But original object is out of scope and it may matter somewhere . – Алексей Неудачин Oct 18 '18 at 23:05
  • "no, expired check is atomic" no thats not true. Read on [cppreference](https://en.cppreference.com/w/cpp/memory/weak_ptr/expired): This function is inherently racy if the managed object is shared among threads. In particular, a false result may become stale before it can be used. – 463035818_is_not_an_ai Oct 26 '18 at 14:29
  • @user463035818 we here are talkin about goin out of scope, not about "not managed to go in". It is true. this stuff you're rely on is not a documenation (again) – Алексей Неудачин Oct 26 '18 at 14:36
  • "not managed to go in" ?? I am just saying that `expired` is not atomic contrary to your claim. What is "this stuff" i rely on? If you mean cppreference, then of course it **is** a documentation, not the official one, though a rather reliable one – 463035818_is_not_an_ai Oct 26 '18 at 14:38
  • object not yet constructed . `.expired()` check is atomic operation (again) – Алексей Неудачин Oct 26 '18 at 14:40
  • I dont know... maybe we are talking about a different language, maybe you have a different understanding of what "atomic" means, maybe you are just trolling, anyhow I give up (not again, but only once and only this time) – 463035818_is_not_an_ai Oct 26 '18 at 15:13