Search code examples
clinked-listpthreadsgtkgdk

Concurent memory access from a GDK Event handler and GtkDrawinaArea on_draw function


I am implementing a simple program that will allow me to place rectangles in a grid when I click on an empty placement in a grid. I have also made it scrollable, so that the size of the grid can be quite big. Here is a brief overview of what it does, and the problem I am facing.

Here are the global variables I have defined:

    //The initial height of the scroll window
    #define SCROLL_WINDOW_HEIGHT 480
    //The initial width of the scroll window
    #define SCROLL_WINDOW_WIDTH 320
    
    //The number of verticall lines
    #define NUMBER_OF_KEYS 14
    // The number of larger horizontal lines
    #define MINUTE 60
    
    //The width of the entire drawing area
    #define FULL_WIDTH 24000
    //The height of the entire drawing area
    #define FULL_HEIGHT 1400
    
    //Height of one rectangle
    #define RECT_HEIGHT FULL_HEIGHT / NUMBER_OF_KEYS
    //Width of one rectangle
    #define RECT_WIDTH FULL_WIDTH / (MINUTE * 20)
    
    //The current x possition of the cursor
    int x;
    //The current y possition of the cursor
    int y;
    //The linked list sructure storing the rectangles
    node *rect_list;

I have made a struct to represent the rectangles:

    typedef struct rectangle
    {
        int x;
        int y;
        int width;
        int height; 
        int id;
    }rectangle;

To save the rectangles that I have already created I simply add them to my linked list that has a sentinel. Here is it's struct, the build_sentinel function and the function that adds a new element to the the linked list:

    typedef struct node
    {
        struct rectangle *value;
        struct node *next;
    } node;
    rectangle * init_null_rectangle()
    {
        rectangle * new_rectangle = malloc(sizeof(rectangle));
        new_rectangle->height = 0;
        new_rectangle->width = 0;
        new_rectangle->x = 0;
        new_rectangle->y = 0;
        new_rectangle->id = -1;
        return new_rectangle;
    }
    // Builds the sentinell of the node structure
    node *node_build_sentinel()
    {
        // Creates the sentinel.
        node *head = mem_alloc(sizeof(node));
        head->value = init_null_rectangle();
        head->next = NULL;
        // Returns the head of the node which is the sentinell.
        return head;
    }
    // Frees the allocated node
    void node_free(node *head)
    {
        node *previous;
    
        while (head)
        {
            previous = head;
            head = head->next;
            free(previous->value);
            free(previous);
        }
    }
    // Inserts a value right after the head
    /*
    
        HEAD -> 1 -> 2 -> ..... -> 8
        node_insert_beg(node* HEAD, int 42);
        HEAD -> 42 -> 1 -> 2 -> ..... -> 8
    
    */
    void node_insert_beg(node *head, rectangle *value)
    {
        node *tmp = malloc(sizeof(node));
        tmp->value = value;
        tmp->next = head->next;
        head->next = tmp;
    }

Here are some other utility functions used to calculate the id of a rectangle, and the top most left corner of that rectangle

    void set_closest_rectangle()
    {
        // First check if the square has no rectangle already
        int new_x = x%(RECT_WIDTH);
        int new_y = y%(RECT_HEIGHT);
        x -= new_x;
        y -= new_y;
    }
    
    int id_from_coordinate(int x, int y)
    {
        int id = (y / (RECT_HEIGHT)) * MINUTE * 20 + x / (RECT_WIDTH);
        return id;
    }

These functions work perfectly on their own but I am providing them for detail.

Let us now talk about the way I have organized the drawing area and the event management:

Drawing Area

Here are the functions used for setting the drawing area and drawing the different parts of it, again they all work perfectly.

    //Sets up the drawing space to dynamically manage the resize
    void set_up_grid(GtkWidget *widget, cairo_t *cr,double *dx,double *dy,double *clip_y1, double *clip_y2,double *clip_x1, double *clip_x2,GdkRectangle *  da )
    {
        GdkWindow *window = gtk_widget_get_window(widget);
    
        /* Determine GtkDrawingArea dimensions */
        gdk_window_get_geometry(window, &da->x, &da->y, &da->width, &da->height);
    
        /* Draw on a black background */
        cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
        cairo_paint(cr);
    
        /* Determine the data points to calculate (ie. those in the clipping zone */
        cairo_device_to_user_distance(cr, dx, dy);
        cairo_clip_extents(cr, clip_x1, clip_y1, clip_x2, clip_y2);
    }
    
    //Draws the grid on which the rectangles are placed
    void on_draw_grid(cairo_t *cr, double dx, double clip_y2, double clip_x2)
    {
        //Horizontal lines
        for (size_t i = 0; i < FULL_WIDTH; i += (FULL_WIDTH / MINUTE))
        {
            cairo_set_source_rgb(cr, 0.2, 0.2, 0.2);
            cairo_move_to(cr, i, 0.0);
            cairo_line_to(cr, i, clip_y2);
            cairo_stroke(cr);
            cairo_set_line_width(cr, dx / 4);
            cairo_set_source_rgb(cr, 0.1, 0.1, 0.1);
            for (size_t j = 0; j < (FULL_WIDTH / MINUTE); j += (FULL_WIDTH / (MINUTE * 20)))
            {
                cairo_move_to(cr, j + i, 0.0);
                cairo_line_to(cr, j + i, clip_y2);
            }
            cairo_stroke(cr);
        }
    
        // vertical lines
        for (size_t i = 0; i < FULL_HEIGHT; i += FULL_HEIGHT / NUMBER_OF_KEYS)
        {
            cairo_set_source_rgb(cr, 0.2, 0.2, 0.2);
            cairo_move_to(cr, 0.0, i);
            cairo_line_to(cr, clip_x2, i);
            cairo_stroke(cr);
        }
    }
    
    //Draws one rectangle on the cr, with left top most corner (x,y)
    void on_draw_rectangle(cairo_t *cr,int x, int y)
    {
        cairo_move_to(cr,x,y);
        cairo_line_to(cr,x,y + RECT_HEIGHT);
        cairo_line_to(cr,x + RECT_WIDTH,y + RECT_HEIGHT);
        cairo_line_to(cr,x + RECT_WIDTH,y);
        cairo_line_to(cr,x,y);
        cairo_set_source_rgb(cr, 0.2, 0.6, 0.1);
    
        cairo_fill(cr);
    }

Here is the main drawing area function that does the final drawing: Notice that each rectangle in the linked list is supposed to be drawn in the while condition except the sentinel of course.

    static gboolean on_draw(GtkWidget *widget, cairo_t *cr, __attribute_maybe_unused__ gpointer user_data)
    {
        GdkRectangle da;            /* GtkDrawingArea size */
        gdouble dx = 4.0, dy = 4.0; /* Pixels between each point */
        gdouble clip_x1 = 0.0, clip_y1 = 0.0, clip_x2 = 0.0, clip_y2 = 0.0;
    
        set_up_grid(widget,cr,&dx,&dy,&clip_y1,&clip_y2,&clip_x1,&clip_x2, &da);
    
        on_draw_grid(cr,dx,clip_y2,clip_x2);
    
        
        while(rect_list->next)
        {
            rect_list = rect_list->next;
            on_draw_rectangle(cr,rect_list->value->x,rect_list->value->y);
        }
        gtk_widget_queue_draw_area(widget, 0, 0, da.width, da.height);
        return G_SOURCE_REMOVE;
    }

To be able to show only a part of the drawing area I have implemented a scrolling functionality. It is managed by this struct and the corresponding caller function. Once more this functionality works perfectly and I don't think it has anything to do with the problem, but I am providing it for consistency.

This struct contains the main surface and the drawn surface:

    typedef struct cairo_surfaces
    {
        cairo_surface_t *main_surface, *seen_surface;
    } cairo_surfaces;

Here is the caller function that is triggered when a configure event is emitted by the DrawingArea:

    static gboolean on_configure(GtkWidget *widget, __attribute_maybe_unused__ GdkEventConfigure *event_p, gpointer user_data)
    {
        cairo_t *cr_p;
        cairo_surfaces *my_data;
    
        my_data = (cairo_surfaces *)user_data;
    
        if (my_data->seen_surface)
        {
            cairo_surface_destroy(my_data->seen_surface);
        }
    
        my_data->seen_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, SCROLL_WINDOW_HEIGHT, SCROLL_WINDOW_WIDTH);
    
        gtk_widget_set_size_request(widget, FULL_WIDTH, FULL_HEIGHT);
    
        cr_p = cairo_create(my_data->seen_surface);
        cairo_set_source_surface(cr_p, my_data->main_surface, 0, 0);
        cairo_paint(cr_p);
        cairo_destroy(cr_p);
        return FALSE;
    }

Events

Let us now talk about the event management: So I have simply added GDK masks of the events that I wanted to get, and here is the function that will add a new rectangle when a press event is detected.

    // Gets the current event and adds a rectangle to rect_list
    static gboolean
    current_key_click(GtkWidget *da,GdkEvent *event, __attribute_maybe_unused__ gpointer user_data)
    {
        GdkDisplay *display = gdk_display_get_default();
        GdkSeat *seat = gdk_display_get_default_seat(display);
        GdkDevice *device = gdk_seat_get_pointer(seat);
    
        if (gdk_event_get_event_type(event) == GDK_BUTTON_PRESS)
        {
            
            gdk_window_get_device_position(gtk_widget_get_window(GTK_WIDGET(da)), device, &x, &y, NULL);
            set_closest_rectangle();
            
    
            //Create the rectangle if there is no rectangle at this coordinate
            rectangle * new_rect = init_null_rectangle();
    
            new_rect->id = id_from_coordinate(x,y);
            new_rect->x = x;
            new_rect->y = y;
            new_rect->width = RECT_WIDTH;
            new_rect->height = RECT_HEIGHT;
            node_insert_beg(rect_list,new_rect);
        }
        return G_SOURCE_REMOVE;
    }

However it seems that there is concurrent memory access from the drawing function that is looping on the linked list, and the current_key_click function that tries to add a new element of the linked list to the linked list. To try to resolve this problem I have added a pthread_mutex in the critical sections.

    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

That is when I insert the node

***
    pthread_mutex_lock(&mutex);
    node_insert_beg(rect_list,new_rect);
    pthread_mutex_unlock(&mutex);
***

And when I am running the while loop

    while(rect_list->next)
    {
        pthread_mutex_lock(&mutex);
        rect_list = rect_list->next;
        pthread_mutex_unlock(&mutex);
        on_draw_rectangle(cr,rect_list->value->x,rect_list->value->y);
    }

PROBLEM

However this has not resolved my problem, when I try to print the linked list, it always only contains one item, and all the others are lost. That certainly happens because the adding and the looping over the list happen at the same time. Any ideas on how to resolve this problem?

For the sake of completeness and to save you some time if you wish to help here is the whole file.

The .glade from which I am building:


    <?xml version="1.0" encoding="UTF-8"?>
    <!-- Generated with glade 3.38.2 -->
    <interface>
      <requires lib="gtk+" version="3.24"/>
      <object class="GtkWindow" id="window">
        <property name="can-focus">False</property>
        <property name="default-width">480</property>
        <property name="default-height">320</property>
        <child>
          <object class="GtkScrolledWindow" id="scrolled_window">
            <property name="visible">True</property>
            <property name="can-focus">True</property>
            <property name="shadow-type">in</property>
            <child>
              <object class="GtkViewport" id="view_port">
                <property name="visible">True</property>
                <property name="can-focus">False</property>
                <child>
                  <object class="GtkDrawingArea" id="da">
                    <property name="visible">True</property>
                    <property name="can-focus">False</property>
                  </object>
                </child>
              </object>
            </child>
          </object>
        </child>
      </object>
    </interface>

And the actual main.c

    #include <stdio.h>
    #include <stdlib.h>
    #include <math.h>
    #include <string.h>
    #include <ctype.h>
    #include <time.h>
    #include <gtk/gtk.h>
    #include <pthread.h>
    
    #define SCROLL_WINDOW_HEIGHT 480
    #define SCROLL_WINDOW_WIDTH 320
    
    #define NUMBER_OF_KEYS 14
    #define MINUTE 60
    
    #define FULL_WIDTH 24000
    #define FULL_HEIGHT 1400
    
    #define RECT_HEIGHT FULL_HEIGHT/NUMBER_OF_KEYS
    #define RECT_WIDTH FULL_WIDTH/(MINUTE * 20)
    
    
    
    
    
    /*
    
    Functions and structs for the linked lists
    
    */
    
    typedef struct rectangle
    {
        int x;
        int y;
        int width;
        int height; 
        int id;
    }rectangle;
    
    typedef struct node
    {
        struct rectangle *value;
        struct node *next;
    } node;
    
    rectangle * init_null_rectangle()
    {
        rectangle * new_rectangle = malloc(sizeof(rectangle));
        new_rectangle->height = 0;
        new_rectangle->width = 0;
        new_rectangle->x = 0;
        new_rectangle->y = 0;
        new_rectangle->id = -1;
        return new_rectangle;
    }
    
    void print_rectangle(rectangle * rect)
    {
        printf(" Id: %d --->  x: %d, y: %d, width: %d, height: %d\n",rect->id,rect->x,rect->y,rect->width,rect->height);
    }
    
    
    // Verify if node is empty
    int node_is_empty(node *head)
    {
        return head == NULL;
    }
    
    // Verify if node is not empty
    int node_is_not_empty(node *head)
    {
        return head != NULL;
    }
    
    // Builds the sentinell of the node structure
    node *node_build_sentinel()
    {
        // Creates the sentinel.
        node *head = malloc(sizeof(node));
        head->value = init_null_rectangle();
        head->next = NULL;
        // Returns the head of the node which is the sentinell.
        return head;
    }
    
    // Prints the contents of a node node* node_build_sentinel()
    void node_print(node *head)
    {
        while (head->next)
        {
            head = head->next;
            print_rectangle(head->value);
        }
    }
    
    // Frees the allocated node
    void node_free(node *head)
    {
        node *previous;
    
        while (head)
        {
            previous = head;
            head = head->next;
            free(previous->value);
            free(previous);
        }
    }
    
    // Inserts a value right after the head
    /*
    
        HEAD -> 1 -> 2 -> ..... -> 8
        node_insert_beg(node* HEAD, int 42);
        HEAD -> 42 -> 1 -> 2 -> ..... -> 8
    
    */
    void node_insert_beg(node *head, rectangle *value)
    {
        node *tmp = malloc(sizeof(node));
        tmp->value = value;
        tmp->next = head->next;
        head->next = tmp;
    }
    
    /*
    
    All Other Functions
    
    */
    
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 
    
    
    int x;
    int y;
    node * rect_list;
    
    
    typedef struct cairo_surfaces
    {
        cairo_surface_t *main_surface, *seen_surface;
    } cairo_surfaces;
    
    void set_closest_rectangle()
    {
        //First check if the square has no rectangle already
        int new_x = x%(RECT_WIDTH);
        int new_y = y%(RECT_HEIGHT);
        x -= new_x; 
        y -= new_y; 
    }
    
    int id_from_coordinate(int x,int y)
    {
        int id = (y/(RECT_HEIGHT))*MINUTE * 20+ x/(RECT_WIDTH);
        printf("id: %d\n",id);
        return id;
    }
    
    
    // Gets the current event and sets the x,y possition
    static gboolean
    current_key_click(GtkWidget *da,GdkEvent *event, __attribute_maybe_unused__ gpointer user_data)
    {
        GdkDisplay *display = gdk_display_get_default();
        GdkSeat *seat = gdk_display_get_default_seat(display);
        GdkDevice *device = gdk_seat_get_pointer(seat);
    
        if (gdk_event_get_event_type(event) == GDK_BUTTON_PRESS)
        {
            
            gdk_window_get_device_position(gtk_widget_get_window(GTK_WIDGET(da)), device, &x, &y, NULL);
            set_closest_rectangle();
    
            //Create the rectangle if there is no rectangle at this coordinate
            rectangle * new_rect = init_null_rectangle();
    
            new_rect->id = id_from_coordinate(x,y);
            new_rect->x = x;
            new_rect->y = y;
            new_rect->width = RECT_WIDTH;
            new_rect->height = RECT_HEIGHT;
    
            pthread_mutex_lock(&mutex);
            node_insert_beg(rect_list,new_rect);
            pthread_mutex_unlock(&mutex);
        }
        return G_SOURCE_REMOVE;
    }
    
    void on_quit(__attribute_maybe_unused__ GtkWidget *widget, gpointer user_data)
    {
        cairo_surfaces *my_data;
        my_data = (cairo_surfaces *)user_data;
        cairo_surface_destroy(my_data->main_surface);
        cairo_surface_destroy(my_data->seen_surface);
        gtk_main_quit();
    }
    
    static gboolean on_configure(GtkWidget *widget, __attribute_maybe_unused__ GdkEventConfigure *event_p, gpointer user_data)
    {
        cairo_t *cr_p;
        cairo_surfaces *my_data;
    
        my_data = (cairo_surfaces *)user_data;
    
        if (my_data->seen_surface)
        {
            cairo_surface_destroy(my_data->seen_surface);
        }
    
        my_data->seen_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, SCROLL_WINDOW_HEIGHT, SCROLL_WINDOW_WIDTH);
    
        gtk_widget_set_size_request(widget, FULL_WIDTH, FULL_HEIGHT);
    
        cr_p = cairo_create(my_data->seen_surface);
        cairo_set_source_surface(cr_p, my_data->main_surface, 0, 0);
        cairo_paint(cr_p);
        cairo_destroy(cr_p);
        return FALSE;
    }
    
    
    
    //Sets up the drawing space to dynamically manage the resize
    void set_up_grid(GtkWidget *widget, cairo_t *cr,double *dx,double *dy,double *clip_y1, double *clip_y2,double *clip_x1, double *clip_x2,GdkRectangle *  da )
    {
        GdkWindow *window = gtk_widget_get_window(widget);
    
        /* Determine GtkDrawingArea dimensions */
        gdk_window_get_geometry(window, &da->x, &da->y, &da->width, &da->height);
    
        /* Draw on a black background */
        cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
        cairo_paint(cr);
    
        /* Determine the data points to calculate (ie. those in the clipping zone */
        cairo_device_to_user_distance(cr, dx, dy);
        cairo_clip_extents(cr, clip_x1, clip_y1, clip_x2, clip_y2);
    }
    
    //Draws the grid on which the rectangles are placed
    void on_draw_grid(cairo_t *cr, double dx, double clip_y2, double clip_x2)
    {
        //Horizontal lines
        for (size_t i = 0; i < FULL_WIDTH; i += (FULL_WIDTH / MINUTE))
        {
            cairo_set_source_rgb(cr, 0.2, 0.2, 0.2);
            cairo_move_to(cr, i, 0.0);
            cairo_line_to(cr, i, clip_y2);
            cairo_stroke(cr);
            cairo_set_line_width(cr, dx / 4);
            cairo_set_source_rgb(cr, 0.1, 0.1, 0.1);
            for (size_t j = 0; j < (FULL_WIDTH / MINUTE); j += (FULL_WIDTH / (MINUTE * 20)))
            {
                cairo_move_to(cr, j + i, 0.0);
                cairo_line_to(cr, j + i, clip_y2);
            }
            cairo_stroke(cr);
        }
    
        // vertical lines
        for (size_t i = 0; i < FULL_HEIGHT; i += FULL_HEIGHT / NUMBER_OF_KEYS)
        {
            cairo_set_source_rgb(cr, 0.2, 0.2, 0.2);
            cairo_move_to(cr, 0.0, i);
            cairo_line_to(cr, clip_x2, i);
            cairo_stroke(cr);
        }
    }
    
    //Draws one rectangle on the cr, with left top most corner (x,y)
    void on_draw_rectangle(cairo_t *cr,int x, int y)
    {
        cairo_move_to(cr,x,y);
        cairo_line_to(cr,x,y + RECT_HEIGHT);
        cairo_line_to(cr,x + RECT_WIDTH,y + RECT_HEIGHT);
        cairo_line_to(cr,x + RECT_WIDTH,y);
        cairo_line_to(cr,x,y);
        cairo_set_source_rgb(cr, 0.2, 0.6, 0.1);
    
        cairo_fill(cr);
    }
    
    static gboolean on_draw(GtkWidget *widget, cairo_t *cr, __attribute_maybe_unused__ gpointer user_data)
    {
        GdkRectangle da;            /* GtkDrawingArea size */
        gdouble dx = 4.0, dy = 4.0; /* Pixels between each point */
        gdouble clip_x1 = 0.0, clip_y1 = 0.0, clip_x2 = 0.0, clip_y2 = 0.0;
    
        set_up_grid(widget,cr,&dx,&dy,&clip_y1,&clip_y2,&clip_x1,&clip_x2, &da);
    
        on_draw_grid(cr,dx,clip_y2,clip_x2);
    
        
        while(rect_list->next)
        {
            pthread_mutex_lock(&mutex);
            rect_list = rect_list->next;
            pthread_mutex_unlock(&mutex);
            on_draw_rectangle(cr,rect_list->value->x,rect_list->value->y);
        }
        gtk_widget_queue_draw_area(widget, 0, 0, da.width, da.height);
        return G_SOURCE_REMOVE;
    }
    
    int main()
    {
        gtk_init(NULL, NULL);
    
        cairo_surfaces my_data;
        rect_list = node_build_sentinel();
    
    
        my_data.seen_surface = NULL;
        my_data.main_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, FULL_WIDTH, FULL_HEIGHT);
    
        GtkBuilder *builder = gtk_builder_new();
        GError *error = NULL;
        if (gtk_builder_add_from_file(builder, "file.glade", &error) == 0)
        {
            g_printerr("Error loading file: %s\n", error->message);
            g_clear_error(&error);
            return 1;
        }
    
        GtkWindow *window = GTK_WINDOW(gtk_builder_get_object(builder, "window"));
        GtkScrolledWindow *scrolled_window = GTK_SCROLLED_WINDOW(gtk_builder_get_object(builder, "scrolled_window"));
        GtkDrawingArea *da = GTK_DRAWING_AREA(gtk_builder_get_object(builder, "da"));
    
        gtk_widget_set_size_request(GTK_WIDGET(scrolled_window), SCROLL_WINDOW_HEIGHT, SCROLL_WINDOW_WIDTH);
    
        gtk_widget_set_events(GTK_WIDGET(da), gtk_widget_get_events(GTK_WIDGET(da)) | GDK_SCROLL_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_MOTION_MASK );
    
        g_signal_connect(G_OBJECT(da), "configure-event", G_CALLBACK(on_configure), &my_data);
        g_signal_connect(G_OBJECT(da), "draw", G_CALLBACK(on_draw), NULL);
        g_signal_connect(G_OBJECT(da), "event", G_CALLBACK(current_key_click), NULL);
        g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(on_quit), &my_data);
    
        gtk_widget_show_all(GTK_WIDGET(window));
    
        gtk_main();
        node_print(rect_list);
        node_free(rect_list);
    
        return 0;
    }

Here is what I am compiling with:


    gcc -Wall -Wextra `pkg-config --cflags gtk+-3.0` -g -fsanitize=address   main.c -o visualiser `pkg-config --libs gtk+-3.0` -lm -pthread

Thanks in advance for any help!


Solution

  • I copied your code to my sandbox and tested out your program. After sprinkling a bunch of "printf" statements through your program, I believe I found the issue. You set up the "rect_list" node via your sentinel function and initially it holds the pointer to your first structure element. However, as rectangles are being clicked on, the value in that pointer gets set to the new node structure that has been created and its "next node" pointer is always NULL as would be the case for the latest entry. So at the end when the program attempts to print out the record list it references the last created node entry and just ends. Also, none of the allocated elements get freed so the memory leak error prints out as well.

    To get around this issue, I just added a second node structure pointer to hold the initial node pointer when the sentinel function is called (I called it "starting_point").

    node * rect_list,  *starting_point;
    

    Then, down in the main function, after the sentinel function is called, the initial node pointer is saved in this pointer copy for later use.

    rect_list = node_build_sentinel();
    starting_point = rect_list;      /* Save the initial pointer */
    

    Then in the finalization functions, the starting point pointer is used as the node pointer reference.

    node_print(starting_point);
    node_free(starting_point);
    
    return 0;
    

    This resulted in producing a list of elements along with eliminating the memory leak warning messages.

    Node list output

    I hope that helps.

    Regards.