Repository: joblib/joblib

Issue:
Wrong number of workers reported by effective_n_jobs
I want to highlight something which looks weird to me (not sure yet if this is a bug). I made a snippet which is a pattern that we use in scikit-learn.

```python
from joblib import Parallel, delayed, effective_n_jobs, parallel_backend
from joblib.parallel import get_active_backend


def func(x):
    return x ** 2


class Klass:
    def __init__(self, n_jobs=None):
        self.n_jobs = n_jobs

    def func(self, x):
        n_jobs = effective_n_jobs(self.n_jobs)
        print(
            f"Number of effective jobs found: {effective_n_jobs(self.n_jobs)}"
        )
        backend, backend_n_jobs = get_active_backend()
        print(
            f"Current backend used: {backend}"
        )
        print(
            f"Number of jobs in the backend: {backend_n_jobs}"
        )
        return Parallel(n_jobs=n_jobs)(delayed(func)(i) for i in x)


n_jobs = 4
with parallel_backend("threading", n_jobs=n_jobs):
    r = Klass().func(range(10))
```

```
Number of effective jobs found: 1
Current backend used: <joblib._parallel_backends.ThreadingBackend object at 0x7f946468d810>
Number of jobs in the backend: 4
```

If I am not wrong, one would expect to make the `Parallel` call with `n_jobs=4`. However, `effective_n_jobs` will report `n_jobs=1` due to:

https://github.com/joblib/joblib/blob/1753c3bcb1d8430439ce48a1ef4e866166f34296/joblib/_parallel_backends.py#L232-L235

I would have expected `parallel_backend` to overwrite the number of jobs if not specified in the estimator but I am not sure about it.

NB: found during investigating https://github.com/scikit-learn/scikit-learn/issues/15978

Discussion:
glemaitre: Uhm some of the errors are cryptic. Any idea what is the impact of this PR on the `hash`?
tomMoral: I don't know what are these failures. It does not look like it is related to this PR.
codecov[bot]: # [Codecov](https://codecov.io/gh/joblib/joblib/pull/985?src=pr&el=h1) Report
> Merging [#985](https://codecov.io/gh/joblib/joblib/pull/985?src=pr&el=desc) into [master](https://codecov.io/gh/joblib/joblib/commit/1753c3bcb1d8430439ce48a1ef4e866166f34296?src=pr&el=desc) will **decrease** coverage by `3.14%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/joblib/joblib/pull/985/graphs/tree.svg?width=650&token=gA6LF5DGTW&height=150&src=pr)](https://codecov.io/gh/joblib/joblib/pull/985?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #985      +/-   ##
==========================================
- Coverage    95.4%   92.25%   -3.15%     
==========================================
  Files          45       45              
  Lines        6634     6640       +6     
==========================================
- Hits         6329     6126     -203     
- Misses        305      514     +209
```


| [Impacted Files](https://codecov.io/gh/joblib/joblib/pull/985?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [joblib/test/test\_parallel.py](https://codecov.io/gh/joblib/joblib/pull/985/diff?src=pr&el=tree#diff-am9ibGliL3Rlc3QvdGVzdF9wYXJhbGxlbC5weQ==) | `94% <100%> (-2.75%)` | :arrow_down: |
| [joblib/parallel.py](https://codecov.io/gh/joblib/joblib/pull/985/diff?src=pr&el=tree#diff-am9ibGliL3BhcmFsbGVsLnB5) | `96.42% <100%> (-0.81%)` | :arrow_down: |
| [joblib/test/common.py](https://codecov.io/gh/joblib/joblib/pull/985/diff?src=pr&el=tree#diff-am9ibGliL3Rlc3QvY29tbW9uLnB5) | `64.4% <0%> (-23.73%)` | :arrow_down: |
| [joblib/backports.py](https://codecov.io/gh/joblib/joblib/pull/985/diff?src=pr&el=tree#diff-am9ibGliL2JhY2twb3J0cy5weQ==) | `79.16% <0%> (-14.59%)` | :arrow_down: |
| [joblib/test/test\_hashing.py](https://codecov.io/gh/joblib/joblib/pull/985/diff?src=pr&el=tree#diff-am9ibGliL3Rlc3QvdGVzdF9oYXNoaW5nLnB5) | `86.17% <0%> (-12.77%)` | :arrow_down: |
| [joblib/pool.py](https://codecov.io/gh/joblib/joblib/pull/985/diff?src=pr&el=tree#diff-am9ibGliL3Bvb2wucHk=) | `80.17% <0%> (-11.21%)` | :arrow_down: |
| [joblib/test/test\_memmapping.py](https://codecov.io/gh/joblib/joblib/pull/985/diff?src=pr&el=tree#diff-am9ibGliL3Rlc3QvdGVzdF9tZW1tYXBwaW5nLnB5) | `88.49% <0%> (-10.69%)` | :arrow_down: |
| [joblib/testing.py](https://codecov.io/gh/joblib/joblib/pull/985/diff?src=pr&el=tree#diff-am9ibGliL3Rlc3RpbmcucHk=) | `87.5% <0%> (-7.5%)` | :arrow_down: |
| [joblib/\_memmapping\_reducer.py](https://codecov.io/gh/joblib/joblib/pull/985/diff?src=pr&el=tree#diff-am9ibGliL19tZW1tYXBwaW5nX3JlZHVjZXIucHk=) | `87.77% <0%> (-7.23%)` | :arrow_down: |
| [joblib/disk.py](https://codecov.io/gh/joblib/joblib/pull/985/diff?src=pr&el=tree#diff-am9ibGliL2Rpc2sucHk=) | `76.66% <0%> (-5%)` | :arrow_down: |
| ... and [13 more](https://codecov.io/gh/joblib/joblib/pull/985/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/joblib/joblib/pull/985?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/joblib/joblib/pull/985?src=pr&el=footer). Last update [1753c3b...e95e419](https://codecov.io/gh/joblib/joblib/pull/985?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

glemaitre: @ogrisel any idea about the other failure in Travis?
ogrisel: I am having a look in this PR: https://github.com/joblib/joblib/pull/986

I have even weirder failures on my local machine...
tomMoral: This seems to be due to the new release of coverage: https://github.com/pymc-devs/pymc3/issues/3735

pinning coverage to `coverage<5.0` did the trick for `pymc3`.
glemaitre: I pinned coverage. I think that the original issue could be the following: https://github.com/nedbat/coveragepy/issues/883
glemaitre: Fixed conflict. This should be fine now.
ogrisel: The new test need to be decorated with `@with_multiprocessing`.
ogrisel: Also please document the change in the changelog.
glemaitre: Failure is only a timeout.
ogrisel: Changelog?

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index cc0f542..fdfe8d2 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,6 +1,13 @@
 Latest changes
 ==============
 
+Release 0.14.2
+--------------
+
+- Fix the number of jobs reported by ``effective_n_jobs`` when ``n_jobs=None``
+  called in a parallel backend context.
+  https://github.com/joblib/joblib/pull/985
+
 Release 0.14.1
 --------------
 
diff --git a/continuous_integration/travis/install.sh b/continuous_integration/travis/install.sh
index 96ddb91..a45c0be 100755
--- a/continuous_integration/travis/install.sh
+++ b/continuous_integration/travis/install.sh
@@ -91,6 +91,7 @@ if [ -n "$NUMPY_VERSION" ]; then
 fi
 
 if [[ "$COVERAGE" == "true" ]]; then
+    # TODO: unpin when https://github.com/nedbat/coveragepy/issues/883 is fixed
     # Weird issues with recent version of coverage: unpin when not causing
     # pytest to raise INTERNALERROR exceptions.
     PIP_INSTALL_PACKAGES="$PIP_INSTALL_PACKAGES coverage==4.5.4 pytest-cov codecov"
diff --git a/joblib/parallel.py b/joblib/parallel.py
index b673425..16b0b89 100644
--- a/joblib/parallel.py
+++ b/joblib/parallel.py
@@ -385,7 +385,9 @@ def effective_n_jobs(n_jobs=-1):
     .. versionadded:: 0.10
 
     """
-    backend, _ = get_active_backend()
+    backend, backend_n_jobs = get_active_backend()
+    if n_jobs is None:
+        n_jobs = backend_n_jobs
     return backend.effective_n_jobs(n_jobs=n_jobs)
 
 


Test patch:
diff --git a/joblib/test/test_parallel.py b/joblib/test/test_parallel.py
index 317ce4f..a7bec44 100644
--- a/joblib/test/test_parallel.py
+++ b/joblib/test/test_parallel.py
@@ -152,6 +152,23 @@ def test_effective_n_jobs():
     assert effective_n_jobs() > 0
 
 
+@pytest.mark.parametrize(
+    "backend_n_jobs, expected_n_jobs",
+    [(3, 3), (-1, effective_n_jobs(n_jobs=-1)), (None, 1)],
+    ids=["positive-int", "negative-int", "None"]
+)
+@with_multiprocessing
+def test_effective_n_jobs_None(backend_n_jobs, expected_n_jobs):
+    # check the number of effective jobs when `n_jobs=None`
+    # non-regression test for https://github.com/joblib/joblib/issues/984
+    with parallel_backend("threading", n_jobs=backend_n_jobs):
+        # when using a backend, the default of number jobs will be the one set
+        # in the backend
+        assert effective_n_jobs(n_jobs=None) == expected_n_jobs
+    # without any backend, None will default to a single job
+    assert effective_n_jobs(n_jobs=None) == 1
+
+
 ###############################################################################
 # Test parallel