[Solved] QTcpSocket in a thread doesn't close pipes
-
Hi all,
I have a multithread application that acts as tcp server. When it receive a new connection it pass the socket descriptor to a new object and the it "moveToThread" the same object.I'm on Linux.
The problem is that when the connection close I delete the object but it seems it doesn't close "pipe".
This is the situation:
@
StarServer::StarServer(.....)
: QTcpServer(parent)
{
...
...
}void StarServer::incomingConnection(int socket_descriptor)
{GestoreComunicazioneSatellite *gestore_comunicazione_satellite; gestore_comunicazione_satellite = new GestoreComunicazioneSatellite(socket_descriptor); QThread *thread = new QThread(); gestore_comunicazione_satellite->moveToThread(thread); connect(gestore_comunicazione_satellite, SIGNAL(destroyed()), thread, SLOT(terminate())); connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); thread->start();
}
GestoreComunicazioneSatellite::GestoreComunicazioneSatellite(int socket_descriptor) :
QObject(0)
{
m_socket = new QTcpSocket(this);
m_socket->setSocketDescriptor(socket_descriptor);}
@When I want to close connection from server I call from StarServer:
@
gestore_comunicazione_satellite->deleteLater();
@It seems to works but if I use "lsof -p" to see the opened connections from my server application I get:
@
lsof -p 14561
...
...
star 14561 luca 14r FIFO 0,8 0t0 86449 pipe
star 14561 luca 15w FIFO 0,8 0t0 86449 pipe
star 14561 luca 16r FIFO 0,8 0t0 87564 pipe
star 14561 luca 17w FIFO 0,8 0t0 87564 pipe
star 14561 luca 18r FIFO 0,8 0t0 87630 pipe
star 14561 luca 19w FIFO 0,8 0t0 87630 pipe
@and every time a tcp client connect and disconnect I get e new additional line in lsof output.
I seems that deleting the object doesn't close pipe connection. -
The problem disappears if I don't move to thread:
@
void StarServer::incomingConnection(int socket_descriptor)
{
GestoreComunicazioneSatellite *gestore_comunicazione_satellite;
gestore_comunicazione_satellite = new GestoreComunicazioneSatellite(socket_descriptor);
}
@But this way I get a single thread application...
-
To test the problem I modified the threadedfortuneserver (from SDK example) as follow:
(fortuneserver.cpp)
@#include "fortuneserver.h"
#include "fortunethread.h"#include <stdlib.h>
//! [0]
FortuneServer::FortuneServer(QObject *parent)
: QTcpServer(parent)
{
fortunes << tr("You've been leading a dog's life. Stay off the furniture.")
<< tr("You've got to think about tomorrow.")
<< tr("You will be surprised by a loud noise.")
<< tr("You will feel hungry again in another hour.")
<< tr("You might have mail.")
<< tr("You cannot kill time without injuring eternity.")
<< tr("Computers are not intelligent. They only think they are.");
}
//! [0]//! [1]
void FortuneServer::incomingConnection(int socketDescriptor)
{
QString fortune = fortunes.at(qrand() % fortunes.size());
FortuneThread *ft = new FortuneThread(socketDescriptor, fortune);//connect(ft, SIGNAL(finished()), ft, SLOT(deleteLater())); QThread *t = new QThread(); ft->moveToThread(t); connect(ft, SIGNAL(destroyed()), t, SLOT(terminate())); connect(t, SIGNAL(finished()), t, SLOT(deleteLater())); t->start();
}
@(fortunethread.h)
@
#ifndef FORTUNETHREAD_H
#define FORTUNETHREAD_H#include <QThread>
#include <QTcpSocket>//! [0]
class FortuneThread : public QObject
{
Q_OBJECTpublic:
FortuneThread(int socketDescriptor, const QString &fortune);private:
int socketDescriptor;
QString text;private slots:
void sendMessage();
signals:
void error(QTcpSocket::SocketError socketError);};
//! [0]#endif
@
(fortunethread.cpp)
@#include "fortunethread.h"
#include <QtNetwork>
#include <QTimer>//! [0]
FortuneThread::FortuneThread(int socketDescriptor, const QString &fortune)
: QObject(0), socketDescriptor(socketDescriptor), text(fortune)
{QTimer::singleShot(100, this, SLOT(sendMessage()));
}
//! [0]//! [1]
void FortuneThread::sendMessage()
{
QTcpSocket tcpSocket;
//! [1] //! [2]
if (!tcpSocket.setSocketDescriptor(socketDescriptor)) {
emit error(tcpSocket.error());
return;
}
//! [2] //! [3]QByteArray block; QDataStream out(&block, QIODevice::WriteOnly); out.setVersion(QDataStream::Qt_4_0); out << (quint16)0; out << text; out.device()->seek(0); out << (quint16)(block.size() - sizeof(quint16));
//! [3] //! [4]
tcpSocket.write(block); tcpSocket.disconnectFromHost(); tcpSocket.waitForDisconnected(); deleteLater();
}
@You can tray and see that every time a fortuneclient connect, the opened pipes number increase.
To test this you can do the following:
@
luca@slack1337-gw:~$ pgrep threadedfortune
21684@
then:
@
lsof -p 21684
...
...
threadedf 21684 luca 0r FIFO 0,8 0t0 144298 pipe
threadedf 21684 luca 1w FIFO 0,8 0t0 144299 pipe
threadedf 21684 luca 2w FIFO 0,8 0t0 144300 pipe
threadedf 21684 luca 3r FIFO 0,8 0t0 144814 pipe
threadedf 21684 luca 4w FIFO 0,8 0t0 144814 pipe
threadedf 21684 luca 5r FIFO 0,8 0t0 144817 pipe
threadedf 21684 luca 6w FIFO 0,8 0t0 144817 pipe
threadedf 21684 luca 7u unix 0xf1bd8240 0t0 144818 socket
threadedf 21684 luca 8u unix 0xf1b5f600 0t0 144303 socket
threadedf 21684 luca 9u unix 0xf1b5fcc0 0t0 144304 socket
threadedf 21684 luca 10u IPv4 144305 0t0 TCP *:58552 (LISTEN)@
and after 4 client connections:
@
lsof -p 21684threadedf 21684 luca 0r FIFO 0,8 0t0 144298 pipe
threadedf 21684 luca 1w FIFO 0,8 0t0 144299 pipe
threadedf 21684 luca 2w FIFO 0,8 0t0 144300 pipe
threadedf 21684 luca 3r FIFO 0,8 0t0 144814 pipe
threadedf 21684 luca 4w FIFO 0,8 0t0 144814 pipe
threadedf 21684 luca 5r FIFO 0,8 0t0 144817 pipe
threadedf 21684 luca 6w FIFO 0,8 0t0 144817 pipe
threadedf 21684 luca 7u unix 0xf1bd8240 0t0 144818 socket
threadedf 21684 luca 8u unix 0xf1b5f600 0t0 144303 socket
threadedf 21684 luca 9u unix 0xf1b5fcc0 0t0 144304 socket
threadedf 21684 luca 10u IPv4 144305 0t0 TCP *:58552 (LISTEN)
threadedf 21684 luca 12r FIFO 0,8 0t0 146593 pipe
threadedf 21684 luca 13w FIFO 0,8 0t0 146593 pipe
threadedf 21684 luca 14r FIFO 0,8 0t0 146596 pipe
threadedf 21684 luca 15w FIFO 0,8 0t0 146596 pipe
threadedf 21684 luca 16r FIFO 0,8 0t0 146597 pipe
threadedf 21684 luca 17w FIFO 0,8 0t0 146597 pipe
threadedf 21684 luca 18r FIFO 0,8 0t0 146598 pipe
threadedf 21684 luca 19w FIFO 0,8 0t0 146598 pipe
threadedf 21684 luca 20r FIFO 0,8 0t0 145980 pipe
threadedf 21684 luca 21w FIFO 0,8 0t0 145980 pipe
threadedf 21684 luca 22r FIFO 0,8 0t0 146600 pipe
threadedf 21684 luca 23w FIFO 0,8 0t0 146600 pipe
@As you can see there are a lot of new opened pipes.
This problem doesn't happens with the original threadedfortuneserver .
-
Ok now I know that the problem is caused by QThread and not by QTcpSocket.
In fact I have the same problem with this simple code:
@
#include "mainwindow.h"
#include "ui_mainwindow.h"#include <QTimerEvent>
#include <QTimer>#include <QThread>
#include <QDebug>MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);timer = startTimer(100);
}
MainWindow::~MainWindow()
{
delete ui;
}void MainWindow::timerEvent(QTimerEvent *e)
{
if(e->timerId() == timer)
{
QThread *t = new QThread;
QTimer::singleShot(500, t, SLOT(terminate()));
connect(t, SIGNAL(terminated()),t, SLOT(quit()));
connect(t, SIGNAL(terminated()),t, SLOT(deleteLater()));
t->start();
}
}
@After some seconds the program crash and I get the error message:
@
GLib-ERROR **: Cannot create pipe main loop wake-up: Too many open filesaborting...
@This is because I get 1024 opened pipe.
What do I wrong? -
I haven't verified this but terminating a thread is usually always a bad idea, because it immediately removes the thread from existence. And as you have a spinning event loop in the thread due to start() you are removing the event loop from existence anywhere in its code path too - which may or will lead to the leaked resources.
The same way
@
connect(t, SIGNAL(terminated()),t, SLOT(quit()));
@
makes no sense, because once you have terminated a thread it is just gone; there is nothing left to quit.If you want to "end" a thread use quit() or exit() or just leave the run() method when reimplemented. Never ever use terminate(), unless you are absolutely aware of what you are doing.
There is a reason for warnings like this:
[quote]Warning: This function is dangerous and its use is discouraged. The thread can be terminated at any point in its code path. Threads can be terminated while modifying data. There is no chance for the thread to clean up after itself, unlock any held mutexes, etc. In short, use this function only if absolutely necessary.[/quote]Just substiute <code>terminate()</code> with <code>quit()</code> and <code>terminated()</code> with <code>finished()</code> in your example and you might have your solution.
-
That's interesting. I've tried a small example similar to yours and I have no issues whatsoever with thousands of threads on <code>Linux ubuntu 3.0.0-16-generic #29-Ubuntu SMP Tue Feb 14 12:48:51 UTC 2012 x86_64 x86_64 x86_64 GNU/Linux</code>, <code>QT_VERSION:4.8.0</code> and <code>gcc (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1</code>.
Can you provide a small compilable example that reproduces your problem?
-
[quote author="Lukas Geyer" date="1332441168"]That's interesting. I've tried a small example similar to yours and I have no issues whatsoever with thousands of threads on <code>Linux ubuntu 3.0.0-16-generic #29-Ubuntu SMP Tue Feb 14 12:48:51 UTC 2012 x86_64 x86_64 x86_64 GNU/Linux</code>, <code>QT_VERSION:4.8.0</code> and <code>gcc (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1</code>.
Can you provide a small compilable example that reproduces your problem?[/quote]
To reproduce the problem you can create a new QtCreator project:
"Create project"
"Qt Widget Project"
"Qt GUI Application"
(keep all default parameters)You get a MainWindow application. Now simply replace mainwindow.h and mainwindow.cpp as follow:
(mainwindow.h)
@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>
namespace Ui {
class MainWindow;
}class MainWindow : public QMainWindow
{
Q_OBJECTpublic:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();private:
int timer;
Ui::MainWindow *ui;
void timerEvent(QTimerEvent *);
};#endif // MAINWINDOW_H
@
(mainwindow.cpp)
@
#include "mainwindow.h"
#include "ui_mainwindow.h"#include <QTimerEvent>
#include <QTimer>#include <QThread>
#include <QDebug>MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);timer = startTimer(100);
}
MainWindow::~MainWindow()
{
delete ui;
}void MainWindow::timerEvent(QTimerEvent *e)
{
if(e->timerId() == timer)
{
QThread *t = new QThread;
QTimer::singleShot(500, t, SLOT(terminate()));
connect(t, SIGNAL(terminated()),t, SLOT(quit()));
connect(t, SIGNAL(terminated()),t, SLOT(deleteLater()));
t->start();
}
}@
Now run, open a console and type:
pgrep yourapplicationname
(you get the PID) then:
lsof -p YOUR_PID
At this point I get:
@
....
....
....
test_thre 2834 luca 152r FIFO 0,8 0t0 8820 pipe
test_thre 2834 luca 153w FIFO 0,8 0t0 8820 pipe
test_thre 2834 luca 154r FIFO 0,8 0t0 8821 pipe
test_thre 2834 luca 155w FIFO 0,8 0t0 8821 pipe
test_thre 2834 luca 156r FIFO 0,8 0t0 8822 pipe
test_thre 2834 luca 157w FIFO 0,8 0t0 8822 pipe
test_thre 2834 luca 158r FIFO 0,8 0t0 8823 pipe
test_thre 2834 luca 159w FIFO 0,8 0t0 8823 pipe
test_thre 2834 luca 160r FIFO 0,8 0t0 8824 pipe
test_thre 2834 luca 161w FIFO 0,8 0t0 8824 pipe
test_thre 2834 luca 162r FIFO 0,8 0t0 8825 pipe
test_thre 2834 luca 163w FIFO 0,8 0t0 8825 pipe
test_thre 2834 luca 164r FIFO 0,8 0t0 8826 pipe
test_thre 2834 luca 165w FIFO 0,8 0t0 8826 pipe
test_thre 2834 luca 166r FIFO 0,8 0t0 8827 pipe
test_thre 2834 luca 167w FIFO 0,8 0t0 8827 pipe
test_thre 2834 luca 168r FIFO 0,8 0t0 8828 pipe
test_thre 2834 luca 169w FIFO 0,8 0t0 8828 pipe
test_thre 2834 luca 170r FIFO 0,8 0t0 8829 pipe
test_thre 2834 luca 171w FIFO 0,8 0t0 8829 pipe
test_thre 2834 luca 172r FIFO 0,8 0t0 8830 pipe
test_thre 2834 luca 173w FIFO 0,8 0t0 8830 pipe
test_thre 2834 luca 174r FIFO 0,8 0t0 8831 pipe
test_thre 2834 luca 175w FIFO 0,8 0t0 8831 pipe
test_thre 2834 luca 176r FIFO 0,8 0t0 8832 pipe
test_thre 2834 luca 177w FIFO 0,8 0t0 8832 pipe
test_thre 2834 luca 178r FIFO 0,8 0t0 8833 pipe
test_thre 2834 luca 179w FIFO 0,8 0t0 8833 pipe
test_thre 2834 luca 180r FIFO 0,8 0t0 8834 pipe
test_thre 2834 luca 181w FIFO 0,8 0t0 8834 pipe
test_thre 2834 luca 182r FIFO 0,8 0t0 8835 pipe
test_thre 2834 luca 183w FIFO 0,8 0t0 8835 pipe
test_thre 2834 luca 184r FIFO 0,8 0t0 8867 pipe
test_thre 2834 luca 185w FIFO 0,8 0t0 8867 pipe
test_thre 2834 luca 186r FIFO 0,8 0t0 8868 pipe
test_thre 2834 luca 187w FIFO 0,8 0t0 8868 pipe
test_thre 2834 luca 188r FIFO 0,8 0t0 8892 pipe
test_thre 2834 luca 189w FIFO 0,8 0t0 8892 pipe
test_thre 2834 luca 190r FIFO 0,8 0t0 8893 pipe
test_thre 2834 luca 191w FIFO 0,8 0t0 8893 pipe
test_thre 2834 luca 192r FIFO 0,8 0t0 8894 pipe
test_thre 2834 luca 193w FIFO 0,8 0t0 8894 pipe
....
....
@ -
I cannot reproduce your problem, even with the code you've provided. Your code throws tons of exceptions as you are still using <code>terminate()</code> instead of <code>quit()</code> but the number of used pipes remains at a low constant, independent of the number of threads.
You may strace to find out what could cause the excessive amount of opened pipes, but I'm still not quite sure if it is even caused by Qt. Do you have access to a fresh environment - for example in a virtual machine - to find out if this issue might be restricted to your local installation?
In addition, you may file a bug report for Qt but it could take some time until your issue is handeled. So your next place to go is probably the Qt mailing list.
-
[quote author="Lukas Geyer" date="1332491252"]I cannot reproduce your problem, even with the code you've provided. Your code throws tons of exceptions as you are still using <code>terminate()</code> instead of <code>quit()</code> but the number of used pipes remains at a low constant, independent of the number of threads.
You may strace to find out what could cause the excessive amount of opened pipes, but I'm still not quite sure if it is even caused by Qt. Do you have access to a fresh environment - for example in a virtual machine - to find out if this issue might be restricted to your local installation?
In addition, you may file a bug report for Qt but it could take some time until your issue is handeled. So your next place to go is probably the Qt mailing list.[/quote]
Sorry Lukas, I didn't understand you before.
I solved the problem as you said. This is the code:
@
void MainWindow::timerEvent(QTimerEvent *e)
{
if(e->timerId() == timer)
{
QThread *t = new QThread;
QTimer::singleShot(500, t, SLOT(quit()));
//connect(t, SIGNAL(terminated()),t, SLOT(quit()));
connect(t, SIGNAL(terminated()),t, SLOT(deleteLater()));
t->start();
}
}
@Thanks!