Be careful with Sitecore pipeline processors lifecycle

In a current project we have the requirement to show the same content in different contextes. For example, we want to show the same content item under different url’s with different breadcrumb navigation etc. To achieve this we’ve added a new processor for the httpRequestBegin pipeline where we build our context. In the different views we catch up this context and generate the markup. The context we build was registered with an IoC (inversion of control) container in a "per webrequest" lifecycle (a new instance for each http request). We ran into many problems with the pipeline processor... But let’s start at the beginning.

Our processor resolves an instance of IContext from the container in the constructor. There are of course better ways (i.e. poor man dependency injection or adding a factory to resolve pipeline processors), but for demonstrating I think it’s enough.

public class LifecycleTest : HttpRequestProcessor
{
    private IContext context;

    public LifecycleTest()
    {
        this.context = MyContainer.Resolve<IContext>;
        Sitecore.Diagnostics.Log.Info("ctor() has been called", this);
    }

    public override void Process(HttpRequestArgs args)
    {
        Sitecore.Diagnostics.Log.Info("Process(HttpRequestArgs args) method has been called", this);

        // do stuff here
    }
}

After some tests we found that the context is always the same (not only the same for one web request). Here the log for this processor with three requests:

21.02.2015 14:03:23 ctor() has been called
21.02.2015 14:03:23 Process(HttpRequestArgs args) method has been called
21.02.2015 14:03:39 Process(HttpRequestArgs args) method has been called
21.02.2015 14:03:55 Process(HttpRequestArgs args) method has been called

In fact, pipeline processors from Sitecore lives in a Singleton lifecycle and therefor the constructor is only called once at the first request and our context stays the same for all future requests. So we tried to resolve the context within the Process() method, as this method is called once per web request:

public class LifecycleTest : HttpRequestProcessor
{
    private IContext context;

    public override void Process(HttpRequestArgs args)
    {
        Sitecore.Diagnostics.Log.Info("Process(HttpRequestArgs args) method has been called", this);
        this.context = MyContainer.Resolve<IContext>;

        // do stuff here
    }
}

We needed the context in several methods of the processor, so we did it with a private field. This works then very well, until we have seen the results of load tests. We got really strange results. The key point was, that different requests swap/takes the context of another request. How is this possible? The answer is quite simple, but hard to find when you did the mistake: As the pipeline processor is in fact a Singleton, the private field keeps it’s value over multiple requests. Because the Process() method overrides the field at every request this works fine for single requests, but under high load with concurrent requests, no chance. We have also tried the way with a factory, as described by Nat in his blog, with the same result: Pipeline processors have a Singleton scope.

Conclusion

A rule when working with dependency injection is, that the lifecycle of a class can never be lower than the one which holds it’s reference. Same is when working with Sitecore pipeline processors: Constructor injection (or working with private fields at all) only works for classes which lives in a Singleton lifecycle.

Update: Nick Wesselman mentioned on Twitter that there is an (undocumented) attribute reusable="false" on the processor configuration which makes the scope of the pipeline processor transient.

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <httpRequestBegin>
        <processor type="Website.Pipelines.HttpRequestBegin.LifecycleTest, Website" reusable="false" />
      </httpRequestBegin>
    </pipelines>
  </sitecore>
</configuration>

And yes, this worked. With this configuration, the constructor of the processor is called for every request. For performance reasons, I would only use this for testing purposes or if it’s really necessary to have the processor in transient scope. I prefer to solve the described requirements with local variables.

Kevin Brechbühl

Senior Developer & Architect | C# & ASP.NET | Sitecore Technology MVP

comments powered by Disqus