schollii / pypubsub

A Python publish-subcribe library (moved here from SourceForge.net where I had it for many years)
198 stars 29 forks source link

A listener has died #33

Closed sander76 closed 5 years ago

sander76 commented 5 years ago

I am trying to implement the MVC pattern using Python3.7, wxpython (4.0.4) and Pypubsub 4.0.3.

See below a code example. When I run this the following gets printed to the console:

PUBSUB: New topic "pressed" created
PUBSUB: Subscribed listener "TestController.pressed" to topic "pressed"
PUBSUB: Listener "TestController.pressed" of Topic "pressed" has died

Last line shows Listener of Topic "pressed" has died. I don't understand why. Also when I run this through the debugger (pycharm) and put a breakpoint where the listener is created, wait a bit and continue the Listener doesn't die and all works as it should. Any suggestion whether this might be a bug, or am I doing something wrong ?

import sys
import wx
from pubsub import pub
from pubsub.utils import useNotifyByWriteFile

useNotifyByWriteFile(sys.stdout)

class TestModel:
    def do_something(self):
        print("doing something")

class TestView(wx.Window):
    def __init__(self, parent):
        wx.Window.__init__(self, parent, -1)

        sizer = wx.BoxSizer(wx.VERTICAL)
        self.btn = wx.Button(self, -1, "Press")
        sizer.Add(self.btn, 0, wx.EXPAND | wx.ALL)
        self.SetSizer(sizer)

        self.btn.Bind(wx.EVT_BUTTON, self.on_press)

    def on_press(self, evt):
        print("button pressed")
        pub.sendMessage("pressed", val=1)

class TestController:
    def __init__(self, parent):

        self.view = TestView(parent)
        self.model = TestModel()

        pub.subscribe(self.pressed, "pressed")

    def pressed(self, val):
        print("pressed event received")
        self.model.do_something()

class MainFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, title="a test", size=(800, 600))
        sizer = wx.BoxSizer(wx.VERTICAL)

        controller = TestController(self)
        sizer.Add(controller.view, 0, wx.EXPAND)

        self.SetSizer(sizer)

        self.Show()

if __name__ == "__main__":
    app = wx.App()

    start = MainFrame()

    sys.stdout = sys.__stdout__

    app.MainLoop()
schollii commented 5 years ago

Hi sander76, I took a close look. Pypubsub holds only weak reference to the listeners it registers, so that when a listener is no longer used by your app, it can be released from memory (if pubsub stored strong ref, the listener would remain alive and registered even when nothing else in the app referenced it, which can cause mem leak in some cases).

Looking at your code, every thing seems to be holding strong references in the hierarchy leading to pressed listener EXCEPT that controller variable in the Mainframe.__init__ is not being saved as instance variable. This means that at the end of init, controller will be garbaged, and pubsub will automatically release it. The fact that your debugger indicates that it is in fact not dead may be false-positive, as the debugger can hold strong reference in some situations that interfere with normal pubsub lifecycle management.

If changing to self.controller = ... does not help, try clicking on the button multiple times. If you never get the event, or get it only once, then weak referencing is at play. If called every time, then the listener is in fact alive and well and there must be a bug somewhere.

sander76 commented 5 years ago

@schollii Thanks! Your suggestion self.controller = did the trick. Thanks again !