Friday, December 1, 2017

New implementation of clearing recursive intrusive list

There is still memory leak happens at the end of each test case. Urrhhh! Now only I got to remember this is a new implementation of loading up the file path into memory. A new thing. And the clearMemory() is still implementing the old code.
void FileBot::clearMemory()
{
    for(FileNode &T : fileList) {
        T.sibling.erase_and_dispose(T.sibling.begin(), T.sibling.end(), DisposeFileNode());
    }
 
    fileList.erase_and_dispose(fileList.begin(), fileList.end(), DisposeFileNode());
}
In the new implementation, one I can think of is a recursive function. The recursive function usage is like this: As long as there are file objects inside the sibling, dive into the sibling and look for any other file objects inside the sibling. If it doesn't contain any file object, it will return. This return will go back up one level, then only clean the sibling for the particular file object. The process will continue until it reaches to the beginning level of the path, then only remove the remaining memory from container.
void FileBot::clearMemory(FileNode *fileNode)
{
    bool beginning = false;

    if( fileNode == NULL ) {
       fileNode = &*(fileList.begin());
       beginning = true;
    }

    if( fileNode->sibling.size() > 0 ) {
        cout << "scanning current node: " << fileNode << endl;
        clearMemory(&*(fileNode->sibling.begin()));
    }
    else
        return;

    cout << "clean sibling memory: " << fileNode << " sibling size: " << fileNode->sibling.size() << endl;
    fileNode->sibling.erase_and_dispose(fileNode->sibling.begin(), fileNode->sibling.end(), DisposeFileNode());

    if( beginning == true) {
        cout << "clean root. Root size [" << fileList.size() << "]" << endl;
        fileList.erase_and_dispose(fileList.begin(), fileList.end(), DisposeFileNode());
    }
}
Notice the clearMemory(FileNode* ) is expecting an argument. I have declared this argument to NULL when it is first called. Something like this:
class FileBot
{
public:
   void clearMemory(FileNode *fileNode = NULL);

   ...
   ...
};
Thanks to the C++ great feature. When it is NULL, this indicate the beginning of the file path, after the subsequent call to the clearMemory(), it is no longer NULL anymore. And when the sibling size is zero, clearMemory() wouldn't get called. Thus, no worry about that.

In addition to that, there is a memory leak happened on following test case:
/* load 1 level parent path
 *
 */
BOOST_AUTO_TEST_CASE(TL_4)
{
    BOOST_TEST_MESSAGE("TC4 : load 1 level parent path");

#if defined(WIN32)
    BOOST_TEST_MESSAGE("Test path: D:");
    FileBot fb("D:");
#else
    BOOST_TEST_MESSAGE("Test path: /home");
    FileBot fb("/home");
#endif

    string path = "";
    if( fb.initialize() == 1 )
       path = fb.verifyFileList();

#if defined(WIN32)
    BOOST_TEST(path == "D:");
    fb.clearMemory();
#else
    BOOST_TEST(path == "/home");
#endif
}
This is due to something was not being handle probably in clearMemory(), end up the fileList wasn't clear. This test case consists of a file path with only one level, such as C:\ in Windows or /home in Linux. For Linux, it is a little bit special, /home is actually 2 levels file path. One is root path, and home is under the root. For my requirement, I just treat it as 1 level. Unlike Windows, the root is represented by a label, C:\.

I can't think of a perfect solution to resolve this defect yet. For now, I make a validation check at the beginning of the function:
void FileBot::clearMemory(FileNode *fileNode)
{
    bool beginning = false;

    // this parent have no child
    if( (&*(fileList.begin()))->sibling.size() == 0 ) {
        fileList.erase_and_dispose(fileList.begin(), fileList.end(), DisposeFileNode());
        return;
    }

    ...
    ...
}
When I see there is no sibling, clean up the mess and then bail out from the function.