Repository: ReactiveX/RxPY

Issue:
from_future and asyncio.CancelledError
The [`from_future`](https://github.com/ReactiveX/RxPY/blob/master/rx/core/observable/fromfuture.py) operator no longer forwards `asyncio.CancelledError`s. This is due to a [change](https://bugs.python.org/issue32528) in Python 3.8 that makes `asyncio.CancelledError` inherit from `BaseException` (it inherited from `Exception` in previous releases).

The [current implementation](https://github.com/ReactiveX/RxPY/blob/master/rx/core/observable/fromfuture.py#L29) of `from_future` only forwards `Exception`s:
```
        def done(future):
            try:
                value = future.result()
            except Exception as ex:  # pylint: disable=broad-except
                observer.on_error(ex)
            else:
                observer.on_next(value)
                observer.on_completed()
```

I suggest that we either:
 * Broaden the exception handler even more, so that `BaseException`s are forwarded.
 * Make a specialized version of `from_future` (say `from_coro`) that forwards the relevant cancellation exceptions. I think that the built-in `asyncio.CancelledError` should be supported by default.
 * Add argument to `from_future` that specifies the exception types, that we want to forward. E.g., `ops.from_future(fut, exceptions=(Exception, asyncio.CancelledError))`.

---

I got hit by this issue in practice. It gave me a short and non-descriptive stack trace:
```
<27>1 2020-02-10T11:43:55.556901+01:00 zeus1934001 baxter 5680 - - [geist.geist] Stopped due to error:
Traceback (most recent call last):
  File "/usr/lib/python3.8/site-packages/baxter/__main__.py", line 40, in main
    geist.run(_amain())
  File "/usr/lib/python3.8/site-packages/geist/geist.py", line 40, in run
    self.loop.run_forever()
  File "uvloop/loop.pyx", line 1312, in uvloop.loop.Loop.run_forever
  File "uvloop/loop.pyx", line 492, in uvloop.loop.Loop._run
  File "uvloop/loop.pyx", line 409, in uvloop.loop.Loop._on_idle
  File "uvloop/cbhandles.pyx", line 70, in uvloop.loop.Handle._run
  File "usr/lib/python3.8/site-packages/rx/core/observable/fromfuture.py", line 28, in done
asyncio.exceptions.CancelledError
```
There are no indications of which task got cancelled. This makes it very difficult to debug.

Discussion:

Patch:
diff --git a/.travis.yml b/.travis.yml
index e10f1846..2131a27f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -23,11 +23,11 @@ matrix:
         - xvfb-run -a python3 setup.py test
 
 
-    - name: "Python 3.8-dev on Linux"
+    - name: "Python 3.8 on Linux"
       os: linux
       dist: xenial
       language: python
-      python: 3.8-dev
+      python: 3.8
       service:
         - xvfb
       script:
diff --git a/rx/core/observable/fromfuture.py b/rx/core/observable/fromfuture.py
index fa83acbc..70ab7670 100644
--- a/rx/core/observable/fromfuture.py
+++ b/rx/core/observable/fromfuture.py
@@ -1,3 +1,4 @@
+import asyncio
 from asyncio.futures import Future
 from typing import Optional
 
@@ -26,7 +27,7 @@ def _from_future(future: Future) -> Observable:
         def done(future):
             try:
                 value = future.result()
-            except Exception as ex:  # pylint: disable=broad-except
+            except (Exception, asyncio.CancelledError) as ex:  # pylint: disable=broad-except
                 observer.on_error(ex)
             else:
                 observer.on_next(value)
diff --git a/rx/operators/__init__.py b/rx/operators/__init__.py
index c4d5afbe..c2373d76 100644
--- a/rx/operators/__init__.py
+++ b/rx/operators/__init__.py
@@ -870,7 +870,7 @@ def expand(mapper: Mapper) -> Callable[[Observable], Observable]:
 
 def filter(predicate: Predicate) -> Callable[[Observable], Observable]:
     """Filters the elements of an observable sequence based on a
-    predicate by incorporating the element's index.
+    predicate.
 
     .. marble::
         :alt: filter
@@ -2019,7 +2019,7 @@ def repeat(repeat_count: Optional[int] = None) -> Callable[[Observable], Observa
 
     Returns:
         An operator function that takes an observable sources and
-        returna an observable sequence producing the elements of the
+        returns an observable sequence producing the elements of the
         given sequence repeatedly.
     """
     from rx.core.operators.repeat import _repeat


Test patch:
diff --git a/tests/test_observable/test_fromfuture.py b/tests/test_observable/test_fromfuture.py
index 17493fcf..f4ac2eb7 100644
--- a/tests/test_observable/test_fromfuture.py
+++ b/tests/test_observable/test_fromfuture.py
@@ -59,6 +59,30 @@ class TestFromFuture(unittest.TestCase):
         loop.run_until_complete(go())
         assert all(success)
 
+    def test_future_cancel(self):
+        loop = asyncio.get_event_loop()
+        success = [True, False, True]
+
+        @asyncio.coroutine
+        def go():
+            future = Future()
+            source = rx.from_future(future)
+
+            def on_next(x):
+                success[0] = False
+
+            def on_error(err):
+                success[1] = type(err) == asyncio.CancelledError
+
+            def on_completed():
+                success[2] = False
+
+            source.subscribe(on_next, on_error, on_completed)
+            future.cancel()
+
+        loop.run_until_complete(go())
+        assert all(success)
+
     def test_future_dispose(self):
         loop = asyncio.get_event_loop()
         success = [True, True, True]