Repository: bokeh/bokeh

Issue:
Tile Sources cannot be shared between app sessions
Bokeh: 0.12.4+
Datashader: 0.4.0+
Browser: Firefox

Running a bokeh server using datashader and streaming (using a periodic callback) works as expected with Safari and Chrome on OS X 10.12.5 but does not work on Firefox. On Firefox the first image loads but then there are no updates. Firefox shows the following error in the console.

```
TypeError: invalid arguments bokeh.min.js:27:12591
	Bokeh</<["models/glyphs/image_rgba"]</r.ImageRGBAView</e.prototype._set_data http://localhost:5006/static/js/bokeh.min.js:27:12591
	Bokeh</<["models/glyphs/glyph"]</r.GlyphView</r.prototype.set_data http://localhost:5006/static/js/bokeh.min.js:27:2575
	Bokeh</<["models/renderers/glyph_renderer"]</r.GlyphRendererView</e.prototype.set_data http://localhost:5006/static/js/bokeh.min.js:31:27491
	Bokeh</<["models/renderers/glyph_renderer"]</r.GlyphRendererView</e.prototype.connect_signals/< http://localhost:5006/static/js/bokeh.min.js:31:26642
	Bokeh</<["core/signaling"]</u</t.prototype.emit http://localhost:5006/static/js/bokeh.min.js:2:26078
	Bokeh</<["models/sources/column_data_source"]</r.ColumnDataSource</e.prototype.stream http://localhost:5006/static/js/bokeh.min.js:32:13704
	Bokeh</<.document</r.Document</t.prototype.apply_json_patch http://localhost:5006/static/js/bokeh.min.js:22:20953
	Bokeh</<.client</i</t.prototype._handle_patch http://localhost:5006/static/js/bokeh.min.js:1:10794
	PATCH-DOC/< http://localhost:5006/static/js/bokeh.min.js:1:2839
	Bokeh</<.client</o</t.prototype._for_session http://localhost:5006/static/js/bokeh.min.js:1:3716
	PATCH-DOC http://localhost:5006/static/js/bokeh.min.js:1:2805
	Bokeh</<.client</o</t.prototype._steady_state_handler http://localhost:5006/static/js/bokeh.min.js:1:9603
	Bokeh</<.client</o</t.prototype._awaiting_ack_handler/this._current_handler</< http://localhost:5006/static/js/bokeh.min.js:1:9262
	Bokeh</<.client</o</t.prototype._on_message_unchecked http://localhost:5006/static/js/bokeh.min.js:1:8228
	Bokeh</<.client</o</t.prototype._on_message http://localhost:5006/static/js/bokeh.min.js:1:7440
	Bokeh</<.client</o</t.prototype.connect/</</t.socket.onmessage http://localhost:5006/static/js/bokeh.min.js:1:446
```
 No error is shown in the terminal.

[Firefox Stream Error.zip](https://github.com/bokeh/bokeh/files/1129670/Firefox.Stream.Error.zip)



Discussion:
@hsparra is this still an issue?
@bryevdv, the plot still does not update in Chrome. The JS console shows the following

```
Uncaught TypeError: Cannot read property 'handle' of null
    at t.r.ClientConnection.t._steady_state_handler (connection.js:286)
    at t._current_handler (connection.js:267)
    at t.r.ClientConnection.t._on_message (connection.js:222)
    at WebSocket.t.socket.onmessage (connection.js:63)
```

The little timer below the plot is updating.
@hsparra did you open FF second,  after another browser? If I make sure its the first browser, it works. Otherwise, I see this in the shell console:
```
2017-10-13 15:51:51,533 Error running application handler <bokeh.application.handlers.script.ScriptHandler object at 0x11341a160>: Models must be owned by only a single document, WMTSTileSource(id='95176d77-9eae-4b0a-960b-7cd9763a146a', ...) is already in a doc
File "model.py", line 553, in _attach_document:
raise RuntimeError("Models must be owned by only a single document, %r is already in a doc" % (self)) Traceback (most recent call last):
  File "/Users/bryan/work/bokeh/bokeh/application/handlers/code_runner.py", line 125, in run
    exec(self._code, module.__dict__)
  File "/Users/bryan/Desktop/taxi-stream_copy.py", line 126, in <module>
    curdoc().add_root(layout)
  File "/Users/bryan/work/bokeh/bokeh/document/document.py", line 243, in add_root
    self._pop_all_models_freeze()
  File "/Users/bryan/work/bokeh/bokeh/document/document.py", line 923, in _pop_all_models_freeze
    self._recompute_all_models()
  File "/Users/bryan/work/bokeh/bokeh/document/document.py", line 946, in _recompute_all_models
    a._attach_document(self)
  File "/Users/bryan/work/bokeh/bokeh/model.py", line 553, in _attach_document
    raise RuntimeError("Models must be owned by only a single document, %r is already in a doc" % (self))
RuntimeError: Models must be owned by only a single document, WMTSTileSource(id='95176d77-9eae-4b0a-960b-7cd9763a146a', ...) is already in a doc
```

The problem is that the built in tile sources always return the *same object*, which is problematic. You cannot reuse the actual same object across sessions. So we probably need to make some way to return new copies instead of just having fixed objects in `tile_providers.py` 

As a workaround, you can just make new tile sources yourself. I.e. copy and paste the code from `tile_providers.py` to create *new, different* tile sources the can be used by each session. 
This has bitten me too.  Should we consider removing the built-in tile sources, i.e. force people to instantiate them before use?
I still get this error in FF.
```
TypeError: this.session is null[Learn More] bokeh.min.js:1:7293
	r.ClientConnection</t.prototype._steady_state_handler http://localhost:5006/static/js/bokeh.min.js:1:7293
	r.ClientConnection</t.prototype._awaiting_ack_handler/this._current_handler</< http://localhost:5006/static/js/bokeh.min.js:1:6977
	r.ClientConnection</t.prototype._on_message http://localhost:5006/static/js/bokeh.min.js:1:5942
	r.ClientConnection</t.prototype.connect/</</t.socket.onmessage
```

I do not load the page in any other browser. The map appears but does not update. The white points do not move around.

Chrome has the same issue, but not Safari. Perhaps the code needs to be changed since bokeh has been updated. I am going to revisit the code.
Safari JS console does show two errors:
```
TypeError: Not enough arguments
removeEventListener — ghostery-min.js:8:9845
(anonymous function) — ghostery-min.js:8:9845
_on_open — connection.js:192
```
and 
```
TypeError: null is not an object (evaluating 'this.session.handle')
_steady_state_handler — connection.js:286
initialize — widget_box.js:109
```
@hsparra it’s working for me in FF if I make sure it’s the very first browser to open the app 

@jbednar I’d prefer to make the effort to implement the class as module pattern  and have properties that return new copies every time 
@hsparra or put another way: an app that uses built on tile sources only works *once* (in any browser)
@hsparra I'm not sure. I ran the code and it worked as is in both Safari and FF (after restarting to deal with tile issue)
At this point I'd say let's leave this issue for the tile object problem and you can open a new issue after you take a new look at the code. 

Resolving patch:
diff --git a/bokeh/palettes.py b/bokeh/palettes.py
--- a/bokeh/palettes.py
+++ b/bokeh/palettes.py
@@ -1224,7 +1224,7 @@ def gray(self, n):
         return self.linear_palette(self.Greys256, n)
 
 # need to explicitly transfer the docstring for Sphinx docs to build correctly
-_mod = _PalettesModule('bokeh.palettes')
+_mod = _PalettesModule(str('bokeh.palettes'))
 _mod.__doc__ = __doc__
 _mod.__all__ = dir(_mod)
 _sys.modules['bokeh.palettes'] = _mod
diff --git a/bokeh/tile_providers.py b/bokeh/tile_providers.py
--- a/bokeh/tile_providers.py
+++ b/bokeh/tile_providers.py
@@ -7,6 +7,58 @@
 #-----------------------------------------------------------------------------
 ''' Pre-configured tile sources for common third party tile services.
 
+
+Attributes:
+
+    CARTODBPOSITRON
+        Tile Source for CartoDB Tile Service
+
+        .. raw:: html
+
+            <img src="http://tiles.basemaps.cartocdn.com/light_all/14/2627/6331.png" />
+
+    CARTODBPOSITRON_RETINA
+        Tile Source for CartoDB Tile Service (tiles at 'retina' resolution)
+
+        .. raw:: html
+
+            <img src="http://tiles.basemaps.cartocdn.com/light_all/14/2627/6331@2x.png" />
+
+    STAMEN_TERRAIN
+        Tile Source for Stamen Terrain Service
+
+        .. raw:: html
+
+            <img src="http://c.tile.stamen.com/terrain/14/2627/6331.png" />
+
+    STAMEN_TERRAIN_RETINA
+        Tile Source for Stamen Terrain Service
+
+        .. raw:: html
+
+            <img src="http://c.tile.stamen.com/terrain/14/2627/6331@2x.png" />
+
+    STAMEN_TONER
+        Tile Source for Stamen Toner Service
+
+        .. raw:: html
+
+            <img src="http://c.tile.stamen.com/toner/14/2627/6331.png" />
+
+    STAMEN_TONER_BACKGROUND
+        Tile Source for Stamen Toner Background Service which does not include labels
+
+        .. raw:: html
+
+            <img src="http://c.tile.stamen.com/toner-background/14/2627/6331.png" />
+
+    STAMEN_TONER_LABELS
+        Tile Source for Stamen Toner Service which includes only labels
+
+        .. raw:: html
+
+            <img src="http://c.tile.stamen.com/toner-labels/14/2627/6331.png" />
+
 Additional information available at:
 
 * Stamen tile service - http://maps.stamen.com/
@@ -29,73 +81,18 @@
 #-----------------------------------------------------------------------------
 
 # Standard library imports
+import sys
+import types
 
 # External imports
 
 # Bokeh imports
-from .models.tiles import WMTSTileSource
 
 #-----------------------------------------------------------------------------
 # Globals and constants
 #-----------------------------------------------------------------------------
 
-__all__ = (
-    'STAMEN_TONER',
-    'STAMEN_TONER_BACKGROUND',
-    'STAMEN_TONER_LABELS',
-    'STAMEN_TERRAIN',
-    'CARTODBPOSITRON',
-    'CARTODBPOSITRON_RETINA',
-)
-
-
-_CARTO_ATTRIBUTION = (
-    '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors,'
-    '&copy; <a href="https://cartodb.com/attributions">CartoDB</a>'
-)
-
-_STAMEN_ATTRIBUTION = (
-    'Map tiles by <a href="http://stamen.com">Stamen Design</a>, '
-    'under <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. '
-    'Data by <a href="http://openstreetmap.org">OpenStreetMap</a>, '
-    'under %s.'
-)
-
-#: Tile Source for Stamen Toner Service
-STAMEN_TONER = WMTSTileSource(
-    url='http://tile.stamen.com/toner/{Z}/{X}/{Y}.png',
-    attribution=_STAMEN_ATTRIBUTION % '<a href="http://www.openstreetmap.org/copyright">ODbL</a>'
-)
-
-#: Tile Source for Stamen Toner Background Service which does not include labels
-STAMEN_TONER_BACKGROUND = WMTSTileSource(
-    url='http://tile.stamen.com/toner-background/{Z}/{X}/{Y}.png',
-    attribution=_STAMEN_ATTRIBUTION % '<a href="http://www.openstreetmap.org/copyright">ODbL</a>'
-)
-
-#: Tile Source for Stamen Toner Service which includes only labels
-STAMEN_TONER_LABELS = WMTSTileSource(
-    url='http://tile.stamen.com/toner-labels/{Z}/{X}/{Y}.png',
-    attribution=_STAMEN_ATTRIBUTION % '<a href="http://www.openstreetmap.org/copyright">ODbL</a>'
-)
-
-#: Tile Source for Stamen Terrain Service
-STAMEN_TERRAIN = WMTSTileSource(
-    url='http://tile.stamen.com/terrain/{Z}/{X}/{Y}.png',
-    attribution=_STAMEN_ATTRIBUTION % '<a href="http://creativecommons.org/licenses/by-sa/3.0">CC BY SA</a>'
-)
-
-#: Tile Source for CartoDB Tile Service
-CARTODBPOSITRON = WMTSTileSource(
-    url='http://tiles.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png',
-    attribution=_CARTO_ATTRIBUTION
-)
-
-#: Tile Source for CartoDB Tile Service (tiles at 'retina' resolution)
-CARTODBPOSITRON_RETINA = WMTSTileSource(
-    url='http://tiles.basemaps.cartocdn.com/light_all/{z}/{x}/{y}@2x.png',
-    attribution=_CARTO_ATTRIBUTION
-)
+# __all__ defined at the bottom on the class module
 
 #-----------------------------------------------------------------------------
 # Public API
@@ -109,6 +106,90 @@
 # Private API
 #-----------------------------------------------------------------------------
 
+class _TileProvidersModule(types.ModuleType):
+
+    _CARTO_ATTRIBUTION = (
+        '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors,'
+        '&copy; <a href="https://cartodb.com/attributions">CartoDB</a>'
+    )
+
+    _STAMEN_ATTRIBUTION = (
+        'Map tiles by <a href="http://stamen.com">Stamen Design</a>, '
+        'under <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. '
+        'Data by <a href="http://openstreetmap.org">OpenStreetMap</a>, '
+        'under %s.'
+    )
+
+    @property
+    def CARTODBPOSITRON(self):
+        from bokeh.models.tiles import WMTSTileSource
+        return WMTSTileSource(
+            url='http://tiles.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png',
+            attribution=self._CARTO_ATTRIBUTION
+        )
+
+    @property
+    def CARTODBPOSITRON_RETINA(self):
+        from bokeh.models.tiles import WMTSTileSource
+        return WMTSTileSource(
+            url='http://tiles.basemaps.cartocdn.com/light_all/{z}/{x}/{y}@2x.png',
+            attribution=self._CARTO_ATTRIBUTION
+        )
+
+    @property
+    def STAMEN_TERRAIN(self):
+        from bokeh.models.tiles import WMTSTileSource
+        return WMTSTileSource(
+            url='http://tile.stamen.com/terrain/{Z}/{X}/{Y}.png',
+            attribution=self._STAMEN_ATTRIBUTION % '<a href="http://creativecommons.org/licenses/by-sa/3.0">CC BY SA</a>'
+        )
+
+    @property
+    def STAMEN_TERRAIN_RETINA(self):
+        from bokeh.models.tiles import WMTSTileSource
+        return WMTSTileSource(
+            url='http://tile.stamen.com/terrain/{Z}/{X}/{Y}@2x.png',
+            attribution=self._STAMEN_ATTRIBUTION % '<a href="http://creativecommons.org/licenses/by-sa/3.0">CC BY SA</a>'
+        )
+
+    @property
+    def STAMEN_TONER(self):
+        from bokeh.models.tiles import WMTSTileSource
+        return WMTSTileSource(
+            url='http://tile.stamen.com/toner/{Z}/{X}/{Y}.png',
+            attribution=self._STAMEN_ATTRIBUTION % '<a href="http://www.openstreetmap.org/copyright">ODbL</a>'
+        )
+
+    @property
+    def STAMEN_TONER_BACKGROUND(self):
+        from bokeh.models.tiles import WMTSTileSource
+        return WMTSTileSource(
+            url='http://tile.stamen.com/toner-background/{Z}/{X}/{Y}.png',
+            attribution=self._STAMEN_ATTRIBUTION % '<a href="http://www.openstreetmap.org/copyright">ODbL</a>'
+        )
+
+    @property
+    def STAMEN_TONER_LABELS(self):
+        from bokeh.models.tiles import WMTSTileSource
+        return WMTSTileSource(
+            url='http://tile.stamen.com/toner-labels/{Z}/{X}/{Y}.png',
+            attribution=self._STAMEN_ATTRIBUTION % '<a href="http://www.openstreetmap.org/copyright">ODbL</a>'
+        )
+
 #-----------------------------------------------------------------------------
 # Code
 #-----------------------------------------------------------------------------
+
+_mod = _TileProvidersModule(str('bokeh.tile_providers'))
+_mod.__doc__ = __doc__
+_mod.__all__ = (
+    'CARTODBPOSITRON',
+    'CARTODBPOSITRON_RETINA',
+    'STAMEN_TERRAIN',
+    'STAMEN_TERRAIN_RETINA',
+    'STAMEN_TONER',
+    'STAMEN_TONER_BACKGROUND',
+    'STAMEN_TONER_LABELS',
+)
+sys.modules['bokeh.tile_providers'] = _mod
+del _mod, sys, types


Test patch:
diff --git a/bokeh/tests/test_tile_providers.py b/bokeh/tests/test_tile_providers.py
--- a/bokeh/tests/test_tile_providers.py
+++ b/bokeh/tests/test_tile_providers.py
@@ -40,12 +40,13 @@
 #-----------------------------------------------------------------------------
 
 ALL = (
+    'CARTODBPOSITRON',
+    'CARTODBPOSITRON_RETINA',
+    'STAMEN_TERRAIN',
+    'STAMEN_TERRAIN_RETINA',
     'STAMEN_TONER',
     'STAMEN_TONER_BACKGROUND',
     'STAMEN_TONER_LABELS',
-    'STAMEN_TERRAIN',
-    'CARTODBPOSITRON',
-    'CARTODBPOSITRON_RETINA',
 )
 
 _CARTO_URLS = {
@@ -54,17 +55,19 @@
 }
 
 _STAMEN_URLS = {
+    'STAMEN_TERRAIN':          'http://tile.stamen.com/terrain/{Z}/{X}/{Y}.png',
+    'STAMEN_TERRAIN_RETINA':   'http://tile.stamen.com/terrain/{Z}/{X}/{Y}@2x.png',
     'STAMEN_TONER':            'http://tile.stamen.com/toner/{Z}/{X}/{Y}.png',
     'STAMEN_TONER_BACKGROUND': 'http://tile.stamen.com/toner-background/{Z}/{X}/{Y}.png',
     'STAMEN_TONER_LABELS':     'http://tile.stamen.com/toner-labels/{Z}/{X}/{Y}.png',
-    'STAMEN_TERRAIN':          'http://tile.stamen.com/terrain/{Z}/{X}/{Y}.png',
 }
 
 _STAMEN_LIC = {
+    'STAMEN_TERRAIN':          '<a href="http://creativecommons.org/licenses/by-sa/3.0">CC BY SA</a>',
+    'STAMEN_TERRAIN_RETINA':   '<a href="http://creativecommons.org/licenses/by-sa/3.0">CC BY SA</a>',
     'STAMEN_TONER':            '<a href="http://www.openstreetmap.org/copyright">ODbL</a>',
     'STAMEN_TONER_BACKGROUND': '<a href="http://www.openstreetmap.org/copyright">ODbL</a>',
     'STAMEN_TONER_LABELS':     '<a href="http://www.openstreetmap.org/copyright">ODbL</a>',
-    'STAMEN_TERRAIN':          '<a href="http://creativecommons.org/licenses/by-sa/3.0">CC BY SA</a>',
 }
 
 #-----------------------------------------------------------------------------
@@ -73,8 +76,7 @@
 
 Test___all__ = verify_all(bt, ALL)
 
-
-@pytest.mark.parametrize('name', ['STAMEN_TONER', 'STAMEN_TONER_BACKGROUND', 'STAMEN_TONER_LABELS', 'STAMEN_TERRAIN'])
+@pytest.mark.parametrize('name', [ 'STAMEN_TERRAIN',  'STAMEN_TERRAIN_RETINA', 'STAMEN_TONER', 'STAMEN_TONER_BACKGROUND', 'STAMEN_TONER_LABELS',])
 class Test_StamenProviders(object):
     def test_type(self, name):
         p = getattr(bt, name)
@@ -93,6 +95,11 @@ def test_attribution(self, name):
             'under %s.'
         ) % _STAMEN_LIC[name]
 
+    def test_copies(self, name):
+        p1 = getattr(bt, name)
+        p2 = getattr(bt, name)
+        assert p1 is not p2
+
 @pytest.mark.parametrize('name', ['CARTODBPOSITRON', 'CARTODBPOSITRON_RETINA'])
 class Test_CartoProviders(object):
     def test_type(self, name):
@@ -110,6 +117,11 @@ def test_attribution(self, name):
             '&copy; <a href="https://cartodb.com/attributions">CartoDB</a>'
         )
 
+    def test_copies(self, name):
+        p1 = getattr(bt, name)
+        p2 = getattr(bt, name)
+        assert p1 is not p2
+
 #-----------------------------------------------------------------------------
 # Internal API
 #-----------------------------------------------------------------------------