From 5cf2144b3ff3438afe0c9f7dbcbcb29cb94c48e0 Mon Sep 17 00:00:00 2001 From: Ashley Sommer Date: Sun, 4 Nov 2018 15:04:12 +1000 Subject: [PATCH] Fix pickling blueprints Change the string passed in the "name" section of the namedtuples in Blueprint to match the name of the Blueprint module attribute name. This allows blueprints to be pickled and unpickled, without errors, which is a requirment of running Sanic in multiprocessing mode in Windows. Added a test for pickling and unpickling blueprints Added a test for pickling and unpickling sanic itself Added a test for enabling multiprocessing on an app with a blueprint (only useful to catch this bug if the tests are run on Windows). --- sanic/blueprints.py | 14 +++++---- tests/test_multiprocessing.py | 53 +++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 5 deletions(-) diff --git a/sanic/blueprints.py b/sanic/blueprints.py index c837a2a9..f7f6cfb7 100644 --- a/sanic/blueprints.py +++ b/sanic/blueprints.py @@ -5,7 +5,7 @@ from sanic.views import CompositionView FutureRoute = namedtuple( - "Route", + "FutureRoute", [ "handler", "uri", @@ -17,11 +17,15 @@ FutureRoute = namedtuple( "name", ], ) -FutureListener = namedtuple("Listener", ["handler", "uri", "methods", "host"]) -FutureMiddleware = namedtuple("Route", ["middleware", "args", "kwargs"]) -FutureException = namedtuple("Route", ["handler", "args", "kwargs"]) +FutureListener = namedtuple( + "FutureListener", ["handler", "uri", "methods", "host"] +) +FutureMiddleware = namedtuple( + "FutureMiddleware", ["middleware", "args", "kwargs"] +) +FutureException = namedtuple("FutureException", ["handler", "args", "kwargs"]) FutureStatic = namedtuple( - "Route", ["uri", "file_or_directory", "args", "kwargs"] + "FutureStatic", ["uri", "file_or_directory", "args", "kwargs"] ) diff --git a/tests/test_multiprocessing.py b/tests/test_multiprocessing.py index d247001f..d054d42f 100644 --- a/tests/test_multiprocessing.py +++ b/tests/test_multiprocessing.py @@ -1,9 +1,11 @@ import multiprocessing import random import signal +import pickle import pytest from sanic.testing import HOST, PORT +from sanic.response import text @pytest.mark.skipif( @@ -27,3 +29,54 @@ def test_multiprocessing(app): app.run(HOST, PORT, workers=num_workers) assert len(process_list) == num_workers + + +def test_multiprocessing_with_blueprint(app): + from sanic import Blueprint + # Selects a number at random so we can spot check + num_workers = random.choice(range(2, multiprocessing.cpu_count() * 2 + 1)) + process_list = set() + + def stop_on_alarm(*args): + for process in multiprocessing.active_children(): + process_list.add(process.pid) + process.terminate() + + signal.signal(signal.SIGALRM, stop_on_alarm) + signal.alarm(3) + + bp = Blueprint('test_text') + app.blueprint(bp) + app.run(HOST, PORT, workers=num_workers) + + assert len(process_list) == num_workers + + +# this function must be outside a test function so that it can be +# able to be pickled (local functions cannot be pickled). +def handler(request): + return text('Hello') + +# Muliprocessing on Windows requires app to be able to be pickled +@pytest.mark.parametrize('protocol', [3, 4]) +def test_pickle_app(app, protocol): + app.route('/')(handler) + p_app = pickle.dumps(app, protocol=protocol) + up_p_app = pickle.loads(p_app) + assert up_p_app + request, response = app.test_client.get('/') + assert response.text == 'Hello' + + +@pytest.mark.parametrize('protocol', [3, 4]) +def test_pikcle_app_with_bp(app, protocol): + from sanic import Blueprint + bp = Blueprint('test_text') + bp.route('/')(handler) + app.blueprint(bp) + p_app = pickle.dumps(app, protocol=protocol) + up_p_app = pickle.loads(p_app) + assert up_p_app + request, response = app.test_client.get('/') + assert app.is_request_stream is False + assert response.text == 'Hello'