Bug 108650 - memory leak in qtruby? (Qt::Socket)
Summary: memory leak in qtruby? (Qt::Socket)
Status: RESOLVED UNMAINTAINED
Alias: None
Product: bindings
Classification: Developer tools
Component: general (show other bugs)
Version: unspecified
Platform: Gentoo Packages Linux
: NOR normal
Target Milestone: ---
Assignee: kde-bindings
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2005-07-06 15:42 UTC by Caleb Tennis
Modified: 2022-12-22 06:51 UTC (History)
1 user (show)

See Also:
Latest Commit:
Version Fixed In:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Caleb Tennis 2005-07-06 15:42:50 UTC
Version:            (using KDE KDE 3.4.1)
Installed from:    Gentoo Packages
OS:                Linux

I've been chasing a memory leak in a couple of my programs now for sometime, and I believe I have found it.  Please accept my apologies if I'm missing something obvious here.  Bear with me as I try to describe it.

I have a few qtruby  programs which, after running for a few days, tend to die with OOM errors after growing to very large memory consumption.  I also tried manually calling the garbage collector every second while the program was running to no avail.  Other programs did not exhibit this behavior, so after some tests, I found out what was causing hte leak:

I have a Qt::Object derived class which houses a Qt::Socket that I use for communication with various servers around our building.  My Qt::Object class defines this connection:

    Qt::Object::connect(@socket, SIGNAL('readyRead()'), self, SLOT('MessageReceivedSlot()'))

And this slot is defined as:

  def MessageReceivedSlot
    emit MessageSignal(@socket.readLine.chomp) while(@socket.canReadLine)
  end

And this is where the memory leak occurs.

I made a simple test case to reproduce it.  I created a small TCP server which just sends out data over and over again to anything that connects to it:

require 'socket'
port = 1999.to_i
server = TCPServer.new('localhost', port)
while (session = server.accept)
  idx = 0
  while true
    20.times { session.puts "Request: Blah #{idx}" }
    idx = idx + 1
    sleep 1
  end
end

And then I created a small program which uses this socket class to connect to the server and just continue to receive the messages.  I even changed the receive slot to NOT emit the signal, figuring that that may be the problem:

  def MessageReceivedSlot
    @socket.readLine.chomp while(@socket.canReadLine)
  end

So all my class does now is continue to receive data, and readLine it, but not do anything with it.  I am also calling the garbage collector quite often to make sure I'm not seeing anything funny.

Over time, the programs memory consumption continues to grow and grow.  It may start out at something like 22000K memory, and after 48 hours will be around 150000K, and will continue until it gets an OOM error.

My *hunch* is that there's something spurious going on with string to Qt::String conversion, perhaps a QString is getting created somewhere as a child of my socket and it's never being disposed of, but I don't know for sure.  I'm currently running in a debugger in an attempt to see what is causing the continual growth of memory, but any thoughts would be helpful.
Comment 1 Caleb Tennis 2005-07-06 16:29:06 UTC
As a side note, on another machine where I use this class only for sending data ( using @socket.writeBlock() ), and not receiving, the memory usage does not grow at all.  However, it seems that any time I receive a message from a Qt::Socket using the above mentioned methods, the memory usage grows.
Comment 2 Caleb Tennis 2005-07-06 17:34:02 UTC
If I disconnect my readyRead() signal from the MessageReceivedSlot, and set my socket's readBufferSize to a maximum of 500, and run my test, the program memory does not grow.  In other words, if I don't read data out of the socket buffer and let it fill up, the memory consumption stops.  However, if I read the data out of the socket, the memory goes up.

As an alternative test, I could use the readBlock method, but it takes a char * argument and I haven't figured out yet what to pass to it to get the data.  A  String doesn't seem to work nor does a Qt::ByteArray.
Comment 3 Caleb Tennis 2005-07-06 19:21:22 UTC
This seems to work as a workaround:

  def MessageReceivedSlot
    bytes = [ ]
    while true
      c = @socket.getch
      break if c == -1
      bytes << c
    end
    strings = bytes.pack("C*")
    strings.split("\n").each { |s| emit MessageSignal(s) unless s.empty? }
  end
Comment 4 Caleb Tennis 2005-07-07 16:12:50 UTC
The code in comment #3 doesn't work, after 24 hours of running I'm getting a leak again.

I can't seem to find anything leaky in the ObjectSpace of ruby itself.  I dont' see the number or size of strings, arrays, and hashes getting any bigger.  And I don't notice an increase in the number of Qt:: objects.  I doubt that the leak is in Qt itself, so perhaps somewhere in the glue code (smoke?) in between the two?
Comment 5 Richard Dale 2005-07-08 14:45:51 UTC
On Wednesday 06 July 2005 14:42, Caleb Tennis wrote:
[bugs.kde.org quoted mail]
Yes, you're right there is a leak for QString value return types. When a Smoke 
method returns a QString, it creates a new temporary QString with 'new', but 
never deletes it. I also tried 'QString*' and 'QString&' return types with 
readLine(), and those don't create a temporary string.


    virtual      QString readLine();

Generates this code:
   void x_45(Smoke::Stack x) {
        // readLine()
        QString xret = this->QSocket::readLine();
        x[0].s_voidp = (void*)new QString(xret);
    }


    virtual      QString * readLine(); Becomes

Generates this code:
    void x_45(Smoke::Stack x) {
        // readLine()
        QString* xret = this->QSocket::readLine();
        x[0].s_voidp = (void*)xret;
    }


    virtual      QString & readLine();

Generates this code:
    void x_45(Smoke::Stack x) {
        // readLine()
        QString& xret = this->QSocket::readLine();
        x[0].s_voidp = (void*)&xret;
    }

In Qt.cpp, the class to handle a return value has a boolean method called 
cleanup(), which is false.

class MethodReturnValue : public Marshall {
...

    bool cleanup() { return false; }
};

In handlers.cpp, the code to marshall a QString to a ruby string will only 
delete the QString if cleanup() is true:

case Marshall::ToVALUE:
{
    QString *s = (QString*)m->item().s_voidp;
    if(s) {
        if (s->isNull()) {
            *(m->var()) = Qnil;
        } else {
            *(m->var()) = rstringFromQString(s);
        }
        if(m->cleanup())
            delete s;
    } else {
            *(m->var()) = Qnil;
    }
}

So the temporary QString from the 'QString readLine()' version of the method 
is never deleted. Either cleanup() should return true in MethodReturnValue, 
or the marshalling code should check if the QString is a value type and only 
delete it if it is. 

I looked at the PerlQt code in PerlQt-3.009 beta and it's just the same, and 
will have this leak too.

-- Richard
Comment 6 Richard Dale 2005-07-09 13:48:28 UTC
SVN commit 433005 by rdale:

2005-07-09  Richard Dale  <Richard_Dale@tipitina.demon.co.uk>

* When a Qt method returned a QString value type, such as:
       QString QSocket::readLine()
  A temporary QString was being created that wasn't deleted and caused a 
  memory leak. Fixes problem reported by Caleb Tennis.

CCBUGS:108650


 M  +7 -0      ChangeLog  
 M  +1 -1      rubylib/qtruby/handlers.cpp  


--- trunk/KDE/kdebindings/qtruby/ChangeLog #433004:433005
@@ -1,3 +1,10 @@
+2005-07-09  Richard Dale  <Richard_Dale@tipitina.demon.co.uk>
+
+	* When a Qt method returned a QString value type, such as:
+	       QString QSocket::readLine()
+	  A temporary QString was being created that wasn't deleted and caused a 
+	  memory leak. Fixes problem reported by Caleb Tennis.
+
 2005-06-28  Richard Dale  <Richard_Dale@tipitina.demon.co.uk>
 
 	* Improved display of Qt::Enums in the KDevelop debugger. With a p
--- trunk/KDE/kdebindings/qtruby/rubylib/qtruby/handlers.cpp #433004:433005
@@ -863,7 +863,7 @@
 	     	} else {
                *(m->var()) = rstringFromQString(s);
 	     	}
-	     	if(m->cleanup())
+	     	if(m->cleanup() || m->type().isStack())
 	     	delete s;
          } else {
                 *(m->var()) = Qnil;
Comment 7 Caleb Tennis 2005-07-13 15:39:22 UTC
I've patched my source, recompiled, but now it dies:

tc@supercell ~/subversion/ruby/unico $ ruby unico.rb
/usr/lib/ruby/site_ruby/1.8/gina/client.rb:76: [BUG] Segmentation fault
ruby 1.8.2 (2004-12-25) [i686-linux]

Aborted

(Line 76 is my line where I call a method that returns a QString).

Removing the patch and recompiling, the program runs fine again, but with the memory leak.
Comment 8 Richard Dale 2005-07-14 12:41:01 UTC
SVN commit 434490 by rdale:

* Added example programs for client/server programming with Qt::Socket
  and associated classes. client.rb illustrates current bugs in QtRuby
* Qt::Socket.canReadLine() always returns true
* Qt::readLine() seg faults when called a second time
* A memory leak and seg faulting problems like the above were reported
  by Caleb Tennis

CCBUGS: 108650



 M  +9 -0      ChangeLog  
 A             rubylib/examples/network (directory)  
 A             rubylib/examples/network/clientserver (directory)  
 A             rubylib/examples/network/clientserver/client (directory)  
 A             rubylib/examples/network/clientserver/client/client.rb  
 A             rubylib/examples/network/clientserver/server (directory)  
 A             rubylib/examples/network/clientserver/server/server.rb  


--- trunk/KDE/kdebindings/qtruby/ChangeLog #434489:434490
@@ -1,3 +1,12 @@
+2005-07-14  Richard Dale  <Richard_Dale@tipitina.demon.co.uk>
+
+	* Added example programs for client/server programming with Qt::Socket
+	  and associated classes. client.rb illustrates current bugs in QtRuby
+	* Qt::Socket.canReadLine() always returns true
+	* Qt::readLine() seg faults when called a second time
+	* A memory leak and seg faulting problems like the above were reported
+	  by Caleb Tennis
+
 2005-07-09  Richard Dale  <Richard_Dale@tipitina.demon.co.uk>
 
 	* When a Qt method returned a QString value type, such as:
Comment 9 Richard Dale 2005-07-15 11:23:01 UTC
SVN commit 434751 by rdale:

* Qt::Socket started working correctly when I and regenerated and rebuilt 
  my Smoke library. Before then it was calling the wrong version of 
  QSocket::at() for some reason, and wasn't discarding bytes that had 
  already been read.
* Removed comment from the client.rb example about Qt::Socket.canReadLine
  always returning true now it works.

CCBUGS: 108650


 M  +9 -0      ChangeLog  
 M  +0 -2      rubylib/examples/network/clientserver/client/client.rb  


--- trunk/KDE/kdebindings/qtruby/ChangeLog #434750:434751
@@ -1,3 +1,12 @@
+2005-07-15  Richard Dale  <Richard_Dale@tipitina.demon.co.uk>
+
+	* Qt::Socket started working correctly when I and regenerated and rebuilt 
+	  my Smoke library. Before then it was calling the wrong version of 
+	  QSocket::at() for some reason, and wasn't discarding bytes that had 
+	  already been read.
+	* Removed comment from the client.rb example about Qt::Socket.canReadLine
+	  always returning true now it works.
+
 2005-07-14  Richard Dale  <Richard_Dale@tipitina.demon.co.uk>
 
 	* Added example programs for client/server programming with Qt::Socket
--- trunk/KDE/kdebindings/qtruby/rubylib/examples/network/clientserver/client/client.rb #434750:434751
@@ -59,8 +59,6 @@
 
     def socketReadyRead()
 		# read from the server
-		# This loops forever in QtRuby because 'canReadLine()'
-		# is alway true
 		while @socket.canReadLine() do
 	    	@infoText.append( @socket.readLine() )
 		end
Comment 10 Justin Zobel 2021-03-09 05:24:44 UTC
Thank you for the bug report.

As this report hasn't seen any changes in 5 years or more, we ask if you can please confirm that the issue still persists.

If this bug is no longer persisting or relevant please change the status to resolved.
Comment 11 Mathieu Jobin 2022-12-22 06:51:35 UTC
QtRuby only existed for Qt4 and Korundum, which never got updated for 5.x

This is unmaintained and any effort towards QtRuby for Qt6 will be from scratch.

Therefore, closing this ticket