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)