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".
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"
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.
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 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.
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 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.
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
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 EiffelStudio build number at the end of the main menu strip. From there you can select Show Memory Tool + + to show the "debug" menu, which is shown as the
- If you have a project loaded, hold and click the Project Settings tool bar button.
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 + .
- 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
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:
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
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
- In the debugger add a breakpoint on the first line of
- 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
- 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.