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