Importance of Proper Memory Management
Why is proper memory management so important for a web application?
The answer is simple: like any other server application, uniGUI servers are designed to run continuously. Any harmful memory operation can adversely affect server functionality and stability. These effects may remain hidden and undetected when server load is low, but will become visible as more clients request services.
Now let's discuss those harmful memory operations in more detail.
Memory Leak
A memory leak happens when a manually allocated memory is never released. Sources of memory leaks can be various. A very common mistake is to create an object dynamically without adding proper code to dispose it. The following code shows an example of a memory leak:
A := TSomeControl.Create(nil);
A.Property1 := 0;
A.Run;
A.Free;At first glance the above code may look correct, as it calls the Free() method to dispose the created control. However, what will happen if the Run() method fails and raises an exception? In that case the call to Free() will be skipped and a memory leak will occur. This is the corrected version of the same code:
A := TSomeControl.Create(nil);
try
A.Property1 := 0;
A.Run;
finally
A.Free;
end;In the previous snippet the Free() method will always be called regardless of the code in the try .. finally block.
The following code shows a wrong solution (it won't produce a memory leak, but it will cause memory corruption):
If the first statement fails to create the object A by throwing an exception, the subsequent request to free A will attempt to release an uninitialized variable. The failure to create the object does not assign nil to the variable. Attempting to release an object pointing to a random location can create memory corruption or trigger an Access Violation.
Memory Corruption
A memory corruption occurs when a memory operation overwrites another memory location already occupied by an object or variable. Memory corruptions are more harmful than memory leaks. They can remain hidden for some time, but eventually you will start observing Access Violation errors in your application and uniGUI log files.
There are many sources of memory corruption, most of them resulting from bad coding habits. Some habits may not be harmful in a desktop VCL application, but in a server application their adverse effects will become apparent.
Perhaps one primary source of memory corruption in a multithreaded server application is using global variables. If a server application needs global variables, it is necessary to protect access to them. In a standard VCL application using global variables is common, but in a multithreaded application they should be used only when strictly necessary.
While using ordinal variables such as integers will not lead to an immediate memory corruption, using dynamic global strings can cause memory corruption. Memory corruption is only one side effect of using global variables — another important issue is that globals are shared between sessions.
Consider this scenario:
You have an application with a login form and you save the user information in a record variable.
Now declare it as a global variable in a unit (the wrong way):
After a new user logs in, assign the user name to this variable:
There are two major issues with that scenario:
When a new session is created and user #1 logs in, their name is assigned to the
CurrentUservariable. If another session is created and user #2 logs in,CurrentUseris overwritten with the new user information for the current session. The first session loses the previously saved user information.This scenario can easily lead to severe memory corruptions. With even moderate traffic, concurrent writes from multiple threads can cause string corruption. In Delphi, strings are dynamic memory locations managed by the memory manager. When two concurrent threads write to the same string, pointer corruption can occur.
Correct approach in uniGUI
In uniGUI, each session comes with its set of modules and objects which are public to that session but private to others. Forms and modules are examples of such objects. Each session has its own private copy of MainForm, other forms, MainModule and DataModules (DataModules created with the uniGUI wizard).
If you want a "global" variable for a session, declare it as a field or property of one of those session-specific objects. By doing so, the variable is global or public for its parent session but private to other sessions, avoiding the memory issues described above.
Re-implement the scenario using MainModule as the container class for UserRecord. Add a new property named UserRecord to the TUniMainModule class:
Helper functions and implementation:
This ensures each session accesses its own private copy of UserRecord, avoiding memory issues from global variables.
Finally, access UserRecord from other forms and modules like this: