Repository: zoofIO/flexx
Pull request #233 (2016-10-13 09:33:01)

Small improvenements to session.close


Patch:
diff --git a/flexx/app/session.py b/flexx/app/session.py
index 98651c9..7236517 100644
--- a/flexx/app/session.py
+++ b/flexx/app/session.py
@@ -307,16 +307,24 @@ class Session(SessionAssets):
         self._runtime = runtime
     
     def close(self):
-        """ Close the runtime, if possible
+        """ Close the session: close websocket, close runtime, dispose app.
         """
-        # todo: close via JS
+        # Stop guarding objects to break down any circular refs
+        for id in list(self._instances_guarded.keys()):
+            self._instances_guarded.pop(id)
         self._closing = True  # suppress warnings for session being closed.
         try:
+            
+            # Close the websocket
+            if self._ws:
+                self._ws.close_this()
+            # Close the runtime
             if self._runtime:
                 self._runtime.close()
+            # Dispose the model and break the circular reference
             if self._model:
                 self._model.dispose()
-                self._model = None  # break circular reference
+                self._model = None
         finally:
             self._closing = False
     
@@ -338,12 +346,12 @@ class Session(SessionAssets):
     def _send_command(self, command):
         """ Send the command, add to pending queue.
         """
-        if self.status == self.STATUS.CONNECTED:
+        if self._closing:
+            pass
+        elif self.status == self.STATUS.CONNECTED:
             self._ws.command(command)
         elif self.status == self.STATUS.PENDING:
             self._pending_commands.append(command)
-        elif self._closing:
-            pass
         else:
             #raise RuntimeError('Cannot send commands; app is closed')
             logger.warn('Cannot send commands; app is closed')


Test patch:
diff --git a/flexx/app/tests/test_model.py b/flexx/app/tests/test_model.py
index 3f3d929..b1f180c 100644
--- a/flexx/app/tests/test_model.py
+++ b/flexx/app/tests/test_model.py
@@ -327,4 +327,60 @@ def test_keep_alive():
     assert foo3_ref() is None
 
 
+def test_keep_alive_noleak1():
+    
+    class Foo:
+        pass
+    
+    # Create a session and an object that has a reference to it (like Model)
+    session = app.Session('test')
+    foo = Foo()
+    foo.session = session
+    
+    # Let the session keep the object alive, so it keeps its reference
+    session.keep_alive(foo)
+    
+    session_ref = weakref.ref(session)
+    foo_ref = weakref.ref(foo)
+    
+    # Removing object wont delete it
+    del foo
+    gc.collect()
+    assert foo_ref() is not None
+    
+    # But closing the session will; session clears up after itself
+    session.close()
+    gc.collect()
+    assert foo_ref() is None
+
+
+def test_keep_alive_noleak2():
+    # Even if the above would not work ...
+    
+    class Foo:
+        pass
+    
+    # Create a session and an object that has a reference to it (like Model)
+    session = app.Session('test')
+    foo = Foo()
+    foo.session = session
+    
+    # Let the session keep the object alive, so it keeps its reference
+    session.keep_alive(foo)
+    
+    session_ref = weakref.ref(session)
+    foo_ref = weakref.ref(foo)
+    
+    # Removing session wont delete it
+    del session
+    gc.collect()
+    assert session_ref() is not None
+    
+    # But removing both will; gc is able to clear circular ref
+    del foo
+    gc.collect()
+    assert session_ref() is None
+    assert foo_ref() is None
+
+
 run_tests_if_main()