Skip to content

Go 1.7: http.Request.Context breaks gorilla/context usage #80

Description

@elithrar

The new http.Request.Context() in Go 1.7 creates a shallow copy of the original request that requires the caller to save it upstream. This copy has a different address and therefore has a different map key in the context map provided by gorilla/context.

In order to fix this in gorilla/sessions we need to make a breaking change for Go 1.7+ users:

- func GetRegistry(r *http.Request) *Registry {
+ func GetRegistry(r *http.Request) (*Registry, *http.Request)
- sessions.GetRegistry(r).Get(s, name)
+ var reg *sessions.Registry
+ reg, r = sessions.GetRegistry(r)
+ sess, err := reg.Get(store, name)

That should be about it. This is unavoidable unfortunately, but is (thankfully) a compile-time breaking change that we can document clearly.

Ref: gorilla/mux#168

Activity

  1. self-assigned this
    on Jun 4, 2016
  2. ironcladlou commented on Oct 18, 2016

    @ironcladlou

    FWIW, I'm currently using @shawnps's branch while awaiting this merge and will report any issues I find.

  3. jbrook commented on Nov 22, 2016

    @jbrook

    We inadvertently ran into this issue when we upgraded to Go 1.7.3 recently. The main effect was that our heap used up all of the available memory and our app crashed. It would probably be a good idea to add a warning note to the main README file.

    The diagram below is the in-use space after running a 30 second benchmark against one of the routes/handlers in our app that stores various session variables in a cookie.

    session-leak

  4. ironcladlou commented on Nov 28, 2016

    @ironcladlou
  5. niondir commented on Jul 17, 2017

    @niondir

    Thanks for the hint, A workaround is to save the session in the request.context() manually and still clean up the gorialla context at the end of the request.

  6. seh commented on Jul 21, 2017

    @seh

    I'm confused by @jbrook's findings above, and as to whether it's safe to use this package with Go 1.8.

    If one follows the advice of wrapping an http.Handler with context.ClearHandler, are we safe from accumulating these sessions.Registry instances in memory?

  7. niondir commented on Jul 21, 2017

    @niondir

    The context.ClearHandler uses the request to clean the related data and it might not actually work in some cases and leak some data (depends on any request copy that might be created). But there is a method in the context package that can help:

    // Purge removes request data stored for longer than maxAge, in seconds.
    // It returns the amount of requests removed.
    //
    // If maxAge <= 0, all request data is removed.
    //
    // This is only used for sanity check: in case context cleaning was not
    // properly set some request data can be kept forever, consuming an increasing
    // amount of memory. In case this is detected, Purge() must be called
    // periodically until the problem is fixed.
    func Purge(maxAge int) int {
    

    Just create a contextPurgeMiddleware that calls context.Purge(-1) after each request. Additionally you have to make sure that you do not rely on any gorilla.context since this middleware will wipe contexts of all requests. You must only use the go context than.

  8. seh commented on Jul 22, 2017

    @seh

    By my reading of context.Purge, especially when called with a nonpositive argument, it deletes all the data associated with all http.Requests for which we've stored a session. That means that sessions.(* CookieStore).Get creates session.Registrys, but every time we finish with a given request, we remove all of these registries, some of which could be in use in requests we're still processing concurrently. We are then effectively creating caches that we throw away indiscriminately, possibly before we can ever make use of them.

    Wouldn't the more sound advice be to just avoid all use of sessions.GetRegistry? It turns out we can't, if ever intend to call sessions.Save. We have to skirt that function and use, say, sessions.(*CookieStore).Save directly, which is far less practical.

    What a mess. We have an attempted optimization that turned out to be an even bigger problem, as hiding things in global variables so often does, and we're now having trouble reworking it in order to be backward compatible. I have no previous application using this package, so I'm more interested in how to break it to fix it.

    The original proposal here by @elithrar sounds reasonable, though I'm not sure yet how the callers of GetRegistry in which I'm interested—sessions.(* CookieStore).Get and sessions.Save—would have to change too.

  9. elithrar commented on Jul 22, 2017

    @elithrar
    ContributorAuthor
  10. seh commented on Jul 22, 2017

    @seh

    I think I see a way to avoid all use of the Registry type with the package as is, but doing so requires careful study and diligence. I agree that using http.Request.WithContext is the way to go for caching these things, but that does force callers to deal with copied http.Request values.

    The way that http.Request.WithContext works is an odd retrofit. I'd like to find the design rationale that mandated that http.Request's "ctx" field be immutable. I suppose that when one is using http.Request in the context of a server—as opposed to preparing a request as a client—it makes some sense to treat the request as a fixed thing.

    This article (I apologize for linking to Medium) shows an example of one "outer" HTTP handler preparing a contribution to a request's context, and passing the result of http.Request.WithContext directly on to the next delegated "inner" handler. That looks natural. This package's attempted use of it is awkward, because there's no handler wrapping or delegation in play that establishes a containing and contained extent for the context change.

  11. elithrar commented on Jul 24, 2017

    @elithrar
    ContributorAuthor
  12. seh commented on Jul 24, 2017

    @seh

    Over the weekend I wrote this library to help with using gorilla/sessions without touching session.Registry. It needs documentation and examples, but I hope you can get the gist from the function-level documentation that's there.

  13. justinclift commented on Aug 2, 2017

    @justinclift
    Contributor

    Hmmm, so as a new-ish Golang programmer looking to use gorilla/sessions for the first time... does this affect the code base I'm working on? (using Go 1.8, and not already using any explicit context package)

    The current README doc says:

    Important Note: If you aren't using gorilla/mux, you need to wrap your handlers
    with context.ClearHandler ...
    

    Which turns out to mean gorilla/context (only). 😉 Looking at various issues here and the open PR about memory docs it seems like that conflicts with the Go 1.7+ context package.

    Hmmm, does that doc fragment need updating to say something more like:

    Important Note: If you are using gorilla/content, but aren't using gorilla/mux, you
    need to wrap your handlers with context.ClearHandler ...
    

    It's pretty confusing atm. 😉

  14. 3 remaining items

  15. justinclift commented on Aug 2, 2017

    @justinclift
    Contributor

    k, well I'll explain what's confusing me (as dumb as it might sound), as it might shed some clarity on what others experience. 😄

    When I (newbie user) read through the session docs it seems reasonably straightforward up until this bit:

    Important Note: If you aren't using gorilla/mux, you need to wrap your handlers with
    context.ClearHandler or else you will leak memory! An easy way to do this is to wrap
    the top-level mux when calling http.ListenAndServe:
    
    	http.ListenAndServe(":8080", context.ClearHandler(http.DefaultServeMux))
    
    The ClearHandler function is provided by the gorilla/context package.
    

    With the application I'm developing, it's using Go 1.8 and doesn't use gorilla/mux. So, that warning sounds like something we need to do. Leaking memory obviously being bad. Thus:

    1. Immediate added "context" to our imports
    • Didn't work. After re-reading of the above paragraph it mentions a gorilla/context package. So, that must be the right one to use.
    1. Look at the gorilla/context page. That one has this warning instead:
    Note: gorilla/context, having been born well before context.Context existed, does not
    play well with the shallow copying of the request that http.Request.WithContext
    (added to net/http Go 1.7 onwards) performs. You should either use just gorilla/context,
    or moving forward, the new http.Request.Context().
    

    Which makes it sound pretty deprecated. Our code base doesn't have context of any sort in any of our imports sections though, so now I'm at a loss which way to go. eg package sessions says to import context, but package context sounds like it's deprecated

    1. Time to ask.

    You've helpfully replied:

    If you are using Go 1.8 only and not importing gorilla/context (which includes
    packages you are using), gorilla/mux defaults to the Go 1.7 http.Request.Context
    implementation.
    

    ... but that doesn't really clarify things (for me). In our (newbie) case, we're not using gorilla/mux. We're wanting to use gorilla/sessions. So, not grokking how the gorilla/mux defaulting there comes into play.

    Anyway, I guess what I'm meaning is that there needs to be a clear statement about this. The current one doesn't seem to address things for newbie users (99.9% who will be using Go 1.7+) who aren't yet using any gorilla packages.

    Hopefully I'm not muddying things. 😄

  16. added 4 commits that reference this issue on Aug 14, 2017
  17. kemitche commented on Jan 22, 2018

    @kemitche

    Would it be viable to move forward with v2 changes on an alternate branch in this repo? That would at least allow users of dep and similar tools to move forward by specifically selecting the alternate branch/tag, without affecting non-vendor-tool users.

  18. elithrar commented on Jan 22, 2018

    @elithrar
    ContributorAuthor

    Yes, that's what we plan to do, but that's the easy part. The hard part is doing all of the work to write the code, new tests, ensure the new API has time to bake, etc :)

  19. flibustenet commented on Mar 2, 2019

    @flibustenet

    Is it closed by recent PR #175 ?

  20. elithrar commented on Mar 2, 2019

    @elithrar
    ContributorAuthor
  21. flibustenet commented on Mar 4, 2019

    @flibustenet

    Fine, do you plan to make a minor release with this change or still jump to v2 for this ?

  22. elithrar commented on Mar 4, 2019

    @elithrar
    ContributorAuthor
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions