FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Memory leak when attaching event handlers from Python · Issue #1972 · pythonnet/pythonnet · GitHub

Repository navigation

Memory leak when attaching event handlers from Python #1972

Closed

Description

Environment

  • Pythonnet version: 3.0.0
  • Python version: 3.7.4
  • Operating System: Windows 10
  • .NET Framework 4.8.4515.0

Details

  • There appears to be a memory leak when attaching a method to an event handler from Python. I was able to break this down into a very simple example. Below is my C# Library
using System;

namespace MemoryLeak
{
    public Class MemoryLeak : IDisposable
    {
        public event EventHandler LeakEvent;
        public MemoryLeak()
        {
            GC.Collect();
            Console.WriteLine(GC.GetTotalMemory(true));
        }
        public void Dispose()
        {
            this.LeakEvent = null;
        }
    }
}

(This was the file in my C# class library, just built and took the DLL to use in the below python script)

and the python script that utilizes this library:

import gc
import sys

import clr

def main():
    sys.path.append("./dlls") # the dll from the above class library is stored here
    clr.AddReference("MemoryLeak")

    import System

    from MemoryLeak import MemoryLeak

    def event_handler():
        pass

    for _ in range(200):
        example = MemoryLeak()
        example.LeakEvent += event_handler
        example.LeakEvent -= event_handler
        example.Dispose()
        del example
        gc.collect()
        System.GC.Collect()

if __name__ == "__main__":
    main()

If you run this python script successfully, you will notice that the total memory steadily increases. If you simply remove the following two lines from the python script regarding the addition of the event handler into the C# object, you will notice no memory increase:

        example.LeakEvent += event_handler
        example.LeakEvent -= event_handler

I would expect that between the cleanup of the attached handler, disposal and manual collections from both python and C#, that the event handler + memory leak class would get garbage collected properly, but they do not appear to be removed entirely based on memory tracking.

To note, I did attempt to make a C# console application that performs the same steps as the python script and the memory did not leak in that example.

I am more than happy to help contribute if this is deemed to be an issue with pythonnet, but would need a little guidance on where to start.

Activity

  1. sean-marten commented on Oct 13, 2022

    Author

    Output from full python script (in bytes):

    502260
    525832
    526020
    526160
    526316
    526392
    526820
    527056
    527332
    527408
    527484
    527912
    527988
    525456
    525532
    525608
    526036
    526112
    526588
    526664
    527092
    527168
    527244
    527320
    527396
    527824
    527900
    527976
    536244
    528800
    528876
    528952
    529028
    537296
    529544
    529620
    529696
    529772
    531240
    531316
    531392
    529020
    529096
    529524
    529600
    529676
    529752
    529828
    530256
    530332
    530408
    538676
    530912
    530988
    531064
    531140
    531216
    531644
    531720
    531796
    531872
    532300
    532376
    532452
    532528
    532604
    533032
    533108
    533184
    541452
    530876
    530952
    531028
    531104
    531180
    531608
    531684
    531760
    531836
    531912
    532340
    532416
    532492
    532568
    532996
    533072
    533148
    533224
    533300
    533728
    535964
    536040
    536116
    536544
    536620
    536696
    536772
    536848
    537276
    534892
    534968
    535044
    535472
    535548
    535624
    535700
    535776
    536204
    536280
    536356
    536432
    536508
    536936
    537012
    537088
    537164
    537592
    537668
    537744
    537820
    537896
    538324
    538400
    538476
    538552
    538980
    539056
    536684
    536760
    536836
    537264
    537340
    537416
    537492
    537920
    537996
    538072
    538148
    546416
    538652
    538728
    538804
    538880
    547148
    539384
    539460
    539536
    539612
    540040
    540116
    540192
    540268
    540344
    540772
    540848
    540924
    538540
    538968
    539044
    539120
    539196
    539272
    539700
    539776
    539852
    539928
    540356
    540432
    540508
    540584
    540660
    541088
    541164
    541240
    541316
    549584
    541820
    541896
    541972
    542048
    542476
    542552
    542628
    542704
    540332
    540760
    540836
    540912
    549180
    541416
    541492
    541568
    541644
    541720
    542148
    542224
    542300
    542376
    547484
    547560
    

    Output when event handler lines are removed (in bytes):

    502260
    504568
    504944
    505032
    505056
    505080
    505104
    505288
    505312
    505336
    505712
    505736
    505760
    505784
    505808
    505832
    505856
    506200
    506576
    506600
    506624
    506648
    506672
    503704
    503728
    503752
    504128
    504152
    504176
    504200
    504224
    504248
    504272
    504296
    504672
    504696
    504720
    504744
    504768
    504792
    504816
    504840
    505216
    505240
    505264
    505288
    505312
    505336
    505360
    513576
    505760
    505784
    505808
    505832
    505856
    505880
    505904
    505928
    506316
    507172
    507196
    507220
    507244
    507268
    507292
    515508
    507692
    507716
    507740
    507764
    507788
    507812
    507836
    512740
    504924
    504948
    504972
    504996
    505020
    505044
    505068
    513284
    505468
    505492
    505516
    505540
    505564
    505588
    505612
    505636
    506012
    506036
    506060
    506084
    506108
    506132
    506156
    506180
    506556
    506580
    506604
    506628
    506652
    506676
    506700
    506724
    507100
    507124
    507148
    507172
    507196
    507220
    507244
    507268
    507644
    507668
    507692
    507716
    507740
    507764
    507788
    507812
    508188
    504548
    504572
    504596
    504620
    504644
    504668
    504692
    505068
    505092
    505116
    505140
    505164
    505188
    505212
    505236
    505612
    505636
    505660
    505684
    505708
    505732
    505756
    513972
    506156
    506180
    506204
    506228
    506252
    506276
    506300
    506324
    506700
    506724
    506748
    506772
    506796
    506820
    506844
    515060
    507244
    507268
    507292
    507316
    507340
    507364
    507388
    507412
    507788
    507812
    507836
    504548
    504572
    504596
    504620
    504644
    505020
    505044
    505068
    505092
    505116
    505140
    505164
    505188
    505564
    505588
    505612
    505636
    505660
    505684
    505708
    505732
    506108
    506132
    506156
    506180
    506204
    506228
    

    If you run even more cycles, the problem will exacerbate as well.

  2. changed the title [-]Memory link when attaching event handlers from Python[/-] [+]Memory leak when attaching event handlers from Python[/+] on Oct 13, 2022
  3. lostmsu commented on Oct 13, 2022

    Member

    I don't see you calling .NET GC anywhere.

  4. sean-marten commented on Oct 13, 2022

    Author

    I don't see you calling .NET GC anywhere.

    Do you mean I am not manually calling collect?

  5. sean-marten commented on Oct 13, 2022

    Author

    @lostmsu Sorry I was typing over from a different computer, I was actually manually calling GC.Collect(); (and results are the same). If that is not what you meant, could you please clarify?

    EDIT: I saw a different issue where you also mentioned someone was not calling .NET GC, and by those comments I assume that you mean that I am not doing some sort of:

    import System
    
    # run some code...
    
    System.GC.Collect()

    ...in my python script. I have tried this out and edited my original python script and the results are still the same.

  6. lostmsu commented on Oct 13, 2022

    Member

    @sean-marten please, doublecheck the output with all the GC calls, then if it reproduces, I can take a look at it.

  7. sean-marten commented on Oct 13, 2022

    Author

    Okay @lostmsu , so I re-ran with the .NET GC collection calls both at the end of the python loop and in the dispose method of the C# library. Also for posterity I still am running the python gc collection at the end of the python loop. I also added a dummy array object to the MemoryLeak constructor class which exacerbates the memory leak as well. I will paste in the updated scripts and a summary of the results:

    import gc
    import sys
    
    import clr
    
    def main():
        sys.path.append("./dlls") # the dll from the above class library is stored here
        clr.AddReference("MemoryLeak")
    
        import System
    
        from MemoryLeak import MemoryLeak
    
        def event_handler():
            pass
    
        for _ in range(200):
            example = MemoryLeak()
            example.LeakEvent += event_handler
            example.LeakEvent -= event_handler
            example.Dispose()
            del example
            gc.collect()
            System.GC.Collect()
    
    if __name__ == "__main__":
        main()
    using System;
    using System.Linq;
    
    namespace MemoryLeak
    {
        public Class MemoryLeak : IDisposable
        {
            public event EventHandler LeakEvent;
            private Array arr;  // dummy array to exacerbate memory leak
    
            public MemoryLeak()
            {
                this.arr = Enumerable.Range(0, 10000).ToArray();
            }
    
            public void Dispose()
            {
                this.LeakEvent = null;
                GC.Collect();
                Console.WriteLine(GC.GetTotalMemory(true));
            }
        }
    }

    When run normally, we see the min and max of the GC.GetTotalMemory method return ~0.6 MB and ~8.6 MB, respectively. If you increase the number of loops in the python script from 200 to let's say 500, that becomes a min and max of ~0.6 MB and ~20.6 MB, respectively. And increasing the loop number only makes it worse, as expected.

    If you remove the python lines regarding registering the event handler from python, the output of GC.GetTotalMemory teeters between ~0.6 MB and ~2.1 MB, but never exceeds the larger value.

    Please let me know if you would like me to do any further diagnosis!

  8. reopened this on Oct 13, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions


      Back | FazBrowse Home | New Git URL