Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AttributeError: Can't pickle local object 'register.<locals>._handler' sanic 19.12.2; python3.8.2; sanic-cors==0.10.0.b1 #1774

Closed
endafarrell opened this issue Jan 28, 2020 · 12 comments · Fixed by #1842

Comments

@endafarrell
Copy link
Contributor

endafarrell commented Jan 28, 2020

Hi.

On OSX 10.14.6, python 3.8.1

conda list sanic                                                                                                
# packages in environment at /Users/efarrell/miniconda3/envs/LG-py38:
#
# Name                    Version                   Build  Channel
pytest-sanic              1.1.2                    pypi_0    pypi
sanic                     19.12.2                  py38_0    conda-forge
sanic-cors                0.10.0b1                 pypi_0    pypi
sanic-openapi3e           0.6.2                    pypi_0    pypi
sanic-plugins-framework   0.9.0b1                  pypi_0    pypi

I cannot start an API due to

Traceback (most recent call last):
  File "API/src/main.py", line 613, in <module>
    main(sys.argv)
  File "API/src/main.py", line 195, in main
    app.go_fast(**sanic_run_env)
  File "/Users/efarrell/miniconda3/envs/py38/lib/python3.8/site-packages/sanic/app.py", line 1169, in run
    serve_multiple(server_settings, workers)
  File "/Users/efarrell/miniconda3/envs/py38/lib/python3.8/site-packages/sanic/server.py", line 997, in serve_multiple
    process.start()
  File "/Users/efarrell/miniconda3/envs/py38/lib/python3.8/multiprocessing/process.py", line 121, in start
    self._popen = self._Popen(self)
  File "/Users/efarrell/miniconda3/envs/py38/lib/python3.8/multiprocessing/context.py", line 224, in _Popen
    return _default_context.get_context().Process._Popen(process_obj)
  File "/Users/efarrell/miniconda3/envs/py38/lib/python3.8/multiprocessing/context.py", line 283, in _Popen
    return Popen(process_obj)
  File "/Users/efarrell/miniconda3/envs/py38/lib/python3.8/multiprocessing/popen_spawn_posix.py", line 32, in __init__
    super().__init__(process_obj)
  File "/Users/efarrell/miniconda3/envs/py38/lib/python3.8/multiprocessing/popen_fork.py", line 19, in __init__
    self._launch(process_obj)
  File "/Users/efarrell/miniconda3/envs/py38/lib/python3.8/multiprocessing/popen_spawn_posix.py", line 47, in _launch
    reduction.dump(process_obj, fp)
  File "/Users/efarrell/miniconda3/envs/py38/lib/python3.8/multiprocessing/reduction.py", line 60, in dump
    ForkingPickler(file, protocol).dump(obj)
AttributeError: Can't pickle local object 'register.<locals>._handler'
@Tronic
Copy link
Member

Tronic commented Jan 28, 2020

For triaging: this is now a problem because Python 3.8 changes the default mode of multiprocessing on MacOS to spawn instead of fork. This requires all parameters passed to Sanic worker processed to be picklable, and now there is an object that isn't.

This could be worked around by forcing fork mode instead of the new default, but there really shouldn't be objects that cannot be pickled there because Windows cannot fork and probably could not spawn any workers then either.

@endafarrell
Copy link
Contributor Author

Hi @Tronic - many thanks for this. We write code on a mixture of OSX and Linux, and deploy to Linux, so in our particular instance, not being able to fork on Windows is not going to cause a problem.

We do have complex objects being passed to the workers, but I had though that those with connections to databases (which we know cannot be pickled) were created only in after_server_start handlers. Will check. Many thanks.

@ashleysommer
Copy link
Member

@Tronic a comment related to this:
around the release of Sanic 18.12 we wanted to get multiprocessing working on Windows, so I put in some effort and made all of Sanic pickle-able.

I thought I added tests to ensure a sanic app could be fully pickled and unpickled without error.

Looks like now some code has been introduced at some point that has a local sub_function, which isn't pickleable, or perhaps its only a problem on Python 3.8, which I think our test suite isn't running properly yet.

@Tronic
Copy link
Member

Tronic commented Jan 31, 2020

@ashleysommer Would it be viable to move most app initialisation to within workers, instead of pickling it from the main process? Only server socket opening really needs to be in the master process, while app definition and other user code could be run in workers. Getting this done would need quite a bit of refactoring, but barring that, it could be the clean solution to this. Doing so would avoid troublesome pickling and the freeze_support hack of multiprocessing that tries to avoid the re-execution of main function and other such code when child processes are run (but it still re-runs any module level code reached prior to that freeze function call).

@stale
Copy link

stale bot commented Apr 30, 2020

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If this is incorrect, please respond with an update. Thank you for your contributions.

@stale stale bot added the stale label Apr 30, 2020
@Tronic
Copy link
Member

Tronic commented Apr 30, 2020

This still needs to be fixed.

@stale stale bot removed the stale label Apr 30, 2020
@endafarrell
Copy link
Contributor Author

@Tronic I have some (hopefully good) news.

Our project was busy for a few months but last week we got around to putting effort back into our py38 upgrade. Somewhere along the lines we had also re-checked all code to ensure that calls to set up connections to external databases (we have three external connection pools to three different kinds of database) were made in @bp.listener("after_server_start") methods in our blueprints. There had been at least one "before_server_start" before.

We were happily surprised to find that due to ensuring we created our connections "after_server_start" we can start the sanic 19.12.2 API just fine on OSX 10.14.6 with py3.8.2 (and sanic-cors 0.10.0.post3 - but I do not believe we did anything special with that).

At this stage I can think of two possible next steps:

1: a note in the docs (and a review of existing examples) to state that connections to databases should be created "after_server_start"
2: a note in the docs that states that "after_server_start" happens after the main thread creates the workers but before allowing them to take traffic. It seems somewhat obvious in hind-sight, but I struggled for a little to grok this.

@Tronic - what's the best way to proceed?

@Tronic
Copy link
Member

Tronic commented May 6, 2020

I don't think that before vs. after server start should make a difference for pickling. Both are done in worker processes, and only await loop.create_server(...) occurs between them. Also, what happens inside those handlers should not affect anything.

According to your traceback, the _handler function defined within sanic.static.register is what cannot be pickled, which makes sense because pickling AFAIK doesn't work for anything defined inside functions. Why this is now working is unknown to me, and more time would be needed to fully investigate. Perhaps different Python or Sanic versions, or perhaps somehow it no longer uses that _handler wrapper.

@endafarrell
Copy link
Contributor Author

Ah - I clearly still don't understand the inner working yet then.

I can't relate the string AttributeError: Can't pickle local object 'register.<locals>._handler' to our code - at least not directly nor in an obvious manner. Do you happen to have any pointers as to how to chase this particular thing down?

@Tronic
Copy link
Member

Tronic commented May 6, 2020

Are you no longer using app.static (for serving static files)? That would explain it because this appears to be the only place where that is used.

@Tronic
Copy link
Member

Tronic commented May 6, 2020

I believe this could be fixed quite easily by rewriting sanic/static.py to use functools.partial to pass use_modified_since, use_content_range, stream_large_files and content_type to _handler instead of it being a local function.

Pinging anyone who cares: pull requests are welcome!

ashleysommer added a commit to ashleysommer/sanic that referenced this issue May 7, 2020
Moves the subfunction _handler out to a module-level function, and parameterizes it with functools.partial().
Fixes the case when picking a sanic app which has a registered static route handler. This is usually encountered when attempting to use multiprocessing or auto_reload on OSX or Windows.
Fixes sanic-org#1774
ashleysommer added a commit to ashleysommer/sanic that referenced this issue May 7, 2020
Moves the subfunction _handler out to a module-level function, and parameterizes it with functools.partial().
Fixes the case when picking a sanic app which has a registered static route handler. This is usually encountered when attempting to use multiprocessing or auto_reload on OSX or Windows.
Fixes sanic-org#1774
ashleysommer added a commit to ashleysommer/sanic that referenced this issue May 7, 2020
Moves the subfunction _handler out to a module-level function, and parameterizes it with functools.partial().
Fixes the case when picking a sanic app which has a registered static route handler. This is usually encountered when attempting to use multiprocessing or auto_reload on OSX or Windows.
Fixes sanic-org#1774
@endafarrell
Copy link
Contributor Author

@Tronic Sorry for the delay in getting back to you.

Our code doesn't use app.static directly, but has been (for over a year) and still does, use blueprint.static - that part of the code has not changed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants