Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

net/http: allow modifying Server's base context #30694

Closed
taralx opened this issue Mar 8, 2019 · 21 comments
Closed

net/http: allow modifying Server's base context #30694

taralx opened this issue Mar 8, 2019 · 21 comments
Labels
FrozenDueToAge NeedsFix The path to resolution is known, but the work has not been done. Proposal Proposal-Accepted
Milestone

Comments

@taralx
Copy link

taralx commented Mar 8, 2019

This is #16220 revived, as that one is locked due to age.

Contexts are everywhere now, and can carry significant information, like authentication data. It is therefore useful to us to be able to set the base context to something other than context.Background().

(#20956 has a similar request, although that one is context modification per-connection instead of per-listener.)

While it is possible to do this by wrapping the handler and merging the contexts, this is error-prone and requires an additional goroutine to properly merge the Done channels.

The main question is one of API. There are two options that I can think of:

  1. Add a field BaseContext to the http.Server struct.
    Advantages: Keeps the API the same, easy to retrofit, composable with existing systems that take/manipulate http.Server.
    Disadvantages: We discourage storing contexts in structs because it makes it easier to misuse them (e.g. using a stale context).
  2. Add a Context parameter to the Serve call.
    Advantages: Standard way to provide base context; difficult to misuse.
    Disadvantages: API proliferation -- we need ServeContext for each variant of Serve.

cc: @bradfitz
cc: #18997

@gopherbot gopherbot added this to the Proposal milestone Mar 8, 2019
@matttproud
Copy link
Contributor

Please consult internal issue id no. 34620629 for additional information and stakeholders.

@happygiraffe
Copy link

Rather than storing a context in http.Server, would storing a context creation function work better? Then the default could be a reference to context.Background.

@taralx
Copy link
Author

taralx commented Mar 9, 2019

@happygiraffe It's possible, sure, but is a more complex API. What is your use case?

@taralx
Copy link
Author

taralx commented Mar 15, 2019

I take that back, it's not much more complex. But since we would be calling the function basically at the start of net/http.Server.Serve I'm not sure that it's a win for expressiveness.

This proposal doesn't change the timing of the base context, so it's still one base context per Serve() call. If we were to re-engineer the code to construct the context later, we could have the connection information available, but that's about as late as we can go. Does anyone think that it's worth doing?

(I kind of wonder why http.Server doesn't have a ServeConn interface? Probably nobody has asked for it.)

@matttproud
Copy link
Contributor

For our use case, we expressly need to be the first to attach data to the request-scoped contexts —thanks to massive legacy use — as we cannot reliably guarantee to be the first callee by handlers enrolled with the http package (think handler chaining). We could be tolerant to accept additional deadline information if something attempts to set a shorter value later.

Between the two proposals, my expectation is a function value to create the request's context could be the most expressive and flexible solution for us:

  • Default (or maximum) deadline for handlers, which is critical for load shedding and other production
    fitness questions.
  • Don't want to preclude having handlers derive from a common distributed trace root ID.
package http

type Server struct {
  // detail elided

  // RequestContext is invoked by the server for each request it receives.  If nil, it defaults to
  // context.Background.
  //
  // The value is passed to each *Request the registered handlers receive.
  RequestContext func() context.Context
}

This also bodes well for supporting ephemeral HTTP servers (think: tests and other time-bound environments) that are not background-/root-scoped.

Another consideration: in the RPC space, we automatically cancel context upon handler completion. This is useful to force users to understand lifetime and remedy resource leaks. Perhaps this becomes part of the contract somehow.

@gopherbot
Copy link
Contributor

Change https://golang.org/cl/167681 mentions this issue: net/http: add Server.ConnContext hook to modify context for new connections

@bradfitz
Copy link
Contributor

How about https://golang.org/cl/167681?

diff --git a/src/net/http/server.go b/src/net/http/server.go
index 4e9ea34491..6ab9ef9906 100644
--- a/src/net/http/server.go
+++ b/src/net/http/server.go
@@ -2542,6 +2542,10 @@ type Server struct {
        // If nil, logging is done via the log package's standard logger.
        ErrorLog *log.Logger
 
+       // ConnContext optionally specifies a function that modifies
+       // the context used for a new connection.
+       ConnContext func(context.Context, net.Conn) context.Context
+
        disableKeepAlives int32     // accessed atomically.
        inShutdown        int32     // accessed atomically (non-zero means we're in Shutdown)
        nextProtoOnce     sync.Once // guards setupHTTP2_* init
@@ -2876,6 +2880,12 @@ func (srv *Server) Serve(l net.Listener) error {
                        }
                        return e
                }
+               if cc := srv.ConnContext; cc != nil {
+                       ctx = cc(ctx, rw)
+                       if ctx == nil {
+                               panic("ConnContext returned nil")
+                       }
+               }
                tempDelay = 0
                c := srv.newConn(rw)
                c.setState(c.rwc, StateNew) // before Serve can return

@matttproud
Copy link
Contributor

Just trying to suss a few details and potential uses for what you propose:

What is the motivation for the input parameters in the signature of func(context.Context, net.Conn)? Are you anticipating supporting client/session scoping with the net.Conn? What context value would the initial parameter contain? Does this still support scoping contexts to individual requests, or are are they just scoped to individual connections?

@bradfitz
Copy link
Contributor

The input context is a context.Background() (the baseCtx) + the context.WithValue(baseCtx, ServerContextKey, srv).

So you can get at the Server if you need to from the provided ctx, without closing over the Server. And if we add more variables to the context later (like the associated listener), we can get at those too.

And then the net.Conn is the just-Accepted connection, so you can add context based on properties of who just connected to you based on their address or whatnot. Or you can recognize the concrete type of the net.Conn if you had a fancy net.Listener upstream.

Not sure what you mean by client/session scoping.

And yes, this is per-connection, not per-request. There are already per-requests contexts that would be derived from this one (that are currently derived from the baseCtx). This per-connection context would go between the two.

@matttproud
Copy link
Contributor

Thanks for clarifying; I had completely forgotten about that the user could acquire the server by way of the context. I think we could live with a layer cake similar to what you have proposed. It might be well to have the hierarchy documented somewhere for posterity if this is accepted and implemented.

By client/session scoping, I had imprecisely meant "connection scoping". I was uncertain how connection pooling factored into this if at all (e.g., sticky connection as a session through which multiple requests are made).

@taralx
Copy link
Author

taralx commented Mar 15, 2019

@bradfitz Can't we already get this by wrapping the Handler? I filed this proposal because I'm trying to avoid merging the http context with an existing context.

@bradfitz
Copy link
Contributor

This is before the Handler. And you couldn't get the net.Conn to influence the context before.

The "existing context" that the above proposed hook gets is pretty minimal: it only has the ServerContextKey variable populated, which is a documented feature, so we can't risk letting users supply a hook that might not populate it.

So if you need to set the very base context (because you already have one), it'd need to be earlier I guess.

We could do both, even, and then be done with this. e.g. see patchset 2 of https://go-review.googlesource.com/c/go/+/167681

@taralx
Copy link
Author

taralx commented Mar 16, 2019

I'm fine with this.

@matttproud
Copy link
Contributor

I think this will work for us (or at least affords the most reasonable tradeoffs). Thank you for the attention.

@ianlancetaylor ianlancetaylor changed the title Proposal: Allow modifying net/http.Server's base context proposal: allow modifying net/http.Server's base context Mar 20, 2019
@ianlancetaylor ianlancetaylor changed the title proposal: allow modifying net/http.Server's base context proposal: net/http: allow modifying Server's base context Mar 20, 2019
@bradfitz
Copy link
Contributor

Accepted in proposal review.

@bradfitz bradfitz self-assigned this Mar 20, 2019
@bradfitz bradfitz changed the title proposal: net/http: allow modifying Server's base context net/http: allow modifying Server's base context Mar 20, 2019
@bradfitz bradfitz modified the milestones: Proposal, Go1.13 Mar 20, 2019
@ianlancetaylor ianlancetaylor added the NeedsFix The path to resolution is known, but the work has not been done. label Mar 20, 2019
@taralx
Copy link
Author

taralx commented Apr 11, 2019

If I add tests to Brad's change, is that all that's needed here?

@bradfitz
Copy link
Contributor

@taralx, sorry, I can do it tomorrow. I've been a combination of distracted by other things, sick, and traveling. But I'm back tonight & working like normal tomorrow.

@matttproud
Copy link
Contributor

Thank you for taking care of this, Brad and JP!

@navytux
Copy link
Contributor

navytux commented Apr 16, 2019

While it is possible to do this by wrapping the handler and merging the contexts, this is error-prone and requires an additional goroutine to properly merge the Done channels.

First of all I appologize for feedback when this issue is closed. Next I suggest to reconsider about context merging. I'm doing exactly this in my services - please see https://godoc.org/lab.nexedi.com/kirr/go123/xcontext#hdr-Merging_contexts for details.

It is true that xcontext.Merge currently requires extra goroutine. This comes from the fact that stdlib context package 1) does not provide way to parent-child link other context implementations tightly with what is in stdlib already, and 2) does not provide context.Merge. It thus could be fixed by implementing either "2" (preferably) or "1".

Attaching xcontext.Merge documentation for the reference.

Thanks beforehand,
Kirill

/cc @Sajmani


Merging contexts

Merge could be handy in situations where spawned job needs to be canceled whenever any of 2 contexts becomes done. This frequently arises with service methods that accept context as argument, and the service itself, on another control line, could be instructed to become non-operational. For example:

func (srv *Service) DoSomething(ctx context.Context) (err error) {
	defer xerr.Contextf(&err, "%s: do something", srv)

	// srv.serveCtx is context that becomes canceled when srv is
	// instructed to stop providing service.
	origCtx := ctx
	ctx, cancel := xcontext.Merge(ctx, srv.serveCtx)
	defer cancel()

	err = srv.doJob(ctx)
	if err != nil {
		if ctx.Err() != nil && origCtx.Err() == nil {
			// error due to service shutdown
			err = ErrServiceDown
		}
		return err
	}

	...
}

func Merge

func Merge(parent1, parent2 context.Context) (context.Context, context.CancelFunc)

Merge merges 2 contexts into 1.

The result context:

  • is done when parent1 or parent2 is done, or cancel called, whichever happens first,
  • has deadline = min(parent1.Deadline, parent2.Deadline),
  • has associated values merged from parent1 and parent2, with parent1 taking precedence.

Canceling this context releases resources associated with it, so code should call cancel as soon as the operations running in this Context complete.

navytux added a commit to navytux/go123 that referenced this issue May 19, 2019
Pygolang does it. However my comment on Go issue tracker about this
topic got no feedback at all:

golang/go#30694 (comment)
@gopherbot
Copy link
Contributor

Change https://golang.org/cl/181260 mentions this issue: net/http: support BaseContext & ConnContext for http2 Server

@gopherbot
Copy link
Contributor

Change https://golang.org/cl/181259 mentions this issue: http2: support getting the Server connection's base context from net/http

gopherbot pushed a commit to golang/net that referenced this issue Jun 7, 2019
…http

This is the x/net/http2 half of the fix. The net/http half is in CL 181260.

Updates golang/go#32476
Updates golang/go#30694

Change-Id: Ic25c678dad99acc4ae8d679384d9e9a38dc1291c
Reviewed-on: https://go-review.googlesource.com/c/net/+/181259
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
gopherbot pushed a commit that referenced this issue Jun 7, 2019
This is the net/http half of #32476. This supplies the method needed
by the other half in x/net/http2 in the already-submitted CL 181259,
which this CL also bundles in h2_bundle.go.

Thanks to Tom Thorogood (@tmthrgd) for the bug report and test.

Fixes #32476
Updates #30694

Change-Id: I79d2a280e486fbf75d116f6695fd3abb61278765
Reviewed-on: https://go-review.googlesource.com/c/go/+/181260
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
gnanda17 added a commit to lifepod-solutions/go that referenced this issue Aug 27, 2019
gnanda17 added a commit to lifepod-solutions/go that referenced this issue Aug 27, 2019
@golang golang locked and limited conversation to collaborators Jun 6, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge NeedsFix The path to resolution is known, but the work has not been done. Proposal Proposal-Accepted
Projects
None yet
Development

No branches or pull requests

7 participants