[HN Gopher] Life of an HTTP request in a Go server (2017)
       ___________________________________________________________________
        
       Life of an HTTP request in a Go server (2017)
        
       Author : ibobev
       Score  : 78 points
       Date   : 2021-02-21 06:11 UTC (16 hours ago)
        
 (HTM) web link (eli.thegreenplace.net)
 (TXT) w3m dump (eli.thegreenplace.net)
        
       | eliben wrote:
       | Thanks for posting this :)
       | 
       | Not sure why the (2017) tag though, this post is from yesterday!
        
       | CraigJPerry wrote:
       | There's a fascinating, if rather long, dive into Go HTTP
       | processing issues (nodejs too) at
       | https://fasterthanli.me/articles/aiming-for-correctness-with...
       | 
       | It's a long read but it's one of the most enjoyable things i've
       | read in ages.
        
       | parley wrote:
       | I am not a Go expert so please correct me if I'm wrong, but...
       | The fact that the stdlib HTTP utils in Go recover from panics in
       | handlers and provide no way of changing this via easy setting is
       | one of the things that really annoy me. Seemingly, they consider
       | it a backwards compatibility break since it's a behavior change
       | that would affect all existing middleware you try to use (see
       | e.g. https://github.com/golang/go/issues/16542). You need to
       | provide your own error handling middleware (of which several
       | exist).
       | 
       | A beginner needs to be very careful and read the docs to catch
       | e.g. this line:
       | 
       | "If ServeHTTP panics, the server (the caller of ServeHTTP)
       | assumes that the effect of the panic was isolated to the active
       | request. It recovers the panic, logs a stack trace to the server
       | error log, and either closes the network connection or sends an
       | HTTP/2 RST_STREAM, depending on the HTTP protocol. To abort a
       | handler so the client sees an interrupted response but the server
       | doesn't log an error, panic with the value ErrAbortHandler. "
       | 
       | I think that is an unintuitive assumption that makes for brittle
       | software. Also, the fact that Go kind of blesses the use of
       | panics internally in a library (e.g. "The convention in the Go
       | libraries is that even when a package uses panic internally, its
       | external API still presents explicit error return values.",
       | https://blog.golang.org/defer-panic-and-recover) makes for even
       | more brittle software, as predicting what state a control flow
       | interruption like panic through a call stack leaves your data in
       | can be challenging and require great care to avoid invariants
       | being violated. Last I checked, e.g. Rust did not allow the reuse
       | of data owned by a panicking call stack without explicitly
       | asserting that it is still considered valid.
       | 
       | I guess I'm showing my bias for languages that make it harder to
       | make mistakes, but I don't like brittle stuff like encouraging
       | panic-like control flow for non-exceptional situations, implicit
       | reuse of "panicked data", missing non-exhaustive match on enums,
       | missing enums altogether, missing sum types and pattern matching
       | making everything from detailed error management to proper state
       | representation more brittle, etc etc. I'll stop there and not get
       | into the rest of the stuff.
       | 
       | Go is one of the languages my employer pays me to write and I
       | will say that I have a significantly higher appreciation for it
       | now than when I started (it IS very beginner friendly and very
       | ergonomic in general), but I wish it would help me more to write
       | really robust and correct software.
        
         | xyzzy_plugh wrote:
         | This is a pretty disingenuous criticism.
         | 
         | > The fact that the stdlib HTTP utils in Go recover from panics
         | in handlers and provide no way of changing this
         | 
         | It's comically easy to just wrap the root handler, catch panics
         | yourself, and explode.
         | 
         | > You need to provide your own error handling middleware (of
         | which several exist).
         | 
         | And you need to provide your own handlers anyways, which seems
         | reasonable to me, unless you're against writing code?
         | 
         | > A beginner needs to be very careful and read the docs to
         | catch e.g. this line:
         | 
         | Yes you need to read the docs to understand what a function
         | does. This does not require being very careful where I'm from.
         | 
         | > Also, the fact that Go kind of blesses the use of panics
         | internally in a library ... makes for even more brittle
         | software, as predicting what state a control flow interruption
         | like panic through a call stack leaves your data in can be
         | challenging and require great care to avoid invariants being
         | violated.
         | 
         | They make this pretty clear, that you shouldn't _leak_ the
         | panic, in which case it 's just an implementation detail. If
         | your library is brittle, that's on the implementer. You don't
         | have to use panics for control flow. It's just something you
         | can do. A tool. Sure, maybe it's sharp.
         | 
         | > I don't like brittle stuff like encouraging panic-like
         | control flow for non-exceptional situations
         | 
         | I've never seen anything remotely _encouraging_ use of panic
         | for control flow. The blog post even states it 's uncommon and
         | unusual. I don't know how you interpreted that blog post, which
         | reads to me as "here's how defer works", to being "you should
         | use panic for control flow whenever possible."
        
           | parley wrote:
           | I'm not a native English speaker, so I had to look up a
           | definition of disingenuous to make sure I didn't
           | misunderstand you. I promise you I'm trying to be candid with
           | my own opinion, and not trying to deceive in any way. What
           | would my nefarious purpose be? I'm just stating my opinion.
           | Whether something is brittle or not is an opinion within a
           | range to me, not an absolute. Perhaps my English is just
           | poor. I'm sorry if you genuinely feel I was being
           | disingenuous.
           | 
           | > It's comically easy to just wrap the root handler, catch
           | panics yourself, and explode. > And you need to provide your
           | own handlers anyways, which seems reasonable to me, unless
           | you're against writing code? > Yes you need to read the docs
           | to understand what a function does. This does not require
           | being very careful where I'm from.
           | 
           | Yes, it's "comically" easy to do so if you know that you need
           | to do it. I do it. I didn't say it was hard to do. But Go
           | prides itself on being beginner friendly, having consistent
           | behaviors and not having many ways of doing the same thing.
           | The main function doesn't automatically recover from panics.
           | Spawned goroutines don't automatically recover from panics. I
           | feel like it wouldn't be outrageous to consider this an
           | expected behavior for someone writing Go code, as very few
           | libraries to my knowledge recover panics they didn't start
           | themselves. The behavior of the stdlib HTTP utils diverge
           | from that expected behavior. I think there are tons of
           | developers in every language who don't necessarily scour the
           | docs but assume consistency with some behaviors that seem
           | basic and logical. Again, opinion, if that wasn't clear.
           | 
           | > They make this pretty clear, that you shouldn't leak the
           | panic, in which case it's just an implementation detail. If
           | your library is brittle, that's on the implementer. You don't
           | have to use panics for control flow. It's just something you
           | can do. A tool. Sure, maybe it's sharp.
           | 
           | Yes, they make it clear how it should be used if it is used,
           | and I didn't claim otherwise. I also didn't claim that
           | leaking it was the problem, but instead that maintaining
           | invariants in the data of the library using the mechanism can
           | be a hard thing to do, and whenever something is hard to do
           | right it can contribute to brittle software.
           | 
           | > I've never seen anything remotely encouraging use of panic
           | for control flow. The blog post even states it's uncommon and
           | unusual. I don't know how you interpreted that blog post,
           | which reads to me as "here's how defer works", to being "you
           | should use panic for control flow whenever possible."
           | 
           | Yes, I will meet you half way here. :) This particular blog
           | post doesn't encourage it, and at the moment I can't other
           | any of the other places I've seen it. The blog post gives an
           | example of Go stdlib using it, and then describes how to use
           | it in a library if it is used. My personal preference would
           | be to offer more caution or perhaps even discouraging in that
           | blog post, but again that is personal opinion.
           | 
           | Summing up, I think Go is a language that both strives to be
           | and is beginner friendly. Diverging from consistency in basic
           | behaviors in the stdlib is not great, sharp tools not labeled
           | as such is not great, etc, in my opinion. I know we don't
           | agree and that's perfectly fine. I appreciate your reply. But
           | I'm not trying to be disingenuous.
        
         | pjmlp wrote:
         | I fail to see the problem, Java servlets and ASP.NET handlers
         | do exactly the same by default.
        
           | parley wrote:
           | I would offer the same criticism of them, as it's the
           | behavior in general that I don't prefer. But of course it's
           | perfectly fine to disagree, this is all about preferences. I
           | like fail fast, being required to handle errors and my tools
           | (including programming languages) to very clearly help me
           | identify where errors can occur.
        
             | matwood wrote:
             | Maybe I'm confused, but are you preferring the entire
             | server shutdown if any request causes a panic?
        
               | parley wrote:
               | This was actually more challenging to respond to than I
               | thought it would be.
               | 
               | In Go (as the blog post that I linked alludes to): "The
               | convention in the Go libraries is that even when a
               | package uses panic internally, its external API still
               | presents explicit error return values."
               | 
               | However, it is not the expectation that libraries should
               | recover from panics they didn't start themselves. If they
               | did, it would be very hard to panic and actually have a
               | natural expectation that your application would exit due
               | to that panic, which is how most Go code behaves and
               | expects to behave.
               | 
               | As I wrote in another reply, neither the main
               | goroutine/function nor spawned goroutines automatically
               | recover from panics. They DO shut down the entire server
               | if any code in them panics (provided that no boiler plate
               | recovery is performed at the root of the call stack,
               | which in itself would make it very, very hard to reason
               | about the consistency of the data that might have been
               | touched before panicking at any one of countless of
               | operations in the code in the call stack).
               | 
               | Therefore, one might also argue: Should the entire server
               | shutdown if any worker thread causes a panic? I do agree
               | that it is more plausible for an HTTP request thread to
               | do so, but not enough to change such a basic behavior. Go
               | doesn't allow to register a global panic handler to be
               | able to perhaps recover but also log panics in a
               | consistent way, such that it would be applied
               | consistently across your entire process and customizable
               | to the preferences of the developer as to their chosen
               | trade off between "fail fast/never continuing processing
               | in the face of unexpected programming errors" and "an
               | unexpected programming error occurred but I still want to
               | continue executing and hope that nothing broke in my
               | application".
               | 
               | And I do acknowledge that different
               | developers/organizations would want to make that trade
               | off differently, but at present it is not very convenient
               | to do consistently. The Go creators chose not to allow
               | global panic handles (there are a bunch of discussions
               | about it on Google Groups and similar, and I do agree
               | with some of the arguments in them).
               | 
               | Some people (myself included) might prefer that the
               | application fails and whatever orchestration manages this
               | application triggers an alarm with operations staff,
               | developers, etc, without instead risking that an
               | application keeps running and perhaps due to some
               | invariant now being violated and data inconsistent keeps
               | making mistakes, perhaps serious mistakes.
               | 
               | This of course depends a lot on what kind of application
               | you're building and how important this is, how much
               | uptime for partial (but potentially buggy) functionality
               | weighs against never risking serious mistakes. I tend to
               | think that the correctness of most software in the world
               | is actually important these days, but I fully admit
               | there's a scale. Go however is being used to build all
               | kinds of software these days.
               | 
               | If one doesn't like that strategy, and wants to build
               | software that recovers in other fashions then perhaps one
               | should have a look at Erlang and its process supervisor
               | trees, or other systems with other trade offs.
               | 
               | It is a genuinely hard question, I admit that. I just
               | don't think Go's stdlib in this case chooses a position
               | on that trade off that I like, that's all. It's all
               | opinion, and we're all entitled to them. Thanks for
               | asking, and forcing me to put thoughts into words!
        
       | bullen wrote:
       | If you need to lock when accessing memory between requests you
       | are using the wrong language:
       | 
       | https://eli.thegreenplace.net/2019/on-concurrency-in-go-http...
       | 
       | Erlang, Rust and Go can only be used to solve embarrassingly
       | parallel problems.
        
         | kevincox wrote:
         | I don't see the problem here. You need to have serial access to
         | the counter. You could use a single-threaded webserver but
         | there is a lot more to that request than actually incrementing
         | the counter. It still makes sense in most cases to use a
         | threaded webserver and lock on incrementing the counter.
         | (Especially if you are using a concurrent hashtable so that you
         | can increment multiple counters in parallel)
        
         | jamra wrote:
         | You could use the atomic package and forego the lock. You can
         | also have a go routine with a channel that receives the counter
         | messages. No need for a lock there.
         | 
         | But use it if you really need it. Likely that's not your
         | bottleneck.
        
         | Jtsummers wrote:
         | > Erlang, Rust and Go can only be used to solve embarrassingly
         | parallel problems.
         | 
         | I'm pretty sure each of those have been used for other kinds of
         | parallelism than just embarrassingly parallel problems.
        
         | fwip wrote:
         | Could you expand on this? Mutexes are not the only way to
         | protect shared state in Go.
         | 
         | What language constructs/patterns do you feel are better suited
         | to solving parallelism problems, and what languages exemplify
         | these techniques?
        
           | bullen wrote:
           | "While I'm on the topic of concurrency I should mention my
           | far too brief chat with Doug Lea. He commented that multi-
           | threaded Java these days far outperforms C, due to the memory
           | management and a garbage collector. If I recall correctly he
           | said "only 12 times faster than C means you haven't started
           | optimizing"." - Martin Fowler
           | https://martinfowler.com/bliki/OOPSLA2005.html
        
             | fwip wrote:
             | Go has both memory management and a quite performant
             | garbage collector, as you probably know.
             | 
             | Your article is from 2005, so I take it to mean that Java 6
             | was doing the interesting things you talk about - from my
             | recollection, the go-to at that time was
             | java.util.concurrent. (I haven't kept up with Java, so it
             | could still be state-of-the-art.)
             | 
             | https://www.sciencedirect.com/science/article/pii/S01676423
             | 0... - this paper describes the implementation as "The
             | heart of the framework is maintenance of queues of blocked
             | threads, which are restricted here to FIFO queues." If
             | you're not familiar, this is exactly what go channels are,
             | just baked into the language rather than in the standard
             | library.
        
               | bullen wrote:
               | http://nil.csail.mit.edu/6.824/2016/notes/gomem.pdf
        
               | pjmlp wrote:
               | The major differences being that java.util.concurrent in
               | Java 16 is a bit more powerful as the one in Java 6, Java
               | doesn't have one single GC rather multiple vendor
               | implementations to choose from, with virtual threads on
               | the horizon.
               | 
               | Additionally one benefit over Go, is that in Java
               | (likewise in .NET), the scheduler algorithm is
               | replaceable.
        
         | icholy wrote:
         | What does that even mean?
        
       ___________________________________________________________________
       (page generated 2021-02-21 23:01 UTC)