prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Port not being released after stopping threaded holoviz panel app server
  Closing a threaded panel app holds on to the port it started on. This is different behavior than closing an app initialized without threading.
  
  ```
  usgs_logo = pn.panel('../assets/usgs_logo.png', height=130)
  
  column = pn.Column(usgs_logo)
  
  app = column.show(port=8889)
  
  app.stop()
  ```
  
  Port 8889 is released.
  
  ```
  app = row.show(port=8889, threaded=True)
  
  app.stop()
  ```
  Port 8889 is not released.
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `panel/io/server.py`
  Content:
  ```
  1 """
  2 Utilities for creating bokeh Server instances.
  3 """
  4 from __future__ import absolute_import, division, unicode_literals
  5 
  6 import signal
  7 import threading
  8 
  9 from functools import partial
  10 
  11 from bokeh.server.server import Server
  12 
  13 from .state import state
  14 
  15 
  16 #---------------------------------------------------------------------
  17 # Private API
  18 #---------------------------------------------------------------------
  19 
  20 def _origin_url(url):
  21     if url.startswith("http"):
  22         url = url.split("//")[1]
  23     return url
  24 
  25 
  26 def _server_url(url, port):
  27     if url.startswith("http"):
  28         return '%s:%d%s' % (url.rsplit(':', 1)[0], port, "/")
  29     else:
  30         return 'http://%s:%d%s' % (url.split(':')[0], port, "/")
  31 
  32 #---------------------------------------------------------------------
  33 # Public API
  34 #---------------------------------------------------------------------
  35 
  36 def get_server(panel, port=0, websocket_origin=None, loop=None,
  37                show=False, start=False, **kwargs):
  38     """
  39     Returns a Server instance with this panel attached as the root
  40     app.
  41 
  42     Arguments
  43     ---------
  44     port: int (optional, default=0)
  45       Allows specifying a specific port
  46     websocket_origin: str or list(str) (optional)
  47       A list of hosts that can connect to the websocket.
  48 
  49       This is typically required when embedding a server app in
  50       an external web site.
  51 
  52       If None, "localhost" is used.
  53     loop : tornado.ioloop.IOLoop (optional, default=IOLoop.current())
  54       The tornado IOLoop to run the Server on
  55     show : boolean (optional, default=False)
  56       Whether to open the server in a new browser tab on start
  57     start : boolean(optional, default=False)
  58       Whether to start the Server
  59     kwargs: dict
  60       Additional keyword arguments to pass to Server instance
  61 
  62     Returns
  63     -------
  64     server : bokeh.server.server.Server
  65       Bokeh Server instance running this panel
  66     """
  67     from tornado.ioloop import IOLoop
  68     opts = dict(kwargs)
  69     if loop:
  70         loop.make_current()
  71         opts['io_loop'] = loop
  72     else:
  73         opts['io_loop'] = IOLoop.current()
  74 
  75     if websocket_origin:
  76         if not isinstance(websocket_origin, list):
  77             websocket_origin = [websocket_origin]
  78         opts['allow_websocket_origin'] = websocket_origin
  79 
  80     server_id = kwargs.pop('server_id', None)
  81     server = Server({'/': partial(panel._modify_doc, server_id)}, port=port, **opts)
  82     if server_id:
  83         state._servers[server_id] = (server, panel, [])
  84 
  85     if show:
  86         def show_callback():
  87             server.show('/')
  88         server.io_loop.add_callback(show_callback)
  89 
  90     def sig_exit(*args, **kwargs):
  91         server.io_loop.add_callback_from_signal(do_stop)
  92 
  93     def do_stop(*args, **kwargs):
  94         server.io_loop.stop()
  95 
  96     try:
  97         signal.signal(signal.SIGINT, sig_exit)
  98     except ValueError:
  99         pass # Can't use signal on a thread
  100 
  101     if start:
  102         server.start()
  103         try:
  104             server.io_loop.start()
  105         except RuntimeError:
  106             pass
  107     return server
  108 
  109 
  110 class StoppableThread(threading.Thread):
  111     """Thread class with a stop() method."""
  112 
  113     def __init__(self, io_loop=None, timeout=1000, **kwargs):
  114         from tornado import ioloop
  115         super(StoppableThread, self).__init__(**kwargs)
  116         self._stop_event = threading.Event()
  117         self.io_loop = io_loop
  118         self._cb = ioloop.PeriodicCallback(self._check_stopped, timeout)
  119         self._cb.start()
  120 
  121     def _check_stopped(self):
  122         if self.stopped:
  123             self._cb.stop()
  124             self.io_loop.stop()
  125 
  126     def stop(self):
  127         self._stop_event.set()
  128 
  129     @property
  130     def stopped(self):
  131         return self._stop_event.is_set()
  132
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/panel/io/server.py b/panel/io/server.py
  --- a/panel/io/server.py
  +++ b/panel/io/server.py
  @@ -122,6 +122,15 @@
           if self.stopped:
               self._cb.stop()
               self.io_loop.stop()
  +    
  +    def run(self):
  +        try:
  +            if self._target:
  +                bokeh_server = self._target(*self._args, **self._kwargs)
  +        finally:
  +            if isinstance(bokeh_server, Server):
  +                bokeh_server.stop()
  +            del self._target, self._args, self._kwargs
   
       def stop(self):
           self._stop_event.set()
  
verification_info: {"golden_diff": "diff --git a/panel/io/server.py b/panel/io/server.py\n--- a/panel/io/server.py\n+++ b/panel/io/server.py\n@@ -122,6 +122,15 @@\n         if self.stopped:\n             self._cb.stop()\n             self.io_loop.stop()\n+    \n+    def run(self):\n+        try:\n+            if self._target:\n+                bokeh_server = self._target(*self._args, **self._kwargs)\n+        finally:\n+            if isinstance(bokeh_server, Server):\n+                bokeh_server.stop()\n+            del self._target, self._args, self._kwargs\n \n     def stop(self):\n         self._stop_event.set()\n", "issue": "Port not being released after stopping threaded holoviz panel app server\nClosing a threaded panel app holds on to the port it started on. This is different behavior than closing an app initialized without threading.\r\n\r\n```\r\nusgs_logo = pn.panel('../assets/usgs_logo.png', height=130)\r\n\r\ncolumn = pn.Column(usgs_logo)\r\n\r\napp = column.show(port=8889)\r\n\r\napp.stop()\r\n```\r\n\r\nPort 8889 is released.\r\n\r\n```\r\napp = row.show(port=8889, threaded=True)\r\n\r\napp.stop()\r\n```\r\nPort 8889 is not released.\n", "before_files": [{"content": "\"\"\"\nUtilities for creating bokeh Server instances.\n\"\"\"\nfrom __future__ import absolute_import, division, unicode_literals\n\nimport signal\nimport threading\n\nfrom functools import partial\n\nfrom bokeh.server.server import Server\n\nfrom .state import state\n\n\n#---------------------------------------------------------------------\n# Private API\n#---------------------------------------------------------------------\n\ndef _origin_url(url):\n    if url.startswith(\"http\"):\n        url = url.split(\"//\")[1]\n    return url\n\n\ndef _server_url(url, port):\n    if url.startswith(\"http\"):\n        return '%s:%d%s' % (url.rsplit(':', 1)[0], port, \"/\")\n    else:\n        return 'http://%s:%d%s' % (url.split(':')[0], port, \"/\")\n\n#---------------------------------------------------------------------\n# Public API\n#---------------------------------------------------------------------\n\ndef get_server(panel, port=0, websocket_origin=None, loop=None,\n               show=False, start=False, **kwargs):\n    \"\"\"\n    Returns a Server instance with this panel attached as the root\n    app.\n\n    Arguments\n    ---------\n    port: int (optional, default=0)\n      Allows specifying a specific port\n    websocket_origin: str or list(str) (optional)\n      A list of hosts that can connect to the websocket.\n\n      This is typically required when embedding a server app in\n      an external web site.\n\n      If None, \"localhost\" is used.\n    loop : tornado.ioloop.IOLoop (optional, default=IOLoop.current())\n      The tornado IOLoop to run the Server on\n    show : boolean (optional, default=False)\n      Whether to open the server in a new browser tab on start\n    start : boolean(optional, default=False)\n      Whether to start the Server\n    kwargs: dict\n      Additional keyword arguments to pass to Server instance\n\n    Returns\n    -------\n    server : bokeh.server.server.Server\n      Bokeh Server instance running this panel\n    \"\"\"\n    from tornado.ioloop import IOLoop\n    opts = dict(kwargs)\n    if loop:\n        loop.make_current()\n        opts['io_loop'] = loop\n    else:\n        opts['io_loop'] = IOLoop.current()\n\n    if websocket_origin:\n        if not isinstance(websocket_origin, list):\n            websocket_origin = [websocket_origin]\n        opts['allow_websocket_origin'] = websocket_origin\n\n    server_id = kwargs.pop('server_id', None)\n    server = Server({'/': partial(panel._modify_doc, server_id)}, port=port, **opts)\n    if server_id:\n        state._servers[server_id] = (server, panel, [])\n\n    if show:\n        def show_callback():\n            server.show('/')\n        server.io_loop.add_callback(show_callback)\n\n    def sig_exit(*args, **kwargs):\n        server.io_loop.add_callback_from_signal(do_stop)\n\n    def do_stop(*args, **kwargs):\n        server.io_loop.stop()\n\n    try:\n        signal.signal(signal.SIGINT, sig_exit)\n    except ValueError:\n        pass # Can't use signal on a thread\n\n    if start:\n        server.start()\n        try:\n            server.io_loop.start()\n        except RuntimeError:\n            pass\n    return server\n\n\nclass StoppableThread(threading.Thread):\n    \"\"\"Thread class with a stop() method.\"\"\"\n\n    def __init__(self, io_loop=None, timeout=1000, **kwargs):\n        from tornado import ioloop\n        super(StoppableThread, self).__init__(**kwargs)\n        self._stop_event = threading.Event()\n        self.io_loop = io_loop\n        self._cb = ioloop.PeriodicCallback(self._check_stopped, timeout)\n        self._cb.start()\n\n    def _check_stopped(self):\n        if self.stopped:\n            self._cb.stop()\n            self.io_loop.stop()\n\n    def stop(self):\n        self._stop_event.set()\n\n    @property\n    def stopped(self):\n        return self._stop_event.is_set()\n", "path": "panel/io/server.py"}], "after_files": [{"content": "\"\"\"\nUtilities for creating bokeh Server instances.\n\"\"\"\nfrom __future__ import absolute_import, division, unicode_literals\n\nimport signal\nimport threading\n\nfrom functools import partial\n\nfrom bokeh.server.server import Server\n\nfrom .state import state\n\n\n#---------------------------------------------------------------------\n# Private API\n#---------------------------------------------------------------------\n\ndef _origin_url(url):\n    if url.startswith(\"http\"):\n        url = url.split(\"//\")[1]\n    return url\n\n\ndef _server_url(url, port):\n    if url.startswith(\"http\"):\n        return '%s:%d%s' % (url.rsplit(':', 1)[0], port, \"/\")\n    else:\n        return 'http://%s:%d%s' % (url.split(':')[0], port, \"/\")\n\n#---------------------------------------------------------------------\n# Public API\n#---------------------------------------------------------------------\n\ndef get_server(panel, port=0, websocket_origin=None, loop=None,\n               show=False, start=False, **kwargs):\n    \"\"\"\n    Returns a Server instance with this panel attached as the root\n    app.\n\n    Arguments\n    ---------\n    port: int (optional, default=0)\n      Allows specifying a specific port\n    websocket_origin: str or list(str) (optional)\n      A list of hosts that can connect to the websocket.\n\n      This is typically required when embedding a server app in\n      an external web site.\n\n      If None, \"localhost\" is used.\n    loop : tornado.ioloop.IOLoop (optional, default=IOLoop.current())\n      The tornado IOLoop to run the Server on\n    show : boolean (optional, default=False)\n      Whether to open the server in a new browser tab on start\n    start : boolean(optional, default=False)\n      Whether to start the Server\n    kwargs: dict\n      Additional keyword arguments to pass to Server instance\n\n    Returns\n    -------\n    server : bokeh.server.server.Server\n      Bokeh Server instance running this panel\n    \"\"\"\n    from tornado.ioloop import IOLoop\n    opts = dict(kwargs)\n    if loop:\n        loop.make_current()\n        opts['io_loop'] = loop\n    else:\n        opts['io_loop'] = IOLoop.current()\n\n    if websocket_origin:\n        if not isinstance(websocket_origin, list):\n            websocket_origin = [websocket_origin]\n        opts['allow_websocket_origin'] = websocket_origin\n\n    server_id = kwargs.pop('server_id', None)\n    server = Server({'/': partial(panel._modify_doc, server_id)}, port=port, **opts)\n    if server_id:\n        state._servers[server_id] = (server, panel, [])\n\n    if show:\n        def show_callback():\n            server.show('/')\n        server.io_loop.add_callback(show_callback)\n\n    def sig_exit(*args, **kwargs):\n        server.io_loop.add_callback_from_signal(do_stop)\n\n    def do_stop(*args, **kwargs):\n        server.io_loop.stop()\n\n    try:\n        signal.signal(signal.SIGINT, sig_exit)\n    except ValueError:\n        pass # Can't use signal on a thread\n\n    if start:\n        server.start()\n        try:\n            server.io_loop.start()\n        except RuntimeError:\n            pass\n    return server\n\n\nclass StoppableThread(threading.Thread):\n    \"\"\"Thread class with a stop() method.\"\"\"\n\n    def __init__(self, io_loop=None, timeout=1000, **kwargs):\n        from tornado import ioloop\n        super(StoppableThread, self).__init__(**kwargs)\n        self._stop_event = threading.Event()\n        self.io_loop = io_loop\n        self._cb = ioloop.PeriodicCallback(self._check_stopped, timeout)\n        self._cb.start()\n\n    def _check_stopped(self):\n        if self.stopped:\n            self._cb.stop()\n            self.io_loop.stop()\n    \n    def run(self):\n        try:\n            if self._target:\n                bokeh_server = self._target(*self._args, **self._kwargs)\n        finally:\n            if isinstance(bokeh_server, Server):\n                bokeh_server.stop()\n            del self._target, self._args, self._kwargs\n\n    def stop(self):\n        self._stop_event.set()\n\n    @property\n    def stopped(self):\n        return self._stop_event.is_set()\n", "path": "panel/io/server.py"}]}