wxLogWindow causing memory leak?

If you are using the main C++ distribution of wxWidgets, Feel free to ask any question related to wxWidgets development here. This means questions regarding to C++ and wxWidgets, not compile problems.
Post Reply
evendine
Experienced Solver
Experienced Solver
Posts: 70
Joined: Tue Dec 05, 2006 6:39 pm

wxLogWindow causing memory leak?

Post by evendine »

Hi,

while tracking down memory leaks in an app which produces many log entries recently, noticed that whenever a wxLogWindow is opened, the app's memory usage increases over time. Have read around the subject and tried several approaches (e.g. attempting to destroy the wxLogWindow when not in use, using a top level window as parent, etc), but the problem remains.

If the wxLogWindow creation line is removed from the following code example, there is no additional memory consumption while logging to file. Adding it back in produces an apparent leak. Any insight much appreciated - have a feeling i'm missing something during initialisation of the window?

Code: Select all

///////////////////////////////////////////////////////////////////////////////
// Dual logging / wxLogWindow memory consumption (leak?) TEST
///////////////////////////////////////////////////////////////////////////////

#ifndef WX_PRECOMP 
    #include "wx\wx.h"    
#endif 

#include "wx/log.h"

class MyApp : public wxApp 
{ 
   public:    
      virtual bool OnInit() 
      { 
         if(!wxApp::OnInit()) 
            return false;    

         // Redirect default error target to file
         FILE *LogFile = fopen(".\\TestLogFile.txt", "w+");
         delete wxLog::SetActiveTarget(new wxLogStderr(LogFile)); 

         // Setup log file timestamp DT format
         wxLog::SetTimestamp("%Y-%m-%d %H:%M:%S"); 
         
         // Add LogWindow (creates a wxLogChain)
         LogWindow = new wxLogWindow((wxWindow *)0, "Event Log", true);

         // Add some data to the log...
         while(true)
            wxLogError("Test log entry................................................");

         return true; 
      } 

   private:
      wxLogWindow         *LogWindow;
}; 

IMPLEMENT_APP(MyApp)
wxLib 3.0.3
MSVS 2010 Ultimate RTMRel v10.0.30319.1
MSVC++ 2010
Windows 7 Ultimate x64 v6.1.7601 SP1
User avatar
doublemax
Moderator
Moderator
Posts: 19160
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxLogWindow causing memory leak?

Post by doublemax »

A wxLogWindow doesn't "forget" anything, every line you output is stored in memory. So it's normal when the memory usage increases over time.
Use the source, Luke!
evendine
Experienced Solver
Experienced Solver
Posts: 70
Joined: Tue Dec 05, 2006 6:39 pm

Re: wxLogWindow causing memory leak?

Post by evendine »

Ah, thanks - had assumed it uses the same sort of approach that a wxListControl does in virtual mode...

Is it right to think that any class derived from wxLog can be used as a target? If so, guess it would be possible to build a simple frame which say snap-shots the end of the file log and displays the results in multi-line text control? (A snapshot would be more useful in a way)...
wxLib 3.0.3
MSVS 2010 Ultimate RTMRel v10.0.30319.1
MSVC++ 2010
Windows 7 Ultimate x64 v6.1.7601 SP1
User avatar
doublemax
Moderator
Moderator
Posts: 19160
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxLogWindow causing memory leak?

Post by doublemax »

Is it right to think that any class derived from wxLog can be used as a target?
Yes.
If so, guess it would be possible to build a simple frame which say snap-shots the end of the file log and displays the results in multi-line text control?
Yes, although it might be a little slow if you're logging at a high speed. Maybe some in-memory buffer that is limited to a certain amount of lines would be better.
Use the source, Luke!
evendine
Experienced Solver
Experienced Solver
Posts: 70
Joined: Tue Dec 05, 2006 6:39 pm

Re: wxLogWindow causing memory leak?

Post by evendine »

Eventually figured out how to delete the associated log target when a wxLogWindow is closed, but for some reason it prevents the app from closing cleanly, which has been reported elsewhere, as i recall.

In the end decided to go the custom log target + frame route and reasonably happy with the result, which can be treated pretty much like an ordinary frame. It's a small class which allows FIFO or free running modes and has a few buttons to allow control of the entries as they scroll through a wxTextCtrl. Used the TextCtrl itself as the memory buffer in FIFO mode, deleting a line from the top of the ctrl when a new one is added at the bottom, once the definable line limit is reached. The free running mode uses increasing amounts of memory as might be expected, but releases it when the frame closes, unlike wxLogFrame.

Here's the code - hopefully useful to someone out there :) Any enhancements / problems / bugs, please let me know...

Code: Select all

///////////////////////////////////////////////////////////////////////////////
// Dual logging (file + frame) custom log target wxLogWindow alternative
///////////////////////////////////////////////////////////////////////////////
//
#ifndef WX_PRECOMP 
    #include "wx\wx.h"    
#endif 

#include "wx/log.h"
#include "wx/timer.h"
#include "wx/app.h"

#define ART_MAX_FIFO_LOG_MSG_COUNT 200
#define ART_MAX_LINE_LENGTH 500

enum
{
   ART_START_STOP_BTN = wxID_HIGHEST + 1,
   ART_PAUSE_CONTINUE_BTN,
   ART_FIFO_DATA_TIMER,
   ART_FIFO_MODE,
   ART_FREE_RUNNING_MODE,
   ART_OP_MODE_RUNNING,
   ART_OP_MODE_STOPPED,
   ART_OP_MODE_PAUSED
};

#define ART_NEED_TEST_DATA    // Comment out when not testing

///////////////////////////////////////////////////////////////////////////////
//
class ArtFIFOLog : public wxFrame, wxLog
{ 
   public: 
      ArtFIFOLog(int ThePrimaryMode = ART_FIFO_MODE) : 
         wxFrame((wxFrame *)0, wxID_ANY, "Event Log"), wxLog()
      {
         SetMinSize(wxSize(600, -1));
         ShouldRun = true;
         DataTimer = 0;
         MsgCount = 0;
         PrimaryMode = ThePrimaryMode;
         OperationalMode = ART_OP_MODE_RUNNING;

         // Create log target for frame (entries arrive via DoLogText() below)
         new wxLogChain((wxLog *)this);   

         // Add the controls + connect to event handler
         wxBoxSizer *TheTopSizer = new wxBoxSizer(wxVERTICAL);
        
         wxPanel *ThePanel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 2521440L | wxCLIP_CHILDREN); 

         LogCtrl = new wxTextCtrl(ThePanel,
                                 wxID_ANY,
                                 wxEmptyString,
                                 wxDefaultPosition,
                                 wxSize(600, 300),
                                 wxTE_LEFT | wxTE_MULTILINE | wxNO_FULL_REPAINT_ON_RESIZE);

         StartStopBtn = new wxButton(ThePanel, ART_START_STOP_BTN, "Stop"); 
         PauseContinueBtn = new wxButton(ThePanel, ART_PAUSE_CONTINUE_BTN, "Pause"); 

         TheTopSizer->Add(LogCtrl, 1, wxEXPAND | wxALL, 5);        
         TheTopSizer->Add(StartStopBtn, 0, wxEXPAND | wxALL, 5);
         TheTopSizer->Add(PauseContinueBtn, 0, wxEXPAND | wxALL, 5);

         Connect(ART_START_STOP_BTN, wxEVT_COMMAND_BUTTON_CLICKED, 
            wxCommandEventHandler(ArtFIFOLog::OnButton)); 
         
         Connect(ART_PAUSE_CONTINUE_BTN, wxEVT_COMMAND_BUTTON_CLICKED, 
            wxCommandEventHandler(ArtFIFOLog::OnButton)); 

         Connect(wxID_ANY, wxEVT_CLOSE_WINDOW, 
            wxCloseEventHandler(ArtFIFOLog::OnClose)); 

         ThePanel->SetSizer(TheTopSizer); 
         TheTopSizer->Fit(this);

         Show();

#ifdef ART_NEED_TEST_DATA        
         StartDataTimer();
#endif
      }
   
   private:

      // wxLog supplied messages arrive here...
      virtual void DoLogText(const wxString &TheMsg)
      {
         // Discard new entries
         if(OperationalMode == ART_OP_MODE_STOPPED)
            return;

         // Store entries for later use
         if(OperationalMode == ART_OP_MODE_PAUSED)
         {
            PauseBuffer.Add(TheMsg);
            return;
         }

         // ART_OP_MODE_RUNNING
         if(PrimaryMode == ART_FREE_RUNNING_MODE)
         {
            LogCtrl->AppendText(TheMsg + "\n"); 
         }
         else  // ART_FIFO_MODE
         {
            // Append messages to the log until limit's reached
            // then remove a line for each added
            if(MsgCount < ART_MAX_FIFO_LOG_MSG_COUNT)
            {
               LogCtrl->AppendText(TheMsg + "\n");         
               MsgCount++;
            }         
            else if(MsgCount >= ART_MAX_FIFO_LOG_MSG_COUNT)
            {
               Freeze();
               RemoveTopLine();
               LogCtrl->AppendText(TheMsg + "\n");  
               Thaw();
            }
         }
      } 	

      void RemoveTopLine(void)
      {
         // Find and remove top line
         int TheEndPoint = LogCtrl->GetRange(0, ART_MAX_LINE_LENGTH).Find("\n");
         LogCtrl->Remove(0, TheEndPoint + 2);
      }
 
      void OnButton(wxCommandEvent &TheEvent)
      {
         switch(TheEvent.GetId())
         {
            case ART_START_STOP_BTN:
            {
               if(OperationalMode == ART_OP_MODE_RUNNING)
               {
                  OperationalMode = ART_OP_MODE_STOPPED;
                  StartStopBtn->SetLabel("Start");
                  PauseContinueBtn->Disable();
               }
               else if(OperationalMode == ART_OP_MODE_STOPPED)
               {
                  OperationalMode = ART_OP_MODE_RUNNING;
                  StartStopBtn->SetLabel("Stop");
                  PauseContinueBtn->Enable();
               }
               break;
            }

            case ART_PAUSE_CONTINUE_BTN:
            {
               if(OperationalMode == ART_OP_MODE_RUNNING)
               {
                  OperationalMode = ART_OP_MODE_PAUSED;
                  PauseContinueBtn->SetLabel("Continue");
                  StartStopBtn->Disable();
               }
               else if(OperationalMode == ART_OP_MODE_PAUSED)
               {
                  // Display any buffered entries
                  DoLogCatchup();

                  OperationalMode = ART_OP_MODE_RUNNING;
                  PauseContinueBtn->SetLabel("Pause");
                  StartStopBtn->Enable();
               }
            }
         }
      }
      
      void DoLogCatchup(void)
      {
         if(PrimaryMode == ART_FREE_RUNNING_MODE)
         {
            for(long j = 0; j < PauseBuffer.size(); j++)
               LogCtrl->AppendText(PauseBuffer[j] + "\n"); 
         }
         else  // ART_FIFO_MODE
         {
            // Clear the control and add the latter ART_MAX_FIFO_LOG_MSG_COUNT buffer entries
            if(PauseBuffer.size() >= ART_MAX_FIFO_LOG_MSG_COUNT)
            {
               LogCtrl->Clear();
               long TheStartIdx = PauseBuffer.size() - ART_MAX_FIFO_LOG_MSG_COUNT;

               for(long j = TheStartIdx; j < PauseBuffer.size(); j++)
                  LogCtrl->AppendText(PauseBuffer[j] + "\n"); 
            }         
            else // PauseBuffer.size() < ART_MAX_FIFO_LOG_MSG_COUNT
            {
               // Fill the ctrl to max if possible
               long TheMakeupLimit = ART_MAX_FIFO_LOG_MSG_COUNT - PauseBuffer.size();
               if(PauseBuffer.size() < TheMakeupLimit)
                  TheMakeupLimit = PauseBuffer.size();

               for(long j = 0; j < TheMakeupLimit; j++)
                  LogCtrl->AppendText(PauseBuffer[j] + "\n");  
               
               // ... then enter normal FIFO mode
               long j;
               for(j = TheMakeupLimit; j < PauseBuffer.size(); j++)
               {
                  Freeze();
                  RemoveTopLine();
                  LogCtrl->AppendText(PauseBuffer[j] + "\n");  
                  Thaw();
               } 
               
               MsgCount = j;
            }
         }

         // Finally empty the buffer
         PauseBuffer.Clear();
      }

      void OnClose(wxCloseEvent &TheEvent)
      {  
         if(DataTimer)
         {
            DataTimer->Stop();           
            delete DataTimer;  
         }

         // Remove the frame's Log Target as defined with new wxLogChain((wxLog *)this); in ctor
         delete wxLog::SetActiveTarget(NULL);    
      }

#ifdef ART_NEED_TEST_DATA   
      void StartDataTimer(void)
      {
         DataTimer = new wxTimer(this, ART_FIFO_DATA_TIMER); 
         Connect(DataTimer->GetId(), wxEVT_TIMER, 
            wxTimerEventHandler(ArtFIFOLog::OnTimer), NULL, this);
         DataTimer->Start(200, wxTIMER_CONTINUOUS);
      }

      void OnTimer(wxTimerEvent &event)
      {
         wxLogError("Test log entry................................................");
      }
#endif

      wxTextCtrl     *LogCtrl;
      long           MsgCount;
      wxButton       *StartStopBtn;
      wxButton       *PauseContinueBtn;
      wxTimer        *DataTimer;
      bool           ShouldRun;
      int            PrimaryMode;
      int            OperationalMode;
      wxArrayString  PauseBuffer;
};

class MyApp : public wxApp 
{ 
   public:    
      virtual bool OnInit() 
      { 
         if(!wxApp::OnInit()) 
            return false;    

         // Redirect default error target to file
         FILE *LogFile = fopen(".\\TestLogFile.txt", "w+");
         delete wxLog::SetActiveTarget(new wxLogStderr(LogFile)); 

         // Setup log file timestamp DT format
         wxLog::SetTimestamp("%Y-%m-%d %H:%M:%S"); 
         
         // Create LogWindow (creates a wxLogChain)
         //LogWindow = new wxLogWindow((wxWindow *)0, "Event Log", true);
         FIFOLog = new ArtFIFOLog(); //ART_FREE_RUNNING_MODE);

         return true; 
      } 

      int OnExit() 
      {
         return(0);
      }

   private:
      //wxLogWindow         *LogWindow;
      ArtFIFOLog           *FIFOLog;
      wxLogChain           *LogChain;
}; 

IMPLEMENT_APP(MyApp)

wxLib 3.0.3
MSVS 2010 Ultimate RTMRel v10.0.30319.1
MSVC++ 2010
Windows 7 Ultimate x64 v6.1.7601 SP1
Post Reply