[HN Gopher] The Elephant in the Event Loop
___________________________________________________________________
The Elephant in the Event Loop
Author : mooreds
Score : 14 points
Date : 2022-03-17 03:41 UTC (2 days ago)
(HTM) web link (gashamola.com)
(TXT) w3m dump (gashamola.com)
| bastawhiz wrote:
| What's the point? Surely this isn't an indictment of JavaScript:
| plenty of languages have concurrency and share the same problems.
| Is it a warning? It doesn't follow up on the cause of the bug at
| Facebook that it mentions. It doesn't mention any specific
| examples at all. It doesn't offer any advice other than "there
| are synchronization primitives" (which it doesn't list) and
| "using synchronization primitives isn't enough" (without
| explaining why or what to do).
|
| Above all else, this has nothing to do with the event loop: these
| are problems with concurrency, not the runtime.
| brundolf wrote:
| I think the idea is "many JS programmers, especially ones who
| haven't really used anything else, may not be used to thinking
| about concurrency problems at all". This seems valid to me.
| Async/await in particular is all about helping you forget that
| you're writing asynchronous code; it can be a real foot-gun.
|
| That said, in my JS experience this is very rarely a problem
| because it means you have global state that multiple pieces of
| concurrent code are accessing and mutating at different points
| over the course of their logic; if I saw this happening at all,
| it would be a huge code smell, whether there's a known bug or
| not. Still, it's good for people to be aware of the possibility
| and what to do about it.
| bastawhiz wrote:
| > is all about helping you forget that you're writing
| asynchronous code
|
| I get what you're saying but you literally need to type out
| "async" and "await" to use it. I'm not sure what else they
| could do to force you to consider that it has a temporal
| component.
|
| My issue is that this article 1) doesn't actually mention any
| specific problems 2) doesn't show or discuss any examples at
| all 3) doesn't offer any advice about what to do. What is a
| programmer supposed to take away from this, other than
| general FUD?
| brundolf wrote:
| > I'm not sure what else they could do to force you to
| consider that it has a temporal component.
|
| I'm not saying they could have done it a better way
| necessarily, just that these kinds of problems are much
| more obvious in a callback-based style
|
| > What is a programmer supposed to take away from this,
| other than general FUD?
|
| A ping on their radar about what might be going wrong next
| time they encounter a bug like this. And/or, a topic to go
| read up on if this is all new and surprising to them.
| eyelidlessness wrote:
| Agree the post is low content, but we can still take it as a
| discussion prompt. The author mentions concurrency problems, and
| rightly attributed the problem to concurrent manipulation of
| shared state. While the author addresses this with a technique--
| locks--commonly used in other concurrent systems such as threads,
| there's another solution just sitting there begging to be
| noticed: don't share state, or if you must share state make
| atomic changes.
|
| This solution is available in many forms (with significant
| conceptual overlap):
|
| - Pure functional programming
|
| - Immutable (sometimes persistent) data structures
|
| - Reducers
|
| - Reactive computation
|
| - Software transactional memory
|
| Plenty more I'm sure. This isn't to say any or even all of them
| are a silver bullet, and each makes some trade offs (compute
| performance, memory overhead, ergonomics/DX) _to some degree_.
| But in quite a lot of cases, those trade offs are worth the
| benefit of not having to reason about shared state, race
| conditions, concurrent consistency _at all_. And they can also
| help you produce more maintainable and testable code.
| horsawlarway wrote:
| This is basically content free, and what content is there is
| mostly incorrect.
|
| Yes - shared data can be modified in between events on the event
| loop. No - this is not unexpected, or really a problem. Events
| are essentially atomic operations, and reasoning about them is
| _much_ easier than equivalent reasoning in paradigms that allow
| pre-empting at any point (ex - threads).
|
| And that single paragraph has more details than the entire
| article, since "you can find it elsewhere" according to the
| source...
| klabb3 wrote:
| Concurrent programming is much more difficult and also come in
| numerous paradigms/flavors which unfortunately lacks an agreed-
| upon taxonomy and terminology, so I'd forgive the author for
| making some confusing statements.
|
| Focusing on the "event loop" is a conceptual mistake imo, since
| it associates heavily to the (traditional) callback-oriented
| concurrency in JS. With callbacks, it is expected that shared
| state can be changed between registering and executing the
| callback.
|
| However, _with async_ , this line is blurred enough to mess
| with the programmer's intuition (this is of course subjective,
| but I've seen enough to know that folks are confused). Async
| functions are (mostly) syntactic sugar for callbacks, and as
| such each await-point is where things can change - yet people
| don't expect it. When I help folks get up to speed on async,
| the first thing I call out is that the await points have this
| important semantic property. Any use of mutable state outside
| an async function is a ticking time bomb.
|
| The other thing that I disagree with is the "logical mutex"
| synchronization in js. Async iterators would be preferable in
| most real world situations, but they're not even mentioned.
|
| Async is a blessing if it's used sanely, but a curse if it's
| not. It doesn't magically make problems go away, but it can
| make your code more concise.
| xg15 wrote:
| I found it somewhat interesting psychologically how much of a
| paradigm shift async/await seems to have caused, even though
| it's really just syntactical sugar.
|
| It changed absolutely zero about JavaScript's processing
| model - but because it "looks" like traditional multithreaded
| code, people seemed to start thinking about it like that,
| suddenly asking about synchronization, locks, etc.
|
| You could have asked all those questions already during
| callback-hell times, but I don't remember reading any blogs
| about that stuff back then.
| xg15 wrote:
| > _Yes - shared data can be modified in between events on the
| event loop._
|
| I think the problem is more that data can be modified between
| an event occurring and the handlers for _that very event_ being
| called.
|
| I think we often conflate the time an event occurs with the
| time the event's handler is executed - when in reality, there
| can be an arbitrary, nontrivial delay between them due to
| queuing.
|
| This means that any state invariants that an event might
| establish can already be long broken by the time your handler
| is called.
|
| E.g., suppose you write database code in NodeJS and query an
| external SQL database. Your NodeJS process is single-threaded,
| uses only a single database connection and there is no other
| process interacting with the database either. Then the
| following code might seem safe: let
| flaggedThings = await db.query("SELECT * FROM things WHERE flag
| = true"); process(flaggedThings) // assume each thing has
| the flag set to true
|
| However, it's not. After the DB query returned, there will be
| some internal call to Promise.resolve() which will advance the
| script past the await. However, the event of the DB query
| returning will not immediately cause this call - it will only
| cause a task for the call to be added to the event queue. But
| at the time the event loop dequeues and executes the task,
| other tasks, which were dequeued earlier, might already have
| changed the DB state.
|
| Therefore, the resultset may already be stale at the moment the
| script continues after the await.
| mikojan wrote:
| Concurrency in JavaScript is simple because there are usually
| just 1 or 2 event sources. Event sources that are mostly
| idling, too.
|
| It has nothing to do with threads v. no threads.
| horsawlarway wrote:
| No - concurrency is simple because the default state of
| running code is atomic. Variables CAN NOT change out from
| under you unless you explicitly place code in a callback, or
| await it. This is utterly untrue in threaded code, where
| _ANY_ line that is not explicitly guarded or explicitly an
| atomic operation, might have shared data change out from
| underneath it.
|
| There are usually several events sources - just in a normal
| application
|
| - Network calls
|
| - Mouse actions
|
| - Keyboard actions
|
| - Timers
|
| - awaits/promises/callbacks
|
| - Service workers
|
| - mutation observers
|
| - postMessage events
|
| - etc...
|
| Are all viable sources of events. Some of them will trigger
| events incredibly often (ex - a handler registered to
| mouseMove or Scroll) - it has nothing to do with limited
| event sources, and everything to do with _how_ the code
| executing the handlers is guaranteed to be atomic (unless it
| adds more events - such as a callback or an await)
| codeflo wrote:
| This is almost content-free, and the little that is there
| misuses/confuses terminology. Not HN worthy material to put it
| mildly.
___________________________________________________________________
(page generated 2022-03-19 23:02 UTC)