User Interface Memory Managment
To be a good tool citizen inside EiffelStudio tools must properly handle resource manager above that provided by the Eiffel garbage collector (GC). When working with any UI aspects in EiffelStudio objects should be "recyclable".
Contents
Recycling
Many UI classes inside EiffelStudio inherit a class EB_RECYCLABLE. It's purpose is to clean up resources and code links to other objects that may causes a memory leaks. As the old adage goes: "Prevention is always better than cure"
Using EB_RECYCLABLE
To use EB_RECYCLABLE one has to simply inherit it and implement the deferred routine internal_recycle
where you should explicitly recycle and object on the Current class instance that are no longer required, prune agents added to action sequences and generally unset entities of auxiliary objects to prevent memory leaks.
It's not recommended to detach class attributes in internal_recycle
unless necessary because of the auto-recycling capabilities of EB_RECYCLABLE. Instead redefine internal_detach_entities
to perform explicit detachment.
Note: Much of the EiffelStudio code base includes detachment of entities in internal_recycle because the auto-recycle functionality was performed by EB_RECYCLER in the 6.0 release. EB_RECYCLER is deprecated and should not be use.
Automatic Recycling
The whole process of explict recycling in internal_recycle
is fraught with potential bug scenarios. Forgetting or being unaware that a created object is in fact a recyclable object can cause a leak. This is where automatic recycling comes in.
The automatic recycling capabilities of EB_RECYCLABLE does not care if an object is actually "recyclable" as it may not be statically but could be dynamically. In addition an object may have started out non-recyclable but was changed somewhere down the line to implement EB_RECYCLABLE. In that respect objects created in a class implementing EB_RECYCLABLE and should be cleaned up when the container object is recycled should use the automatic recycling facilities.
Automatic Recycle and Destroy
EB_RECYCLABLE provides two routines for descending classes to use; auto_recycle
and delayed_auto_recycle
. auto_recycle
simply accepts an object of type ANY and if that object implements EB_RECYCLABLE, it will be recycled when the container object is. delayed_auto_recycle
works a little different in that it takes an agent. The agent is used to "fetch" the recyclable object at recycle-time. This is useful when objects are dynamically or lazily created, there is uncertainly about the attached state of an entity of an object or if a object's attribute is constantly changing.
Note: Automatic recycling does not detach the object which was automatically recycled. If an object must detached to prevent memory leaks then a container object should define internal_detach_entities
.
EB_RECYCLABLE recycling has another assisting function, it will automatically destroy EiffelVision2 objects implementing EB_RECYCLABLE. destroy
will only be called for the implementing EB_RECYCLABLE EiffelVision2 object and not any of it's attributes, unless they are automatically recycled and implement EB_RECYCLABLE also.
Automatic Agent Removal
Agents are probably the number one cause of memory leaks inside EiffelStudio as they could be contained in a live object but references a recycled one. As the agent references a recycled object the GC retains the recycled object because there is still an active reference to it.
EB_RECYCLABLE provides operations to facilitate here also. There are two routines to use when extending a sequence of actions and have the action automatically removed when the functioning object is recycled; register_action
and register_kamikaze_action
. register_action
simply takes an ACTION_SEQUENCE and an action. It does the necessary extension to the action sequence and will prune the action when the object in which register_action
was class is recycled. register_kamikaze_action
performs in the same way as register_action
does except that it extends the action sequence with the action to be called only once. Generally kamikaze actions are removed from the action sequence when the actions are called. However it is possible that the action sequence is never executed, after the kamikaze action has been added, so the action never pruned from the list of callees. This too can cause a memory leaks.
Warning: register_action
and register_kamikaze_action
come in a basic, generic use form so they can be used with any action sequence. This does removed the static compile time analysis so be careful when using an agent with an action sequence that the open arguments match. Failure to do so will result in a segmentation violation at runtime.
No Magic Here
EB_RECYCLABLE is not a special class or is anyway known by the Eiffel compiler. That means unless recycle
is called on an object implementing EB_RECYCLABLE, it wont be recycled. For the time being EB_RECYCLABLE does not implement DISPOSABLE so the GC will be of no help.
The tool foundation implementation is EiffelStudio Foundations (ES_TOOL and ES_DOCKABLE_TOOL_PANEL) implement EB_RECYCLABLE and are automatically recycled when and as needed. However, this is one of the few cases. For example, the dialog foundation implementation in EiffelStudio Foundations (ES_DIALOG) does not call recycle
automatically when close as a dialog may be reused. In that respect a dialog object should explicitly call recycle
when no longer needed or to be automatically recycled by calling auto_recycle
.
Detecting Memory Leaks
EiffelStudio has an in-built debug tool call the Memory Tool. It can be used to provide statistics on the current number of objects alive in EiffelStudio as well and show the difference between two queried sessions.
The tool is to be used on the debugged version of EiffelStudio as it is a debug tool and not a debugging tool. To be a little cleared the tool shows the live view on the running process and is not associated with the debugger.
Showing the Memory Tool
There are two ways to access the Memory Tool:
- Press CTRL+ALT+D to show the "debug" menu, which is shown as the EiffelStudio build number at the end of the main menu strip. From there you can select Show Memory Tool
- If you have a project loaded, hold CTRL and click the Project Settings tool bar button.
Catching Leaks
In order to catch memory leaks you need to play the game of spot the difference. Thankfully the tool does this for you between two selected snapshots of the process' live object map.
Here is the process of beginning to detect a memory leak. It will display a delta representing the number of objects added and not removed between two taken snapshots:
- Once the debugged EiffelStudio is shown, open the Memory Tool.
- Click the Refresh button to display an initial view of the live object map running in that version of EiffelStudio.
A filter has been applied here (more or this later), but this is what the Memory Tool should look something like:
The object count and delta will always match because it is the first snapshot taken.
- Now open a new window either using the File | New Window menu item or pressing CTRL+N.
- Once opened, show your tool or perform any interaction that instantiates classes you have integrated into EiffelStudio.
- Now close the new window and return back to the initial window with the Memory Tool.
- Click Refresh on the Memory Tool again. This will recalculate the live object map and display a delta result showing the number of new objects in memory.
Here there is a leak! The delta is 1, where as it should be 0. This means that opening and closing a new window has leaked a single instance of EB_DEVELOPMENT_WINDOW_AGENTS
.
A memory leak occurs when your tool or any of it's associated objects retains a reference to an internal part of EiffelStudio, indicated by a delta of 1 or more. When this happens there can actually be a lot of noise in the view as it in turn may reference many other objects. To reduce noise and home in on the problem area do the following:
- Toggle the Delta Only button to on so only those results with positive deltas are shown.
- Enter a regular expression in the Type filter. For example, to show all EiffelStudio UI classes you would use:
^ES_|^EB_
Locating Memory Leaks
After you have found a memory leak, it is time to drill down and find out where the leak is occurring. The Memory Tool has implicit knowledge of EB_RECYCLABLE and displays an object's recycled state. In the example image shown in the previous section EB_DEVELOPMENT_WINDOW_AGENT
has two live instances in the object map. The first has not been recycled as it is in use by the first opened window. The second instance however has been recycled, and even so still leaves two references preventing the garbage collector from reclaiming the object.
The problem lies in either or both of the TUPLE [EB_DEVELOPMENT_WINDOW_AGENTS]
referrer instances. Expanding the referrer objects reveals their referrer objects and so on.
This expanded view of the referrers indicate there is a single leak and not two. The first instance of TUPLE [EB_DEVELOPMENT_WINDOW_AGENTS]
is referenced by the instance itself and the garbage collector is able to reclaim this object. The second instance however is reference from EB_CUSTOMIZED_TOOL_MANAGER
. The leak is due to an agent on EB_DEVELOPMENT_WINDOW_AGENTS
being extended on an action sequence in EB_CUSTOMIZED_TOOL_MANAGER
.
This could be a nightmare to find but with the help of the debugger and a well placed breakpoint the agent routine can be located.
Locating an Agent
Note: In order to locate an agent leak you must be debugging EiffelStudio from a finalized version of EiffelStudio, performing the leak analysis on the debugged version of EiffelStudio.
- In the debugger add a breakpoint on the first line of
ES_MEMORY_TOOL_PANEL.on_row_expanded
. - Now in the debugged version of EiffelStudio collapse the row (if it's not already) displaying the object of a PROCEDURE, FUNCTION or PREDICATE type and then expanded it again.
The debugger will now be broken into at the place breakpoint.
- In a debugger Watch Tool add the expression
a_row.data
. - Expand the expression result and the debugger shows the name of the agent, which you can navigate to using pick and drop or the context menu.
- Examine the callers of the agent and locate the area where it's added to an action sequence, or set on an object, and be sure to prune it from the action sequence or unset it in the object's
EB_RECYCLABLE.internal_recycle
. Alternatively you can use the automatic recycling facilities of EB_RECYCLABLE to register the agent.