Repository: holoviz/panel

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.


Discussion:
For additional context, here is an example of a utility that pulls up a map centered around a coordinate, waits for 4 points to be selected on the map, then continues on with whatever comes next in the pipeline. 

Inputs
```
lon = -121.8144
lat = 48.7767
```

Function
```
def corner_coordinate_picker(lon, 
                             lat, 
                             dx  = 1, 
                             dy  = 1, 
                             url = 'https://mt1.google.com/vt/lyrs=s&x={X}&y={Y}&z={Z}'):
    # get extent
    extents = lon-dx, lat-dy, lon+dx, lat+dy
    
    # pull in the basemap
    tiles = gv.WMTS(url, extents=extents, crs=ccrs.PlateCarree())
    
    # create the camera center object
    location = gv.Points([(lon,lat,'name')], vdims="location")
    
    # initialize the corner coordinate picker
    point_stream = PointDraw(source=location)
    
    # create the basemap with camera center point on it
    base_map = (tiles * location).opts(opts.Points(width=500, 
                                                     height=500, 
                                                     size=12, 
                                                     color='black', 
                                                     tools=["hover"]))
    app = pn.panel(base_map)
    # run the server
    server = app.show(port = 8889, threaded=True)
    
    # return the server object
    return point_stream, server

```


Run
```
point_stream, server = corner_coordinate_picker(lon, lat)

# wait for the app to spin up, else while loop will hit a NoneType
time.sleep(1) 

condition = True
while condition == True: 
    if len(point_stream.data['x']) == 5:
        server.stop()
        condition = False
```

Issue
`server.stop()` will stop the server, as verified by `server.isAlive() == False` and `server.stopped == True`, however port 8889 is still being held on to by the PID that belongs to the python kernel running the script or notebook. The port is only released after the PID is killed or finished. I can get around this by running the app with a subprocess, but would be easier if server.stop() also releases the port. The fact that this isn't happening makes it seem like a bug.

Other
Threading is necessary, else the app won't start, as written.
I am moving the issue over to panel for tracking, but I don't have any immediate insight. Thanks for raising it though.

Resolving patch:
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 @@ def _check_stopped(self):
         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()


Test patch: