Thread Safety with Template Tags
After reading this document about thread safety, I am left feeling that there is something missing in the documentation, or my reading of it, or my reasoning.
Let's give a simple example:
class HelloWorldNode(template.Node):
def render(self, context):
return "O HAI LOL"
@register.tag(name="hello_world")
def hello_world(parser, tokens):
"""
Greets the world with wide-eyed awe.
"""
return HelloWorldNode()
I understood this code to construct a new instance of the HelloWorldNode
class whenever the hello_world
tag is used. Other examples involve passing arguments to the constructor, like this:
class HelloWorldNode(template.Node):
def __init__(self, message):
self.message = message
def render(self, context):
return "O HAI LOL " + message
@register.tag(name="hello_world")
def hello_world(parser, tokens):
"""
Greets the world with wide-eyed awe.
"""
message = tokens.split_contents()[1]
return HelloWorldNode(message)
Thus, when hello_world
is executed, a new instance of HelloWorldNode is created, and the instance dictionary has an attribute message
. This instance surely must be used only for the rendering of only the given instance of the tag, as using it for other renderings would mean that the data bound to it would be incorrect. If this were not the case, the arguments would get mixed up between different uses of the tag.
Looking at other examples from the docs, here's a simplified example from here:
def do_current_time(parser, token):
tag_name, format_string = token.split_contents()
return CurrentTimeNode(format_string[1:-1])
As this takes data from the tokens passed to the fun开发者_JAVA技巧ction, the only way that CurrentTimeNode can work is that a new one is instantiated each time do_current_time
is invoked.
Back to the documentation page, where the dissonance sets in. This is 'bad'.
class CycleNode(Node):
def __init__(self, cyclevars):
self.cycle_iter = itertools.cycle(cyclevars)
def render(self, context):
return self.cycle_iter.next()
The doc says that two pages using the same tag might then experience race conditions if they both use the same node. I don't understand how two templates' rendering could end up sharing the same instance if they both independently instantiate their own.
The way to solve this, says the docs is like this:
class CycleNode(Node):
def __init__(self, cyclevars):
self.cyclevars = cyclevars
def render(self, context):
if self not in context.render_context:
context.render_context[self] = itertools.cycle(self.cyclevars)
cycle_iter = context.render_context[self]
return cycle_iter.next()
This appears to index context.render_context
with self
. The implication of that must be that self
is used to identify the instance in one of two ways:
self
references one specific instance of the class in the whole systemself
references that class only, and in order to reference the instance a render context is required
If 1 is true, why not just associate data with self
?
If 2 is true, and the render context is "associated with the context of the template that is currently being rendered", how is it possible to distinguish between two instance of the template tag on the same page?
Is the Node instantiated individually each time the tag is invoked? If so, why the concurrency problems? If not, why not?
Got it with closer reading of this.
The template is compiled when it is loaded. Any arguments passed into the tag function are 'static'. They are either literal strings, or they are strings which are used as identifiers to look up bound variables in the render context.
Therefore the Node object is instantiated for each tag, and hangs around ready for use whenever the template is used (and naturally the template may be used in any number of threads).
Thus the self
in my question is the identity of the specific Node within the template. Combined with the render context, this gives a unique identity on which to hang instance variables.
精彩评论