Repository: tornadoweb/tornado

Issue:
websocket demo exception
tornado/demos/websocket/chatdemo.py

```python
chat["html"] = tornado.escape.to_basestring(
            self.render_string("message.html", message=chat))
```
```
[E 170412 12:04:47 websocket:487] Uncaught exception in /chatsocket
    Traceback (most recent call last):
      File "/Users/crane/Projects/tornado/tornado/websocket.py", line 484, in _run_callback
        result = callback(*args, **kwargs)
      File "/Users/crane/Projects/tornado/demos/websocket/chatdemo.py", line 92, in on_message
        self.render_string("message.html", message=chat))
      File "/Users/crane/Projects/tornado/tornado/web.py", line 863, in render_string
        namespace = self.get_template_namespace()
      File "/Users/crane/Projects/tornado/tornado/web.py", line 887, in get_template_namespace
        namespace.update(self.ui)
    TypeError: 'NoneType' object is not iterable
```


Discussion:

Resolving patch:
diff --git a/tornado/web.py b/tornado/web.py
--- a/tornado/web.py
+++ b/tornado/web.py
@@ -993,6 +993,9 @@ def finish(self, chunk=None):
         self._log()
         self._finished = True
         self.on_finish()
+        self._break_cycles()
+
+    def _break_cycles(self):
         # Break up a reference cycle between this handler and the
         # _ui_module closures to allow for faster GC on CPython.
         self.ui = None
diff --git a/tornado/websocket.py b/tornado/websocket.py
--- a/tornado/websocket.py
+++ b/tornado/websocket.py
@@ -434,6 +434,16 @@ def on_connection_close(self):
         if not self._on_close_called:
             self._on_close_called = True
             self.on_close()
+            self._break_cycles()
+
+    def _break_cycles(self):
+        # WebSocketHandlers call finish() early, but we don't want to
+        # break up reference cycles (which makes it impossible to call
+        # self.render_string) until after we've really closed the
+        # connection (if it was established in the first place,
+        # indicated by status code 101).
+        if self.get_status() != 101 or self._on_close_called:
+            super(WebSocketHandler, self)._break_cycles()
 
     def send_error(self, *args, **kwargs):
         if self.stream is None:


Test patch:
diff --git a/tornado/test/websocket_test.py b/tornado/test/websocket_test.py
--- a/tornado/test/websocket_test.py
+++ b/tornado/test/websocket_test.py
@@ -8,6 +8,7 @@
 from tornado import gen
 from tornado.httpclient import HTTPError, HTTPRequest
 from tornado.log import gen_log, app_log
+from tornado.template import DictLoader
 from tornado.testing import AsyncHTTPTestCase, gen_test, bind_unused_port, ExpectLog
 from tornado.test.util import unittest, skipBefore35, exec_test
 from tornado.web import Application, RequestHandler
@@ -130,6 +131,11 @@ def on_message(self, message):
         self.write_message(message)
 
 
+class RenderMessageHandler(TestWebSocketHandler):
+    def on_message(self, message):
+        self.write_message(self.render_string('message.html', message=message))
+
+
 class WebSocketBaseTestCase(AsyncHTTPTestCase):
     @gen.coroutine
     def ws_connect(self, path, **kwargs):
@@ -168,7 +174,15 @@ def get_app(self):
              dict(close_future=self.close_future)),
             ('/coroutine', CoroutineOnMessageHandler,
              dict(close_future=self.close_future)),
-        ])
+            ('/render', RenderMessageHandler,
+             dict(close_future=self.close_future)),
+        ], template_loader=DictLoader({
+            'message.html': '<b>{{ message }}</b>',
+        }))
+
+    def tearDown(self):
+        super(WebSocketTest, self).tearDown()
+        RequestHandler._template_loaders.clear()
 
     def test_http_request(self):
         # WS server, HTTP client.
@@ -219,6 +233,14 @@ def test_unicode_message(self):
         self.assertEqual(response, u'hello \u00e9')
         yield self.close(ws)
 
+    @gen_test
+    def test_render_message(self):
+        ws = yield self.ws_connect('/render')
+        ws.write_message('hello')
+        response = yield ws.read_message()
+        self.assertEqual(response, '<b>hello</b>')
+        yield self.close(ws)
+
     @gen_test
     def test_error_in_on_message(self):
         ws = yield self.ws_connect('/error_in_on_message')