unbit / uwsgi

uWSGI application server container
http://projects.unbit.it/uwsgi
Other
3.46k stars 691 forks source link

Python uwsgidecorator @spool() allows keyword arguments to leak between spooler function calls #704

Closed jsivak closed 10 years ago

jsivak commented 10 years ago

Found using Python 2.7 and uWSGI 2.0.6.

Executing @spool() decorated functions in this order will result in 'arg3' still being passed to the "do_something()" task on the 3rd call, even though it wasn't explicitly passed.

Example:

spooler_tasks.do_something({'arg1': 'in do_something', 'arg2': 'value2'})
spooler_tasks.do_something({'arg1': 'in do_something', 'arg2': 'value2', 'arg3': 'value3'})
spooler_tasks.do_something({'arg1': 'in do_something', 'arg2': 'value2'})

The problem appears to be in the handling of the arguments variable in uwsgidecorators.py around line 70.

class _spoolraw(object):

    def __call__(self, *args, **kwargs):
        arguments = self.base_dict
        if not self.pass_arguments:
            if len(args) > 0:
                arguments.update(args[0])
            if kwargs:
                arguments.update(kwargs)
        else:
            spooler_args = {}
            for key in ('message_dict', 'spooler', 'priority', 'at', 'body'):
                if key in kwargs:
                    spooler_args.update({key: kwargs.pop(key)})
            arguments.update(spooler_args)
            arguments.update({'args': pickle.dumps(args), 'kwargs': pickle.dumps(kwargs)})
        return uwsgi.spool(arguments)

    # For backward compatibility (uWSGI < 1.9.13)
    def spool(self, *args, **kwargs):
        return self.__class__.__call__(self, *args, **kwargs)

    def __init__(self, f, pass_arguments):
        if not 'spooler' in uwsgi.opt:
            raise Exception(
                "you have to enable the uWSGI spooler to use @%s decorator" % self.__class__.__name__)
        self.f = f
        spooler_functions[self.f.__name__] = self.f
        # For backward compatibility (uWSGI < 1.9.13)
        self.f.spool = self.__call__
        self.pass_arguments = pass_arguments
        self.base_dict = {'ud_spool_func': self.f.__name__}

I believe the assignment of arguments = self.base_dict needs to be set to a copy of self.base_dict, not a reference, so the correct line should be arguments = self.base_dict.copy(). Currently arguments is getting the reference to self.base_dict and any keyword args that are being passed to the @spool() decorated function are being added/remembered across function calls to the decorated function, which is more obvious when the number of keyword arguments to a single spooled function changes.

I believe a shallow copy of self.base_dict should work, but not 100% sure on that.

jsivak commented 10 years ago

Is this the correct place to file this bug?

unbit commented 10 years ago

Yes for sure, sorry for the late, checking it.

unbit commented 10 years ago

We are refactoring the spool decorator to fix this issue and to correctly support python 3. Thanks for your report

aldur commented 10 years ago

It should be fixed now, thanks for your report.

jsivak commented 10 years ago

So this should be in the next (2.0.8) release?

unbit commented 10 years ago

@jsivak You can already use it with

pip install uwsgidecorators

@aldur splitted it a couple of days ago

jsivak commented 10 years ago

Ah, thanks! I didn't realize it was going to be released "outside" of the main uwsgi package.