Support delayed destruction in console applications too.\n\nThis only works if there is a running event loop but if there is one, we can have the same kind of problems with non-GUI objects such as sockets in console applications as we have with windows in GUI ones, so we must support this (see #10989).

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@61488 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Vadim Zeitlin
2009-07-21 14:16:44 +00:00
parent 2388960a08
commit 3185abc278
8 changed files with 203 additions and 129 deletions

View File

@@ -52,6 +52,15 @@ enum
wxPRINT_POSTSCRIPT = 2 wxPRINT_POSTSCRIPT = 2
}; };
// ----------------------------------------------------------------------------
// global variables
// ----------------------------------------------------------------------------
// use of this list is strongly deprecated, use wxApp ScheduleForDestruction()
// and IsScheduledForDestruction() methods instead of this list directly, it
// is here for compatibility purposes only
extern WXDLLIMPEXP_DATA_BASE(wxList) wxPendingDelete;
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// wxAppConsoleBase: wxApp for non-GUI applications // wxAppConsoleBase: wxApp for non-GUI applications
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@@ -317,8 +326,27 @@ public:
void DeletePendingEvents(); void DeletePendingEvents();
// wxEventLoop redirections // delayed destruction
// ------------------------ // -------------------
// If an object may have pending events for it, it shouldn't be deleted
// immediately as this would result in a crash when trying to handle these
// events: instead, it should be scheduled for destruction and really
// destroyed only after processing all pending events.
//
// Notice that this is only possible if we have a running event loop,
// otherwise the object is just deleted directly by ScheduleForDestruction()
// and IsScheduledForDestruction() always returns false.
// schedule the object for destruction in the near future
void ScheduleForDestruction(wxObject *object);
// return true if the object is scheduled for destruction
bool IsScheduledForDestruction(wxObject *object) const;
// wxEventLoop-related methods
// ---------------------------
// all these functions are forwarded to the corresponding methods of the // all these functions are forwarded to the corresponding methods of the
// currently active event loop -- and do nothing if there is none // currently active event loop -- and do nothing if there is none
@@ -340,6 +368,12 @@ public:
// events are still sent out // events are still sent out
virtual bool ProcessIdle(); virtual bool ProcessIdle();
// this virtual function is overridden in GUI wxApp to always return true
// as GUI applications always have an event loop -- but console ones may
// have it or not, so it simply returns true if already have an event loop
// running but false otherwise
virtual bool UsesEventLoop() const;
// debugging support // debugging support
// ----------------- // -----------------
@@ -401,6 +435,11 @@ public:
#endif #endif
protected: protected:
// delete all objects in wxPendingDelete list
//
// called from ProcessPendingEvents()
void DeletePendingObjects();
// the function which creates the traits object when GetTraits() needs it // the function which creates the traits object when GetTraits() needs it
// for the first time // for the first time
virtual wxAppTraits *CreateTraits(); virtual wxAppTraits *CreateTraits();
@@ -527,6 +566,9 @@ public:
// Returns true if more idle time is requested. // Returns true if more idle time is requested.
virtual bool SendIdleEvents(wxWindow* win, wxIdleEvent& event); virtual bool SendIdleEvents(wxWindow* win, wxIdleEvent& event);
// override base class version: GUI apps always use an event loop
virtual bool UsesEventLoop() const { return true; }
// top level window functions // top level window functions
// -------------------------- // --------------------------
@@ -608,9 +650,6 @@ public:
#endif // WXWIN_COMPATIBILITY_2_6 #endif // WXWIN_COMPATIBILITY_2_6
protected: protected:
// delete all objects in wxPendingDelete list
void DeletePendingObjects();
// override base class method to use GUI traits // override base class method to use GUI traits
virtual wxAppTraits *CreateTraits(); virtual wxAppTraits *CreateTraits();

View File

@@ -99,20 +99,6 @@ public:
// return true if fprintf(stderr) goes somewhere, false otherwise // return true if fprintf(stderr) goes somewhere, false otherwise
virtual bool HasStderr() = 0; virtual bool HasStderr() = 0;
// managing "pending delete" list: in GUI mode we can't immediately delete
// some objects because there may be unprocessed events for them and so we
// only do it during the next idle loop iteration while this is, of course,
// unnecessary in wxBase, so we have a few functions to abstract these
// operations
// add the object to the pending delete list in GUI, delete it immediately
// in wxBase
virtual void ScheduleForDestroy(wxObject *object) = 0;
// remove this object from the pending delete list in GUI, do nothing in
// wxBase
virtual void RemoveFromPendingDelete(wxObject *object) = 0;
#if wxUSE_SOCKETS #if wxUSE_SOCKETS
// this function is used by wxNet library to set the default socket manager // this function is used by wxNet library to set the default socket manager
// to use: doing it like this allows us to keep all socket-related code in // to use: doing it like this allows us to keep all socket-related code in
@@ -233,9 +219,6 @@ public:
virtual bool ShowAssertDialog(const wxString& msg); virtual bool ShowAssertDialog(const wxString& msg);
virtual bool HasStderr(); virtual bool HasStderr();
virtual void ScheduleForDestroy(wxObject *object);
virtual void RemoveFromPendingDelete(wxObject *object);
// the GetToolkitVersion for console application is always the same // the GetToolkitVersion for console application is always the same
virtual wxPortId GetToolkitVersion(int *verMaj = NULL, int *verMin = NULL) const virtual wxPortId GetToolkitVersion(int *verMaj = NULL, int *verMin = NULL) const
{ {
@@ -272,9 +255,6 @@ public:
virtual bool ShowAssertDialog(const wxString& msg); virtual bool ShowAssertDialog(const wxString& msg);
virtual bool HasStderr(); virtual bool HasStderr();
virtual void ScheduleForDestroy(wxObject *object);
virtual void RemoveFromPendingDelete(wxObject *object);
virtual bool IsUsingUniversalWidgets() const virtual bool IsUsingUniversalWidgets() const
{ {
#ifdef __WXUNIVERSAL__ #ifdef __WXUNIVERSAL__

View File

@@ -148,7 +148,9 @@ WX_DECLARE_LIST_3(wxWindow, wxWindowBase, wxWindowList, wxWindowListNode, class
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
extern WXDLLIMPEXP_DATA_CORE(wxWindowList) wxTopLevelWindows; extern WXDLLIMPEXP_DATA_CORE(wxWindowList) wxTopLevelWindows;
extern WXDLLIMPEXP_DATA_CORE(wxList) wxPendingDelete;
// declared here for compatibility only, main declaration is in wx/app.h
extern WXDLLIMPEXP_DATA_BASE(wxList) wxPendingDelete;
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// wxWindowBase is the base class for all GUI controls/widgets, this is the public // wxWindowBase is the base class for all GUI controls/widgets, this is the public

View File

@@ -111,6 +111,20 @@ public:
wxEventFunction func, wxEventFunction func,
wxEvent& event) const; wxEvent& event) const;
/**
Returns @true if the application is using an event loop.
This function always returns @true for the GUI applications which
must use an event loop but by default only returns @true for the
console programs if an event loop is already running as it can't know
whether one will be created in the future.
Thus, it only makes sense to override it in console applications which
do use an event loop, to return @true instead of checking if there is a
currently active event loop.
*/
virtual bool UsesEventLoop() const;
//@} //@}
@@ -129,19 +143,19 @@ public:
This happens during each event loop iteration (see wxEventLoopBase) in GUI mode but This happens during each event loop iteration (see wxEventLoopBase) in GUI mode but
it may be also called directly. it may be also called directly.
Note that this function does not only process the pending events for the wxApp object Note that this function does not only process the pending events for the wxApp object
itself (which derives from wxEvtHandler) but also the pending events for @e any itself (which derives from wxEvtHandler) but also the pending events for @e any
event handler of this application. event handler of this application.
This function will immediately return and do nothing if SuspendProcessingOfPendingEvents() This function will immediately return and do nothing if SuspendProcessingOfPendingEvents()
was called. was called.
*/ */
virtual void ProcessPendingEvents(); virtual void ProcessPendingEvents();
/** /**
Deletes the pending events of all wxEvtHandlers of this application. Deletes the pending events of all wxEvtHandlers of this application.
See wxEvtHandler::DeletePendingEvents() for warnings about deleting the pending See wxEvtHandler::DeletePendingEvents() for warnings about deleting the pending
events. events.
*/ */
@@ -149,9 +163,9 @@ public:
/** /**
Returns @true if there are pending events on the internal pending event list. Returns @true if there are pending events on the internal pending event list.
Whenever wxEvtHandler::QueueEvent or wxEvtHandler::AddPendingEvent() are Whenever wxEvtHandler::QueueEvent or wxEvtHandler::AddPendingEvent() are
called (not only for wxApp itself, but for any event handler of the application!), called (not only for wxApp itself, but for any event handler of the application!),
the internal wxApp's list of handlers with pending events is updated and this the internal wxApp's list of handlers with pending events is updated and this
function will return true. function will return true.
*/ */
@@ -172,6 +186,40 @@ public:
//@} //@}
/**
Delayed objects destruction.
In applications using events it may be unsafe for an event handler to
delete the object which generated the event because more events may be
still pending for the same object. In this case the handler may call
ScheduleForDestruction() instead.
*/
//@{
/**
Schedule the object for destruction in the near future.
Notice that if the application is not using an event loop, i.e. if
UsesEventLoop() returns @false, this method will simply delete the
object immediately.
Examples of using this function inside wxWidgets itself include
deleting the top level windows when they are closed and sockets when
they are disconnected.
*/
void ScheduleForDestruction(wxObject *object);
/**
Check if the object had been scheduled for destruction with
ScheduleForDestruction().
This function may be useful as an optimization to avoid doing something
with an object which will be soon destroyed in any case.
*/
bool IsScheduledForDestruction(wxObject *object) const;
//@}
/** /**
Allows external code to modify global ::wxTheApp, but you should really Allows external code to modify global ::wxTheApp, but you should really

View File

@@ -115,6 +115,8 @@ wxAppInitializerFunction wxAppConsoleBase::ms_appInitFn = NULL;
wxSocketManager *wxAppTraitsBase::ms_manager = NULL; wxSocketManager *wxAppTraitsBase::ms_manager = NULL;
WXDLLIMPEXP_DATA_BASE(wxList) wxPendingDelete;
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// wxEventLoopPtr // wxEventLoopPtr
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@@ -341,6 +343,13 @@ bool wxAppConsoleBase::ProcessIdle()
return event.MoreRequested(); return event.MoreRequested();
} }
bool wxAppConsoleBase::UsesEventLoop() const
{
// in console applications we don't know whether we're going to have an
// event loop so assume we won't -- unless we already have one running
return wxEventLoopBase::GetActive() != NULL;
}
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// events // events
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@@ -433,43 +442,46 @@ void wxAppConsoleBase::ResumeProcessingOfPendingEvents()
void wxAppConsoleBase::ProcessPendingEvents() void wxAppConsoleBase::ProcessPendingEvents()
{ {
if (!m_bDoPendingEventProcessing) if ( m_bDoPendingEventProcessing )
return;
wxENTER_CRIT_SECT(m_handlersWithPendingEventsLocker);
wxCHECK_RET( m_handlersWithPendingDelayedEvents.IsEmpty(),
"this helper list should be empty" );
// iterate until the list becomes empty: the handlers remove themselves
// from it when they don't have any more pending events
while (!m_handlersWithPendingEvents.IsEmpty())
{ {
// In ProcessPendingEvents(), new handlers might be added
// and we can safely leave the critical section here.
wxLEAVE_CRIT_SECT(m_handlersWithPendingEventsLocker);
// NOTE: we always call ProcessPendingEvents() on the first event handler
// with pending events because handlers auto-remove themselves
// from this list (see RemovePendingEventHandler) if they have no
// more pending events.
m_handlersWithPendingEvents[0]->ProcessPendingEvents();
wxENTER_CRIT_SECT(m_handlersWithPendingEventsLocker); wxENTER_CRIT_SECT(m_handlersWithPendingEventsLocker);
wxCHECK_RET( m_handlersWithPendingDelayedEvents.IsEmpty(),
"this helper list should be empty" );
// iterate until the list becomes empty: the handlers remove themselves
// from it when they don't have any more pending events
while (!m_handlersWithPendingEvents.IsEmpty())
{
// In ProcessPendingEvents(), new handlers might be added
// and we can safely leave the critical section here.
wxLEAVE_CRIT_SECT(m_handlersWithPendingEventsLocker);
// NOTE: we always call ProcessPendingEvents() on the first event handler
// with pending events because handlers auto-remove themselves
// from this list (see RemovePendingEventHandler) if they have no
// more pending events.
m_handlersWithPendingEvents[0]->ProcessPendingEvents();
wxENTER_CRIT_SECT(m_handlersWithPendingEventsLocker);
}
// now the wxHandlersWithPendingEvents is surely empty; however some event
// handlers may have moved themselves into wxHandlersWithPendingDelayedEvents
// because of a selective wxYield call in progress.
// Now we need to move them back to wxHandlersWithPendingEvents so the next
// call to this function has the chance of processing them:
if (!m_handlersWithPendingDelayedEvents.IsEmpty())
{
WX_APPEND_ARRAY(m_handlersWithPendingEvents, m_handlersWithPendingDelayedEvents);
m_handlersWithPendingDelayedEvents.Clear();
}
wxLEAVE_CRIT_SECT(m_handlersWithPendingEventsLocker);
} }
// now the wxHandlersWithPendingEvents is surely empty; however some event // Garbage collect all objects previously scheduled for destruction.
// handlers may have moved themselves into wxHandlersWithPendingDelayedEvents DeletePendingObjects();
// because of a selective wxYield call in progress.
// Now we need to move them back to wxHandlersWithPendingEvents so the next
// call to this function has the chance of processing them:
if (!m_handlersWithPendingDelayedEvents.IsEmpty())
{
WX_APPEND_ARRAY(m_handlersWithPendingEvents, m_handlersWithPendingDelayedEvents);
m_handlersWithPendingDelayedEvents.Clear();
}
wxLEAVE_CRIT_SECT(m_handlersWithPendingEventsLocker);
} }
void wxAppConsoleBase::DeletePendingEvents() void wxAppConsoleBase::DeletePendingEvents()
@@ -487,6 +499,50 @@ void wxAppConsoleBase::DeletePendingEvents()
wxLEAVE_CRIT_SECT(m_handlersWithPendingEventsLocker); wxLEAVE_CRIT_SECT(m_handlersWithPendingEventsLocker);
} }
// ----------------------------------------------------------------------------
// delayed objects destruction
// ----------------------------------------------------------------------------
bool wxAppConsoleBase::IsScheduledForDestruction(wxObject *object) const
{
return wxPendingDelete.Member(object);
}
void wxAppConsoleBase::ScheduleForDestruction(wxObject *object)
{
if ( !UsesEventLoop() )
{
// we won't be able to delete it later so do it right now
delete object;
return;
}
//else: we either already have or will soon start an event loop
if ( !wxPendingDelete.Member(object) )
wxPendingDelete.Append(object);
}
void wxAppConsoleBase::DeletePendingObjects()
{
wxList::compatibility_iterator node = wxPendingDelete.GetFirst();
while (node)
{
wxObject *obj = node->GetData();
// remove it from the list first so that if we get back here somehow
// during the object deletion (e.g. wxYield called from its dtor) we
// wouldn't try to delete it the second time
if ( wxPendingDelete.Member(obj) )
wxPendingDelete.Erase(node);
delete obj;
// Deleting one object may have deleted other pending
// objects, so start from beginning of list again.
node = wxPendingDelete.GetFirst();
}
}
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// exception handling // exception handling
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@@ -738,16 +794,6 @@ bool wxConsoleAppTraitsBase::HasStderr()
return true; return true;
} }
void wxConsoleAppTraitsBase::ScheduleForDestroy(wxObject *object)
{
delete object;
}
void wxConsoleAppTraitsBase::RemoveFromPendingDelete(wxObject * WXUNUSED(object))
{
// nothing to do
}
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// wxAppTraits // wxAppTraits
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------

View File

@@ -50,8 +50,6 @@
#include "wx/build.h" #include "wx/build.h"
WX_CHECK_BUILD_OPTIONS("wxCore") WX_CHECK_BUILD_OPTIONS("wxCore")
WXDLLIMPEXP_DATA_CORE(wxList) wxPendingDelete;
// ============================================================================ // ============================================================================
// wxAppBase implementation // wxAppBase implementation
// ============================================================================ // ============================================================================
@@ -341,33 +339,11 @@ bool wxAppBase::SafeYieldFor(wxWindow *win, long eventsToProcess)
// idle handling // idle handling
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
void wxAppBase::DeletePendingObjects()
{
wxList::compatibility_iterator node = wxPendingDelete.GetFirst();
while (node)
{
wxObject *obj = node->GetData();
// remove it from the list first so that if we get back here somehow
// during the object deletion (e.g. wxYield called from its dtor) we
// wouldn't try to delete it the second time
if ( wxPendingDelete.Member(obj) )
wxPendingDelete.Erase(node);
delete obj;
// Deleting one object may have deleted other pending
// objects, so start from beginning of list again.
node = wxPendingDelete.GetFirst();
}
}
// Returns true if more time is needed. // Returns true if more time is needed.
bool wxAppBase::ProcessIdle() bool wxAppBase::ProcessIdle()
{ {
// call the base class version first, it will process the pending events // call the base class version first to send the idle event to wxTheApp
// (which should be done before the idle events generation) and send the // itself
// idle event to wxTheApp itself
bool needMore = wxAppConsoleBase::ProcessIdle(); bool needMore = wxAppConsoleBase::ProcessIdle();
wxIdleEvent event; wxIdleEvent event;
wxWindowList::compatibility_iterator node = wxTopLevelWindows.GetFirst(); wxWindowList::compatibility_iterator node = wxTopLevelWindows.GetFirst();
@@ -379,9 +355,6 @@ bool wxAppBase::ProcessIdle()
node = node->GetNext(); node = node->GetNext();
} }
// 'Garbage' collection of windows deleted with Close().
DeletePendingObjects();
#if wxUSE_LOG #if wxUSE_LOG
// flush the logged messages if any // flush the logged messages if any
wxLog::FlushActive(); wxLog::FlushActive();
@@ -544,14 +517,3 @@ bool wxGUIAppTraitsBase::HasStderr()
#endif #endif
} }
void wxGUIAppTraitsBase::ScheduleForDestroy(wxObject *object)
{
if ( !wxPendingDelete.Member(object) )
wxPendingDelete.Append(object);
}
void wxGUIAppTraitsBase::RemoveFromPendingDelete(wxObject *object)
{
wxPendingDelete.DeleteObject(object);
}

View File

@@ -695,6 +695,11 @@ void wxTCPEventHandler::HandleDisconnect(wxTCPConnection *connection)
connection->m_sock->Notify(false); connection->m_sock->Notify(false);
connection->m_sock->Close(); connection->m_sock->Close();
// don't leave references to this soon-to-be-dangling connection in the
// socket as it won't be destroyed immediately as its destruction will be
// delayed in case there are more events pending for it
connection->m_sock->SetClientData(NULL);
connection->SetConnected(false); connection->SetConnected(false);
connection->OnDisconnect(); connection->OnDisconnect();
} }

View File

@@ -841,12 +841,6 @@ wxSocketBase::wxSocketBase(wxSocketFlags flags, wxSocketType type)
wxSocketBase::~wxSocketBase() wxSocketBase::~wxSocketBase()
{ {
// Just in case the app called Destroy() *and* then deleted the socket
// immediately: don't leave dangling pointers.
wxAppTraits *traits = wxTheApp ? wxTheApp->GetTraits() : NULL;
if ( traits )
traits->RemoveFromPendingDelete(this);
// Shutdown and close the socket // Shutdown and close the socket
if (!m_beingDeleted) if (!m_beingDeleted)
Close(); Close();
@@ -855,8 +849,7 @@ wxSocketBase::~wxSocketBase()
delete m_impl; delete m_impl;
// Free the pushback buffer // Free the pushback buffer
if (m_unread) free(m_unread);
free(m_unread);
} }
bool wxSocketBase::Destroy() bool wxSocketBase::Destroy()
@@ -872,14 +865,13 @@ bool wxSocketBase::Destroy()
// Suppress events from now on // Suppress events from now on
Notify(false); Notify(false);
// schedule this object for deletion // Schedule this object for deletion instead of destroying it right now if
wxAppTraits *traits = wxTheApp ? wxTheApp->GetTraits() : NULL; // possible as we may have other events pending for it
if ( traits ) if ( wxTheApp )
{ {
// let the traits object decide what to do with us wxTheApp->ScheduleForDestruction(this);
traits->ScheduleForDestroy(this);
} }
else // no app or no traits else // no app
{ {
// in wxBase we might have no app object at all, don't leak memory // in wxBase we might have no app object at all, don't leak memory
delete this; delete this;