[comp.windows.x] XtDestroyWidget bug?/problem/question

dougl@madison.ivy.isc.COM (Douglas J Leavitt) (03/23/91)

We at Interactive are trying to nail down and solve an outstanding
Xt intrinsics complaint/bug/mis-use/mis-interpretation that has
surfaced a few times.  In order to track down the problem I have
read the relevant portions from a number of important docs including
the X11R4 Xt docs, the X WINDOW SYSTEM TOOLKIT (Asente & Swick),
and O'Reilly Volume 4 & 5, checked the provided test program out on
our X11R3 release, our upcoming X11r4 release, had it tested on a
Sun X11r4 with patches up to 18, and on our X11r4 using the latest
/x/lib/Xt/Destroy.c from expo (dated: Destroy.c,v 1.42 91/02/13).

The problem routine in question is XtDestroyWidget.  The test case
that I've appended to the end of this program (from a customer)
generates and then destroys a widget tree in a forever loop.

In all the cases that I've tried, the malloc arena of this test program
grows without bounds, and unless a XtUnrealizeWidget is placed before
the XtDestroyWidget, all X server's seem to grow as well.

The basic problem is the interpretation of XtDestroyWidget.  What
it seems our customer's expect is that when XtDestroyWidget is called
the widget, and all it's children X windows, and associated client
side widget instance memory are supposed to be freed.  However,
the XtDestroyWidget documentation does not explicitly state that this
is what happens.

Can someone (hopefully one of the original developer's) clarify what
is being done incorrectly (so we can report back through support on
how to do it correctly), or tell me if this is a known bug with a
workaround, or if it's something that hasn't been detected before etc..??

My current interpretation of the MIT docs and Ascente/Swick would say
that the code may be doing what the documents state, and the code is
not freeing all X server window's resources, and associated memory,
although this is not what I think people generally expect.

Here's my reasoning:
Create a Label widget, and call it 'w'.  Have it's parent
be say a Form widget. Perform:

	XtDestroyWidget(w);

From ascent/swick (and walk through of the code),

1) Walk through the widget tree (label) and mark all the
   being destroyed fields as true.

2) Recursively descend tree, which does nothing.

3) Add destroy entry for label to destroy list.  Since label
   doesn't normally have one nothing happens.

4) Phase 2, execute all destroy callback routines previously
   collected.  Since label widget didn't provide any execute
   nothing.

5) Widget is not a popup, but parent is a subclass of constraint (Form)
   so call appropriate parent destroy procedures (there are none)
   so nothing happens.


6) Basically at this point window's should be XDestroyWindow'd, and
   possibly memory should be freed (so I would say), but that doesn't
   seem to happen.

   The XtPhase2Destroy of Destroy.c has a call to XDestroyWindow, but
   it never seems to get called in this case.  Hence the growing server
   window list.  In addition, I've been unable to find any code in this
   sequence that explicitly XtFree's the widget structures etc. leading
   me to believe that the widget pointer's/ids are just being dropped
   and hence the growing malloc arena problem.

At this point XtDestroyWidget has completed, but nothing was freed,
and the customer gets angry and calls us because he does it lots of
times and starts swapping etc...

So, any idea's what is going wrong?  Is there something I've missed?
Is there something that needs to get tracked fixed?  Does anyone want
more info?  Is XtDestroyWidget being used incorrectly?

Any and all help would be greatly appreciated.

Thanks in advance to any comments,
Douglas Leavitt
dougl@ism.isc.com

Below is a test program that shows the problem on every system I've
tried so far...

Compile as:
cc -o xswap xswap.c -lXaw -lXmu -lXt-lX11 -lXext.a
(may need a -Isomething to get correct header file locations for x11r4)

Run as:
$ xswap
Press CreateDestroyInfinite button to get infinite loop create destroy
to execute.  this program does a:
	system("/etc/swap -l");
	system("ps -el | grep xswap");
	system("ps -el | grep X");
every 5 iterations to display current server/program sizes and swap usage...

The #ifdef MULTI does lots of labels in a box widget, with MUTLI undef's
it does 1 label widget.

The #ifdef UNREALIZE performs a XtUnrealizeWidget befoer the XtDestroyWidget
for less serious server growth...

========= cut here for xswap.c
/***********/
/* headers */
/***********/

#include <stdio.h>

#include <X11/Intrinsic.h>      /* common XToolkit definitions */
#include <X11/StringDefs.h>     /* common widget definitions */
#include <X11/Shell.h>
#include <X11/Box.h>
#include <X11/Form.h>
#include <X11/Command.h>

#define MULTI
/* #define UNREALIZE */
#define WIDGET_TOTAL 250

void DoCancelI();
void DoCreateDestroy();
void DoCreateDestroyI();
void DoMapUnmap();
void DoExit();
void UpdateScreenNow();

Boolean bBox2Mapped = False;
Boolean bCancelI;
int createdestroyCount;
Widget wScreen, wBox1;
Widget wBox2 = 0;

/*****************************************************************************/


/**************************/
/* Toolkit initialization */
/**************************/

void main(argc, argv)
unsigned int argc;
char **argv;
{
	Arg arg[25];
	Cardinal n;
	Widget toplevel, w;

	toplevel = XtInitialize("main", "XSwap", NULL, 0, &argc, argv);

	n = 0;
	XtSetArg(arg[n], XtNheight, 350);
	n++;
	XtSetArg(arg[n], XtNwidth, 640);
	n++;
	wScreen = XtCreateManagedWidget("wScreen", formWidgetClass, toplevel, arg, n);

	n = 0;
	XtSetArg(arg[n], XtNvertDistance, 300);
	n++;
	XtSetArg(arg[n], XtNhorizDistance, 600);
	n++;
	XtCreateManagedWidget("marker", labelWidgetClass, wScreen, arg, n);

	wBox1 = XtCreateManagedWidget("box1", boxWidgetClass, wScreen, 0, 0);
	w = XtCreateManagedWidget("CreateDestroyInfinite", commandWidgetClass, wBox1, 0, 0);
	XtAddCallback(w, XtNcallback, DoCreateDestroyI, 0);
	w = XtCreateManagedWidget("CreateDestroy", commandWidgetClass, wBox1, 0, 0);
	XtAddCallback(w, XtNcallback, DoCreateDestroy, 0);
	w = XtCreateManagedWidget("CancelI", commandWidgetClass, wBox1, 0, 0);
	XtAddCallback(w, XtNcallback, DoCancelI, 0);
	w = XtCreateManagedWidget("MapUnmap", commandWidgetClass, wBox1, 0, 0);
	XtAddCallback(w, XtNcallback, DoMapUnmap, 0);
	w = XtCreateManagedWidget("Exit", commandWidgetClass, wBox1, 0, 0);
	XtAddCallback(w, XtNcallback, DoExit, 0);

	XtRealizeWidget(toplevel);

	XtMainLoop();
}

void DoCancelI(w, client_data, call_data)
Widget w;
caddr_t client_data;
caddr_t call_data;
{
	bCancelI = True;
	fprintf(stderr, "CreateDestroy count = %d\n", createdestroyCount);
	system("/etc/swap -l");
	system("ps -el | grep xswap");
	system("ps -el | grep X");
}

void DoCreateDestroyI(w, client_data, call_data)
Widget w;
caddr_t client_data;
caddr_t call_data;
{
	bCancelI = False;
	createdestroyCount = 0;

	while (!bCancelI) {
		if (!(createdestroyCount % 10)) {
			fprintf(stderr, "CreateDestroy count = %d\n", createdestroyCount);
			system("/etc/swap -l");
			system("ps -el | grep xswap");
			system("ps -el | grep X");
		}
		DoCreateDestroy(w, client_data, call_data);
		createdestroyCount++;
		UpdateScreenNow(w);
	}
}

void DoCreateDestroy(w, client_data, call_data)
Widget w;
caddr_t client_data;
caddr_t call_data;
{
	Arg arg[25];
	Cardinal n;
	int i;
	Widget ww;

	if (wBox2) {
		printf("DESTROY...\n");
#ifdef UNREALIZE
		XtUnrealizeWidget(wBox2);
#endif
		XtDestroyWidget(wBox2);
		wBox2 = 0;
		bBox2Mapped = False;
	} else {
		printf("CREATE...");
#ifdef MULTI
		n = 0;
		XtSetArg(arg[n], XtNheight, 200);
		n++;
		XtSetArg(arg[n], XtNwidth, 200);
		n++;
		XtSetArg(arg[n], XtNfromHoriz, wBox1);
		n++;
		wBox2 = XtCreateManagedWidget("box2", boxWidgetClass, wScreen, arg, n);
		bBox2Mapped = True;

		for (i = 0; i < WIDGET_TOTAL; i++)
			XtCreateManagedWidget("labellabel", labelWidgetClass, wBox2, 0, 0);
#else
		wBox2 = XtCreateManagedWidget("labellabel", labelWidgetClass, wScreen, 0, 0);
		bBox2Mapped = True;
#endif
	}
}

void DoMapUnmap(w, client_data, call_data)
Widget w;
caddr_t client_data;
caddr_t call_data;
{
	if (wBox2) {
		if (bBox2Mapped) {
			XtUnmapWidget(wBox2);
			bBox2Mapped = False;
		} else {
			XtMapWidget(wBox2);
			bBox2Mapped = True;
		}
	}
}


void DoExit(w, client_data, call_data)
Widget w;
caddr_t client_data;
caddr_t call_data;
{
	exit(0);
}

void UpdateScreenNow(w)
Widget w;
{
	XSync(XtDisplay(w), 0);
	while (XtPending()) {
		XEvent event;

		XtNextEvent(&event);
		XtDispatchEvent(&event);
	}
}

converse@expo.lcs.mit.EDU (03/23/91)

Phase 2 of the destruction of a widget, which includes destroying
the widget's window and freeing all memory associated with the widget,
does not occur until the current invocation of XtDispatchEvent is
about to return.  When you write a loop to create and destroy 
widgets within a callback, phase 2 of destruction of all those
widgets is not executed until the callback completes.

swick@athena.mit.EDU (Ralph Swick) (04/03/91)

    The problem routine in question is XtDestroyWidget.  The test case
    that I've appended to the end of this program (from a customer)
    generates and then destroys a widget tree in a forever loop.

You need to re-read section 2.8 of the Xt spec (page 690 of the
Digital Press book).  The widget destruction and the freeing of
associated resources does not take place until the very final
stage of XtDispatchEvent.  The reason for this is that the
callback in which XtDestroyWidget was invoked may not be the
last one in the callback list and remaining callbacks need to
be permitted to reference the widget whose destruction is pending.
Among other things, this permits widgets to destroy themselves
from within callbacks.

This means that if an event dispatch invokes a callback procedure
that never returns (as in your example), phase 2 of the destruction
never occurs for widgets destroyed in this dispatch (stack) frame.

Modifying your example as follows will make it work as you expect:

*** original ****
--- fixed ----
***************
*** 130,138 ****
  #include <X11/Intrinsic.h>      /* common XToolkit definitions */
  #include <X11/StringDefs.h>     /* common widget definitions */
  #include <X11/Shell.h>
! #include <X11/Box.h>
! #include <X11/Form.h>
! #include <X11/Command.h>
  
  #define MULTI
  /* #define UNREALIZE */
--- 132,140 ----
  #include <X11/Intrinsic.h>      /* common XToolkit definitions */
  #include <X11/StringDefs.h>     /* common widget definitions */
  #include <X11/Shell.h>
! #include <X11/Xaw/Box.h>
! #include <X11/Xaw/Form.h>
! #include <X11/Xaw/Command.h>
  
  #define MULTI
  /* #define UNREALIZE */
***************
*** 211,225 ****
  	system("ps -el | grep X");
  }
  
! void DoCreateDestroyI(w, client_data, call_data)
! Widget w;
! caddr_t client_data;
! caddr_t call_data;
  {
! 	bCancelI = False;
! 	createdestroyCount = 0;
! 
! 	while (!bCancelI) {
  		if (!(createdestroyCount % 10)) {
  			fprintf(stderr, "CreateDestroy count = %d\n", createdestroyCount);
  			system("/etc/swap -l");
--- 213,224 ----
  	system("ps -el | grep X");
  }
  
! void TimerCreateDestroy(client_data, id)
!     XtPointer client_data;
!     XtIntervalId id;
  {
! 	Widget w = (Widget)client_data;
! 	if (!bCancelI) {
  		if (!(createdestroyCount % 10)) {
  			fprintf(stderr, "CreateDestroy count = %d\n", createdestroyCount);
  			system("/etc/swap -l");
***************
*** 226,235 ****
  			system("ps -el | grep xswap");
  			system("ps -el | grep X");
  		}
! 		DoCreateDestroy(w, client_data, call_data);
  		createdestroyCount++;
  		UpdateScreenNow(w);
  	}
  }
  
  void DoCreateDestroy(w, client_data, call_data)
--- 225,248 ----
  			system("ps -el | grep xswap");
  			system("ps -el | grep X");
  		}
! 		DoCreateDestroy((Widget)client_data, NULL, NULL);
  		createdestroyCount++;
  		UpdateScreenNow(w);
+ 		XtAppAddTimeOut(XtWidgetToApplicationContext(w), 1,
+ 				TimerCreateDestroy, client_data);
  	}
+ }
+ 
+ void DoCreateDestroyI(w, client_data, call_data)
+ Widget w;
+ caddr_t client_data;
+ caddr_t call_data;
+ {
+ 	bCancelI = False;
+ 	createdestroyCount = 0;
+ 
+ 	XtAppAddTimeOut(XtWidgetToApplicationContext(w), 1,
+ 			TimerCreateDestroy, (XtPointer)w);
  }
  
  void DoCreateDestroy(w, client_data, call_data)