[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)