Corruption du tas dans un programme multi-thread

L'auteur de ce sujet a trouvé une solution à son problème.
Auteur du sujet

Bonjour à tous,

Je suis coincé depuis plusieurs jours sur un problème qui viens probablement d'une corruption du tas. Je suis en train d'écrire (ou plutôt de réécrire) une librairie qui permettra à un programme utilisateur de lancer une interface graphique Qt de manière asynchrone.

Après avoir testé pas mal de trucs, j'en suis arrivé à la conclusion que je devais faire un exécutable à part qui contient l'interface graphique et que je devais communiquer avec lui depuis un thread séparé.

Actuellement, le programme qui importe la librairie appelle une fonction pour initialiser (ça lance le thread qui lance l'exécutable). Il appelle ensuite une fonction qui envoi des données à l'interface et attend (de manière synchrone) des données en retour. Enfin, l'appelant libère les ressources (thread, interface, …).

Voici le code de l'exécutable qui appelle la librairie :

1
2
3
4
5
6
7
8
int main(int argc, char* argv[])
{
    unsigned long id;
    InitialiserDll(&id);
    DisplayCorrection(id);
    DechargerDll(id);
    return 0;
}

Le code de la librairie :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class Controler {
public:
    std::thread fccThread;
    BlockingQueue<std::string> putter;
    BlockingQueue<std::string> getter;

    Controler();
    ~Controler();
};

Controler::Controler() {
    // On démarre le thread ici pour être sûr que putter et getter sont bien initialisés
    fccThread = std::thread(threadFun, std::ref(putter), std::ref(getter));
}
Controler::~Controler(){
    putter.push("!!quit!!");
    fccThread.join();
}

std::map<unsigned long, std::shared_ptr<Controler>> controlers;
unsigned long nextId = 0;

unsigned long newControler(){
    controlers.emplace(nextId, std::make_shared<Controler>());
    return nextId++;
}

int InitialiserDll(unsigned long *id){
    *id = newControler();
    return 1;
}
int DechargerDll(unsigned long id){
    controlers.erase(id);
    return 1;
}
int DisplayCorrection(unsigned long id){
    auto controler = controlers[id];

    // on envoie un message et on en attend un en retour
    controler->putter.push("controler.xml");
    std::string corrected = controler->getter.pop();
    std::cout << corrected << std::endl;
    return 1;
}

Le code de la BlockingQueue (que j'ai pompé sur StackOverflow) :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
template<typename T>
class BlockingQueue
{
private:
    std::mutex mut;
    std::condition_variable cv;
    std::deque<T> queue;

public:
    void push(T const &value){
        {
            std::unique_lock<std::mutex> lock(mut);
            queue.push_front(value);
        }
        cv.notify_one();
    }

    T pop(){
        std::unique_lock<std::mutex> lock(mut);
        cv.wait(lock, [this]{ return !this->queue.empty(); });
        T rc(std::move(this->queue.back()));
        this->queue.pop_back();
        return rc;
    }

    bool tryPop(T &v){
        std::unique_lock<std::mutex> lock(mut);
        if(!cv.wait_for(lock, std::chrono::milliseconds(1), [this]{ return !this->queue.empty(); })){
            return false;
        }
        v = std::move(this->queue.back());
        this->queue.pop_back();
        return true;
    }
};

Et enfin ce qui correspond au thread :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
class MessageManager : public QObject {
    Q_OBJECT
    BlockingQueue<std::string> &getter;
    BlockingQueue<std::string> &putter;
    QLocalServer serv;
    QLocalSocket *sock;
    QProcess fcc;
    QTimer timer;

public:
    MessageManager(BlockingQueue<std::string> &getter,
                   BlockingQueue<std::string> &putter,
                   QObject *parent = 0);

signals:
    void correct(std::string);

public slots:
    void tic();
    void newConnection();
    void corrected();
};

void threadFun(BlockingQueue<std::string> &getter, BlockingQueue<std::string> &putter) {
    int argc = 0;
    QCoreApplication app(argc, NULL);
    MessageManager messageManager(getter, putter);
    app.exec();
}

MessageManager::MessageManager(BlockingQueue<std::string> &getter,
                               BlockingQueue<std::string> &putter,
                               QObject *parent) : QObject(parent),
                                                  getter(getter),
                                                  putter(putter),
                                                  serv(this),
                                                  fcc(this),
                                                  timer(this) {
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> dis('a', 'z');
    // génération d'un nom aléatoire de 10 caractères
    QString serverName;
    for(int i=0; i<10; i++){
        serverName += dis(gen);
    }

    connect(&serv, SIGNAL(newConnection()), this, SLOT(newConnection()));
    serv.listen(serverName);
    QStringList args;
    args << serverName;

    fcc.start("executable.exe", args);

    connect(&timer, SIGNAL(timeout()), this, SLOT(tic()));
}

// tic est exécuté périodiquement une fois que l'on est connecté à l'exécutable
void MessageManager::tic(){
    std::string mess;
    if(getter.tryPop(mess)){
        sock->write(mess.c_str());
        if(mess == "!!quit!!"){
            QCoreApplication::exit();
        }
    }
}
void MessageManager::newConnection(){
    sock = serv.nextPendingConnection();
    connect(sock, SIGNAL(readyRead()), this, SLOT(corrected()));
    timer.start(10);
}
void MessageManager::corrected(){
    QString correction = sock->readAll();
    putter.push(correction.toStdString());
}

Voilà, j'ai essayé de supprimer tout ce qui était spécifique à mon utilisation et le code tel que présenté pose quand même problème.

Suivant les exécutions, j'ai une jolie fenêtre de Windows qui me dit qu'une assertion n'est pas respecté (_pFirstBlock == _pHead) dans son fichier dbgheap.c. D'où le fait que je soupçonne très fortement une corruption du tas. Les autres erreurs d'exécution que j'ai eu sont :

  • Plantage lors de la désallocation du lock de tryPop au moment de libérer le mutex.
  • Plantage à la fin de l'exécution du slot corrected() lors de la désallocation de la std::string créé par correction.toStdString.

Dernière info qui est peut-être significative : je compile tout avec visual studio c++ 2012 (j'ai malheureusement pas le choix).

Du coup, est-ce que quelqu'un vois d'où pourrais venir le problème? Autrement, si vous voyez des trucs pas bien dans mon code, hésitez pas à le signaler non plus.

Merci d'avance aux courageux qui se pencheront sur le problème.

Édité par Berdes

+0 -0

Lu'!

Pourquoi implémenter toi même la blocking-queue ? Il doit y avoir la même chose dans boost (voir même dans Qt) en testé 10 000 fois et certainement en plus performant.

T'as cette assertion qui plante, mais t'as essayé de remonter les appels avec un debugger pour voir qui fout la bringue ?

First : Always RTFM - "Tout devrait être rendu aussi simple que possible, mais pas plus." A.Einstein

+0 -0
Auteur du sujet

@Ksass`Peuk : Aussi étonnant que ça puisse paraître, je n'ai rien trouvé de tel (que ce soit dans Qt, dans boost, ni même dans la lib standard).

Finalement, après avoir fait quelques recherches sur la signification de l'erreur, je suis tombé sur un thread de stackoverflow qui indique un certain nombre de causes possibles à celle-ci. L'une d'entre elle est le fait de compiler la librairie en statique. J'étais passé en statique au début du projet à cause d'un certain nombre d'erreurs assez incompréhensibles de la part du compilateur qui laissaient suggérer qu'il fallait compiler en statique. Finalement, j'ai réussi (sur mon cas réduit à l'extrême) à compiler en dynamique, ce qui a miraculeusement résolu tous mes problèmes. Maintenant, je sais qu'il ne faut pas faire confiance au compilateur de visual studio (bon, j'aurais du m'en douter quand j'ai vu qu'il me proposait de changer certaines fonctions standard pour des fonctions non standard spécial microsoft).

EDIT RomHa Korev : en fait, j'ai fait le choix de virer tout les trucs Qt du thread principal après avoir vu qu'une grande partie des trucs de Qt nécessitent une boucle d'événement (ce que je ne peux pas fournir dans le thread principal).

Édité par Berdes

+0 -0
Vous devez être connecté pour pouvoir poster un message.
Connexion

Pas encore inscrit ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte