Corruption du tas dans un programme multi-thread

Le problème exposé dans ce sujet a été résolu.

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.

+0 -0

@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).

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

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