mitogen icon indicating copy to clipboard operation
mitogen copied to clipboard

modules using multiprocessing fail with mitogen: PicklingError: Can't pickle <function ...>

Open asheplyakov opened this issue 2 years ago • 0 comments

Here is a simple module (called countmp)


#!/usr/bin/env python3

import multiprocessing as mp

from ansible.module_utils.basic import AnsibleModule


def fmt(x):
    return str(x)


def count(start, end):
    with mp.Pool() as pool:
        return list(pool.imap(fmt, range(start, end + 1)))


def main():
    module = AnsibleModule(
        argument_spec=dict(
            start=dict(type='int', required=False, default=0),
            end=dict(type='int', required=True),
        )
    )
    start = module.params['start']
    end = module.params['end']
    result = {
        'start': start,
        'end': end,
        'result': count(start, end),
        'changed': False,
    }
    module.exit_json(**result)


if __name__ == '__main__':
    main()

It works just fine without mitogen:

$ ansible -m countmp -a 'start=1 end=5' localhost
localhost | SUCCESS => {
    "changed": false,
    "end": 5,
    "result": [
        "1",
        "2",
        "3",
        "4",
        "5"
    ],
    "start": 1
}

However it fails with mitogen:

$ ansible -m countmp -a 'start=1 end=5' localhost
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: _pickle.PicklingError: Can't pickle <function fmt at 0x7f7c62335940>: attribute lookup fmt on __main__ failed
localhost | FAILED! => {
    "changed": false,
    "module_stderr": "Traceback (most recent call last):\n  File \"master:/home/asheplyakov/hacking/mitogen/mitogen/ansible_mitogen/runner.py\", line 975, in _run\n    self._run_code(code, mod)\n  File \"master:/home/asheplyakov/hacking/mitogen/mitogen/ansible_mitogen/runner.py\", line 939, in _run_code\n    exec(code, vars(mod))\n  File \"master:/home/asheplyakov/hacking/mitogen/library/countmp.py\", line 36, in <module>\n  File \"master:/home/asheplyakov/hacking/mitogen/library/countmp.py\", line 29, in main\n  File \"master:/home/asheplyakov/hacking/mitogen/library/countmp.py\", line 14, in count\n  File \"/usr/lib64/python3.9/multiprocessing/pool.py\", line 870, in next\n    raise value\n  File \"/usr/lib64/python3.9/multiprocessing/pool.py\", line 537, in _handle_tasks\n    put(task)\n  File \"/usr/lib64/python3.9/multiprocessing/connection.py\", line 211, in send\n    self._send_bytes(_ForkingPickler.dumps(obj))\n  File \"/usr/lib64/python3.9/multiprocessing/reduction.py\", line 51, in dumps\n    cls(buf, protocol).dump(obj)\n_pickle.PicklingError: Can't pickle <function fmt at 0x7f7c62335940>: attribute lookup fmt on __main__ failed\n",
    "module_stdout": "",
    "msg": "MODULE FAILURE\nSee stdout/stderr for the exact error",
    "rc": 1
}

The problem is that multiprocessing.Pool needs to pickle arguments to send them to worker processes. However pickling works only for functions defined in the __main__ module. Mitogen subtly changes the context of execution, so the fmt function is not defined within the __main__ module any more, hence the error.

$ ansible --version
ansible 2.9.27
  config file = /home/asheplyakov/hacking/mitogen/ansible.cfg
  configured module search path = ['/home/asheplyakov/hacking/mitogen/library']
  ansible python module location = /usr/lib/python3/site-packages/ansible
  executable location = /usr/bin/ansible
  python version = 3.9.6 (default, Jun 29 2021, 10:42:27) [GCC 10.2.1 20210313 (ALT Sisyphus 10.2.1-alt3)]
$ cat ansible.cfg
[defaults]
strategy_plugins = mitogen/ansible_mitogen/plugins/strategy
strategy = mitogen_linear
library = library

asheplyakov avatar Jan 20 '23 14:01 asheplyakov