0

I am writing a program that processes batches of updates to a graph in the for of new nodes and edges. I recently incorporated a sliding window scheme that checks to see if the edges already in the graph are in the window, and if not deletes them. I am using a Edge and Node class as follows:

class Edge
{
public:
 uint64_t source;
 uint64_t target;
 unsigned type;
 std::string label;
 uint64_t timestamp;
 bool directed;
 bool extracted;
 Edge(){}
 Edge(Edge *e);
 Edge(uint64_t, uint64_t, unsigned, std::string, time_t, bool);
 bool operator ==(const Edge *other)
  {
  return((this->source==other->source)&&(this->target==other->target)&& \
          (this->type==other->type));
  }
};   

class Node
{
  public:
  uint64_t id;
  unsigned type;
  std::string label;
  uint64_t timestamp;
  std::vector<Edge *> adjacent_edges;
  Node(){}
  Node(Node *);
  bool push_edge(Edge *e)
  {
    try
    {
     adjacent_edges.push_back(e);
    }
    catch(std::bad_alloc&)
    {
     std::cout<<"Error pushing edge"<<std::endl;
     return false;
    }
    return true;    
   }

   std::vector<Edge *>::iterator pop_edge(std::vector<Edge *>::iterator e_it)
   {
    return adjacent_edges.erase(e_it);
   }

  bool operator ==(const Node *other)
   {
   return (this->id == other->id);
   }
};

On using a one dataset I get a segfault after processing 69 batch files with a sliding window size of 5 while trying to access an edge using an edge iterator. On using another dataset, I get a segfault after 69 batch files while trying to delete a non empty Edge pointer in the adjacency list (trying to free the memory). I am at my wit's end trying to figure out what is going wrong. The non-sliding window version of this program works just fine. Also I know that using a STL deque data structure would be better for sliding windows. However, I am working with pretty large code, and I would like to be able to solve this without having to use a deque. Thanks in advance. Edit: It happens on two different lines:

  for (int i = 0; i < node_list.size(); i++)
  {
  vector<Edge *>::iterator adj_it;

  for (adj_it = (node_list[i])->adjacent_edges.begin(); adj_it != (node_list[i])->adjacent_edges.end(); ++adj_it )
  {


      if ((max_batch_num - (*adj_it)->timestamp) > time_window)
      {


          deleteEdge(adj_it);
          num_edges_deleted++;
          --adj_it;

      }

  }

}

It happens on the line:

if ((max_batch_num - (*adj_it)->timestamp) > time_window)

on using the first dataset. The problem here is that even though the vector is non-empty, the pointers in the vector are pointing to memory that is not part of the application. When I use gdb to attempt to print out:

print (*adj_it)->timestamp

it gives : Attempt to take address of value not in memory

This shouldn't happen though as edges are being added to the adjacency list. And on using the second dataset the error happens when I use:

delete (*adj_it); 

where adj_it is an iterator for the adjacency_list vector.

What is also weird is that if I increase the sliding window by say 'n', the same problem happens after 'n' batches.

Adding the deleteEdge function:

vector<FSM::Edge *>::iterator FSM::Graph::deleteEdge(vector<Edge *>::iterator e_it)
{
 //cout<<"Deleting edge: e "<<e->source<<" -> "<<e->target<<endl;//DEBUG
 FSM::Node *s = getNode((*e_it)->source);
 FSM::Edge *e_tmp = (*e_it);
 e_it = s->pop_edge(e_it);
 if (e_tmp != NULL)
 {
     delete e_tmp;
 }
 else
 {
     std::cerr<<"Trying to delete an Edge pointer which is NULL"<<endl;
     exit(1);
 }
 return e_it;

}

Also I previously used only indexes, and I tried it again after @Julius' answer. This is my new deletion loop.

for (int j = 0; j<(node_list[i])->adjacent_edges.size();j++)
   {
       if ((max_batch_num - ((node_list[i])->adjacent_edges[j])->timestamp) > time_window)
               {                     
                   (node_list[i])->adjacent_edges.erase((node_list[i])->adjacent_edges.begin() + j);
                  --j;
                  num_edges_deleted++;

              }

  }

However, I am getting the same errors regardless.

BTW. I really appreciate all the comments so far. Thank you for your time.

Edit: Found memory leaks in a different part of the code using valgrind. Getting rid of that code (it wasn't really neccessary to the algorithm) got rid of it. I'm accepting @Julius' answer since it would have solved the problem according to my original statement. Also thanks to @RetiredNinja, @Beta, and @Golazo for their excellent comments.

rayabhik
  • 697
  • 1
  • 5
  • 9
  • WHat line does it happen? – The Vivandiere Mar 28 '15 at 21:57
  • 1
    If you have memory issues, the first thing to do is to get rid of raw owning pointers – The Vivandiere Mar 28 '15 at 21:58
  • 3
    Time to run your debugger. – Lightness Races in Orbit Mar 28 '15 at 22:00
  • Golazo I added the lines where the segfault is happening. I understand about using smart pointers. However, unless absolutely neccessary, I'd like to refrain from having to go through the entire project and get rid of all smart pointers. It's going to take more time than I have. – rayabhik Mar 28 '15 at 22:15
  • How are you storing all your original nodes and edges? Chances are you have an issue somewhere else in your program that just happens to exhibit itself at this point. – uesp Mar 28 '15 at 22:54
  • @uesp - I'm storing the original nodes and edges in a Graph class whose data member is a vector of Node pointers and some statistics about the graph. I think the problem does have to do with the deletions of edges that I am doing. Mostly because a previous version of this code that does not use a sliding window and is incremental does run just fine. – rayabhik Mar 28 '15 at 23:01
  • The errors occur when you use different datasets, but in both cases after you run 69 batch files. Are there other similarities, e.g. number of edges? Can you combine two batch files into one and see what happens? (I don't suppose there's any chance you could give us a [minimal complete example](http://stackoverflow.com/help/mcve)...) – Beta Mar 28 '15 at 23:03
  • @Beta: I will try these suggestions. Offhand, I don't think the number of edges are the same. I will try combining two batches into one and see what happens. However, what is also weird is that if I increase the sliding window by say 'n', the same problem happens after 'n' batches. I only wish I could give a MCE. However, it's research code plus it's really messy and complicated. – rayabhik Mar 28 '15 at 23:11
  • 1
    I don't see the `deleteEdge` function in your code, but if you're erasing items from the container while iterating then your loop is incorrect. As soon as you erase an item the iterator is invalid, so you can't use it to get the previous item. `erase` returns an iterator, you should use that, and only increase the iterator when you don't erase an item. This may help: http://stackoverflow.com/a/4645727/920069 – Retired Ninja Mar 28 '15 at 23:30
  • @RetiredNinja: Thanks for that. I was using an invalid iterator to get to the previous item. So I fixed that. But I'm still getting the same problem. I put the code for the deleteEdge function as it is now, and also changed the pop_edge function accordingly. – rayabhik Mar 28 '15 at 23:54
  • Are you familiar with the rules for "iterator invalidation"? – Ben Voigt Mar 29 '15 at 00:26
  • @BenVoigt: I looked it up, and while I was doing it wrong previously, even after fixing it, I'm getting the same error. – rayabhik Mar 29 '15 at 00:31

1 Answers1

0
for (int i = 0; i < node_list.size(); i++)
  {
  vector<Edge *>::iterator adj_it;

  for (adj_it = (node_list[i])->adjacent_edges.begin(); adj_it != (node_list[i])->adjacent_edges.end(); ++adj_it )
  {


      if ((max_batch_num - (*adj_it)->timestamp) > time_window)
      {


          deleteEdge(adj_it);
          num_edges_deleted++;
          --adj_it;

      }

  }

}

you are deleting the edge, then going backwards with --adj_it, then iterating back on the edge you just deleted with deleteEdge because the for loop does ++adj_it. You then try to check the timestamp object of a deleted (invalid) Edge object, causing the segfault.

Either that or you are removing the object from the Edge* vector and then invalidating your iterator.

The important part is that iterators are not indexes. You can't just erase an element and then do --adj_it. Here is a case where using an index would be easier, as you could just delete the edge object, remove the edge pointer from the vector, then continue the loop after doing --adj_it as you do. Iterators are just slower than indexes on vectors btw.

Jules G.M.
  • 3,624
  • 1
  • 21
  • 35
  • I was going backward, because I use the vector erase method, which removes the edge from the adjacency_list and then brings all the other elements after it forwards. @RetiredNinja's comment said that was wrong as erase returns a new iterator and I cannot use the previous iterator to go to the previous item. Hopefully, I understood your answer correctly. – rayabhik Mar 28 '15 at 23:59
  • I was using an index previously, and I tried it again. Same error. What I cannot understand is that if the size of the adjacent_edge is not 0, how can there be pointers in there that are pointing to illegal memory. Especially since that edge was added in the previous batch. – rayabhik Mar 29 '15 at 00:10
  • The simple answer to why there might be invalid items in the vector is that somehow your code put them there. Raw pointers are dangerous, and there's no guarantee you aren't double deleting something or just pushing some bad in there in the first place. – Retired Ninja Mar 29 '15 at 02:25
  • @RetiredNinja: I understand. Unfortunately, if I have to get rid of raw pointers I have to go over the entire project and changing pointers in tons of places to smart pointers or just objects. No more raw pointers for me for my next project. For this one though, is there any quick way of implementing a sliding window over a vector of pointers? – rayabhik Mar 29 '15 at 04:09