The user presses a button which sets a hardware event flag. CPU wakes up from sleep, checks all event flags, handles them, clears the interrupt bits and goes back to sleep.
But using events like this requires a very tight integration between event producer and consumer, so I don't think this will translate well to distributed systems or microservices.
In a car there are many distributed ECUs(1) that communicate in an event driven system. It was tried to use cyclic communication. But all those attempts failed in the long run. Because cyclic communication would required that all the independent ECUs are synced to each other, which is a very hard problem. That is the reason why everybody moved away from cyclic communication.
That said to help the development in automotive a middleware have been developed and used to help the development of such event driven systems. You develop your functions and define how the signals are routed between those functions. The someone later/independent decides on how the functions are distributed on the different ECUs depending the available resources. The middleware then takes care of the correct routing of the signals. Everything is event driven.
(1) Electronic Control Units
There's cyclic communication by sending messages over CAN (if reliability doesn't matter that much) or FlexRay (if reliability matters), so if you examine the network, you'll typically see the same PDUs repeat in a cyclic manner. But an individual ECU will handle the bulk of its logic in an event-driven way. An interrupt will trigger for the incoming CAN frame (and that's an event-driven thing!) but a couple of layers up it will most likely just set a flag for what changed, and it will actually be taken care on a logical level when the relevant task's loop runs next time.
That is the definition of event driven.
The same is not only true for the CAN-based, and CAN FD-based communication, but also for the Automotive Ethernet-based communication.
There were tries with Time-triggered protocols like TTP/C (used by Daimler for exactly one model) and FlexRay which had cyclic communication. The communication cycle required that the ECUs are synced to the communication cycle because they needed to have to correct data available at exactly their time slot. If they missed the time slot, the data got marked as stale. The same problem on the receiving side.
Though I don't think tight integration between event producer and consumer is necessarily antithetical to microservice design. See the consumer-driven contract pattern, for example: https://www.martinfowler.com/articles/consumerDrivenContract...
Is this why so many gas pumps and ATMs have such horrible delays in their UI?
The GUI worked in a single thread, but your whole program didn't need to do that.
Being able to copy a file and type at the same time was a big deal.
That's not correct. You just needed to create a separate thread for the file operation. For some programmers that was a big deal indeed, the same could be said for some programming tools. But that wasn't a general case at all.
There were some ugly things, like the way file operations were treated down the OS level, but it wasn't impossible to make your application responsive. It wasn't even difficult... if you knew how to do it.
Some people wrote very inventive software to get around it.
In Windows there is a fundamental concept of the message loop.
The message loop is an obligatory section of code in every program that uses a graphical user interface under Microsoft Windows.
Windows programs that have a GUI are event-driven.
Note that WindowsNT is fully multithreaded, but there is a thread sitting there listening to the event loop still. (of course other threads can do other things).
https://en.wikipedia.org/wiki/Message_loop_in_Microsoft_Wind...
I am because I did a few multithreaded programs with it, including what the OP called "a big deal".
Not sure what's your point though.
Writing "my own" code for file copy was not really so low-level, it's what most people understood as programming at the time. Locating the file, opening it, using a descriptor and a loop to copy blocks through a buffer, closing the file, managing errors... that kind of thing.
If you wanted to do the file operation in the background and keep using the GUI for input, you did need to create a separate thread. But that wasn't some black art feat, you just read a book or searched Altavista for a snippet, if in a hurry.
How does that look like? Do you mean to learn how this is done with the CI of choice, create helper functions or are there concrete steps that you would recommend? I'm new to this and would appreciate any feedback.
In the pipeline case, a probably only has visibility to b, each service in the chain has ~7ms to provide its response.
In the fan out case, a is aware of b c and d, they can each take 25ms or more and a can still meet its deadline.
The way a sets its timeouts will vary depending on what's happening downstream.
This is a pedantic and fussy point of view. Nobody really does this, some pretend they do. But it can be important for overall performance.
Anyway, seems to make sense to plan for this timing-wise. Allows addressing and seeing performance bottlenecks. Thank you for spelling it out for me!
Micro-services is just sharding across a different axis (TM)
Proper concurrency support and real mutexes would be way better than having a single execution thread for all your computations that is shared with all the visual and human interaction computations. Plus, the data sharing models between the main thread and web workers is pretty crap for anything serious, so it's not even easy to get computations out of the main thread that don't need to be there.
The mostly single threaded / async model is a hassle sometimes. But I find it much easier to reason about because I know the rest of my program has essentially paused during the current callback’s execution.
In Clojure/JVM core.async has less problems though.
Isn't that normal? A single event/drawing loop. Would there be an advantage to two threads drawing? Sounds pretty novel.
Windows 95. Old style gui programming meant sitting in a loop, waiting for the next event, then handling it. You type a letter, there's a case switch, and the next character is rendered on the screen. Being able to copy a file and type at the same time was a big deal. You'd experience the dead letter queue when you moved a window while the OS was handling a device, and the window would sort of smear across the screen when the repaint events were dropped.
Concurrent programming is hard. State isolation from micro services helps a lot. but eventually you'll need to share state, and people try stuff like `add 1 to x`, but that has bugs, so they say, `if x == 7 add 1 to x` but that has bugs so they say, `my vector clock looks like foo. if your vector clock matches, add 1 to x, and give me back your updated vector clock` but now you've imposed a total order and have given up a lot of performance.
I'm blind to the actual problem you're facing. My default recommendation is to have a monorepo, and break out helpers for expensive tasks, no different than spinning up a thread or process on a big host. Have a plan for building pipelines a->b->c->d. also have a plan for fan out a->b & a->c & a->d
It has been widely observed there are no silver bullets. but there are regular bullets. Careful and thoughtful use can be a huge win. If you're in that exponential growth phase, it's duck tape and baling wire all the way, get used to everything being broken all the time. if you're not, take your time and plan out a few steps ahead. Operationalize a few micro services. Get comfortable with the coordination and monitoring. Learn to recover gracefully, and hopefully find a way to dodge that problem next time around.
Sorry this is hand wavy. I don't think you're missing anything. it's just hard. if you're stuck because it won't fit on 1X anymore, you've got to find a way to spread out the load.