Repository: numba/numba

Issue:
TypeError using default arguments with multiprocessing
When using default arguments in a jit'ed function, I get a TypeError when the function is called from a `multiprocessing` worker and the default arguments are omitted. The function works when run single-threaded. Minimal working example:

```python
from multiprocessing import Pool

from numba import jit


@jit
def add1(x):
    return x + 1

@jit
def add2(x, y=1):
    return x + y

if __name__ == "__main__":
    with Pool(2) as p:
        print(p.map(add1, [1, 2, 3]))  # [2, 3, 4] (OK)
        print(add2(1))  # 2 (OK)
        print(p.map(add2, [1, 2, 3]))  # TypeError
```

Output:
```
[2, 3, 4]
2
multiprocessing.pool.RemoteTraceback:
"""
Traceback (most recent call last):
  File "/root/.pyenv/versions/3.7.3/lib/python3.7/multiprocessing/pool.py", line 121, in worker
    result = (True, func(*args, **kwds))
  File "/root/.pyenv/versions/3.7.3/lib/python3.7/multiprocessing/pool.py", line 44, in mapstar
    return list(map(*args))
TypeError: not enough arguments: expected 2, got 1
"""

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "smpe_paper/numba_test.py", line 18, in <module>
    print(p.map(add2, [1, 2, 3]))
  File "/root/.pyenv/versions/3.7.3/lib/python3.7/multiprocessing/pool.py", line 268, in map
    return self._map_async(func, iterable, mapstar, chunksize).get()
  File "/root/.pyenv/versions/3.7.3/lib/python3.7/multiprocessing/pool.py", line 657, in get
    raise self._value
TypeError: not enough arguments: expected 2, got 1
(smpe-paper) root@5b798e37487d:/smpe-paper# python smpe_paper/numba_test.py
[2, 3, 4]
2
multiprocessing.pool.RemoteTraceback:
"""
Traceback (most recent call last):
  File "/root/.pyenv/versions/3.7.3/lib/python3.7/multiprocessing/pool.py", line 121, in worker
    result = (True, func(*args, **kwds))
  File "/root/.pyenv/versions/3.7.3/lib/python3.7/multiprocessing/pool.py", line 44, in mapstar
    return list(map(*args))
TypeError: not enough arguments: expected 2, got 1
"""

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "smpe_paper/numba_test.py", line 18, in <module>
    print(p.map(add2, [1, 2, 3]))  # TypeError
  File "/root/.pyenv/versions/3.7.3/lib/python3.7/multiprocessing/pool.py", line 268, in map
    return self._map_async(func, iterable, mapstar, chunksize).get()
  File "/root/.pyenv/versions/3.7.3/lib/python3.7/multiprocessing/pool.py", line 657, in get
    raise self._value
TypeError: not enough arguments: expected 2, got 1
```

Expected output:
```
[2, 3, 4]
2
[2, 3, 4]
```

Enviroment:
- Python 3.7.4
- Numba 0.46.0
- ubuntu:latest on Docker


Discussion:
I can reproduce this locally.
current hypothesis is that somehow pickling the numba function is losing the default argument?  Need to investigate this further.
Any updates?
@alihalabyah thanks for circling back on this, unfortunately it seems we haven't yet had the time to look into this.
So the exception itself is raised in the dispatcher:

https://github.com/numba/numba/blob/master/numba/_dispatcher.c#L413

Resolving patch:
diff --git a/numba/core/serialize.py b/numba/core/serialize.py
--- a/numba/core/serialize.py
+++ b/numba/core/serialize.py
@@ -66,7 +66,7 @@ def _reduce_function(func, globs):
         cells = [cell.cell_contents for cell in func.__closure__]
     else:
         cells = None
-    return _reduce_code(func.__code__), globs, func.__name__, cells
+    return _reduce_code(func.__code__), globs, func.__name__, cells, func.__defaults__
 
 def _reduce_code(code):
     """
@@ -80,7 +80,7 @@ def _dummy_closure(x):
     """
     return lambda: x
 
-def _rebuild_function(code_reduced, globals, name, cell_values):
+def _rebuild_function(code_reduced, globals, name, cell_values, defaults):
     """
     Rebuild a function from its _reduce_function() results.
     """
@@ -96,7 +96,7 @@ def _rebuild_function(code_reduced, globals, name, cell_values):
         # If the module can't be found, avoid passing it (it would produce
         # errors when lowering).
         del globals['__name__']
-    return FunctionType(code, globals, name, (), cells)
+    return FunctionType(code, globals, name, defaults, cells)
 
 def _rebuild_code(marshal_version, bytecode_magic, marshalled):
     """


Test patch:
diff --git a/numba/tests/test_dispatcher.py b/numba/tests/test_dispatcher.py
--- a/numba/tests/test_dispatcher.py
+++ b/numba/tests/test_dispatcher.py
@@ -15,7 +15,7 @@
 
 import numpy as np
 
-from numba import jit, generated_jit, typeof
+from numba import njit, jit, generated_jit, typeof
 from numba.core import types, errors, codegen
 from numba import _dispatcher
 from numba.core.compiler import compile_isolated
@@ -26,7 +26,8 @@
 from numba.np.numpy_support import as_dtype
 from numba.core.caching import _UserWideCacheLocator
 from numba.core.dispatcher import Dispatcher
-from numba.tests.support import skip_parfors_unsupported, needs_lapack
+from numba.tests.support import (skip_parfors_unsupported, needs_lapack,
+                                 SerialMixin)
 
 import llvmlite.binding as ll
 import unittest
@@ -1965,5 +1966,46 @@ def assign(out, x):
         self.assertEqual(ct_bad, 1)
 
 
+class TestMultiprocessingDefaultParameters(SerialMixin, unittest.TestCase):
+    def run_fc_multiproc(self, fc):
+        try:
+            ctx = multiprocessing.get_context('spawn')
+        except AttributeError:
+            ctx = multiprocessing
+        with ctx.Pool(1) as p:
+            self.assertEqual(p.map(fc, [1, 2, 3]), list(map(fc, [1, 2, 3])))
+
+    def test_int_def_param(self):
+        """ Tests issue #4888"""
+
+        @njit
+        def add(x, y=1):
+            return x + y
+
+        self.run_fc_multiproc(add)
+
+    def test_none_def_param(self):
+        """ Tests None as a default parameter"""
+
+        @njit
+        def add(x, y=None):
+            return x + (1 if y else 2)
+
+        self.run_fc_multiproc(add)
+
+    def test_function_def_param(self):
+        """ Tests a function as a default parameter"""
+
+        @njit
+        def mult(x, y):
+            return x * y
+
+        @njit
+        def add(x, func=mult):
+            return x + func(x, x)
+
+        self.run_fc_multiproc(add)
+
+
 if __name__ == '__main__':
     unittest.main()