2016-05-04 1 views
4

J'utilise l'API C d'un fournisseur pour un logiciel métier en chargeant leur bibliothèque à l'aide du module ctypes de Python. Après le déploiement du logiciel que j'ai écrit, j'ai trouvé que la bibliothèque du fournisseur fuit la mémoire sur une base cohérente et prévisible, selon le nombre d'appels d'une certaine fonction qui fait partie de leur API.Comment contourner une fuite de mémoire dans la DLL d'un fournisseur utilisé en Python?

J'ai même dupliqué la fuite dans un programme C qui n'utilise aucune allocation de tas. J'ai contacté le vendeur à propos du problème, et ils ont dit qu'ils y travaillaient, mais je ne peux probablement pas attendre un correctif avant la prochaine version du logiciel.

J'ai eu l'idée de recharger la DLL du fournisseur après un certain seuil d'appels à la fonction de fuite, mais cela n'a pas libéré la mémoire qui a fui.

Je trouve que je pouvais forcer la bibliothèque à décharger comme ceci:

_ctypes.FreeLibrary(vendor_dll._handle) 

Cela permet de libérer la mémoire, mais l'interpréteur crash apparemment au hasard après un certain nombre de minutes d'utilisation de l'API du fournisseur.

Je trouve cette question dans le bug tracker Python qui décrit ma situation: https://bugs.python.org/issue14597

Il semble que s'il y a encore une référence ouverte à la bibliothèque, le forçant à décharger va planter inévitablement l'interpréteur Python. Dans le pire des cas, je pense pouvoir charger la bibliothèque du fournisseur dans un processus séparé, demander des requêtes proxy à l'aide d'une file d'attente multiprocesseur et configurer un chien de garde pour recréer le processus si l'interpréteur meurt.

Y a-t-il une meilleure façon de contourner ce problème?

+0

Je suppose qu'une solution plus élégante à ma solution de contournement ci-dessus serait de quitter et de relancer le processus de travail sur un intervalle régulier. Cela permettrait à la librairie libérée par la librairie du vendeur de se libérer, mais ne ferait pas planter l'interpréteur. – jakogut

+0

Si libérer la bibliothèque fonctionne, ce n'est pas vraiment une fuite de mémoire. La bibliothèque doit suivre ces allocations pour les libérer sur 'DLL_PROCESS_DETACH'. En ce qui concerne le déchargement de 'vendor_dll', vous devez d'abord supprimer toutes les références à l'instance en cours et veiller à ce que rien ne répète son code, y compris les rappels asynchrones en attente. Ensuite, vous pouvez décharger et recharger la DLL. Les bibliothèques partagées sont comptées par référence, donc pour s'assurer qu'elles sont effectivement déchargées, appelez 'FreeLibrary' jusqu'à ce que cela déclenche un' OSError'. – eryksun

Répondre

1

En fin de compte, je résolu le problème en chargeant la bibliothèque du fournisseur dans un processus séparé, et l'accès à travers Pyro4, comme ceci:

class LibraryWorker(multiprocessing.Process): 
    def __init__(self): 
     super().__init__() 

    def run(self): 
     self.library = ctypes.windll.LoadLibrary(
      'vendor_library.dll') 

     Pyro4.serveSimple(
      {self, 'library'}, 
      ns=False) 

    def lib_func(self): 
     res = self.library.func() 
     return res 

Il était un peu de travail supplémentaire pour masser l'ancien code ne pas passer les pointeurs ctypes entre les deux processus, mais cela fonctionne.

Avec la bibliothèque chargée dans un processus séparé, je peux garder une trace de l'utilisation de la mémoire. Quand il devient trop élevé, je peux terminer et recréer le processus pour libérer la mémoire.