Interesting approach. I often get to the same goal by using the replicated state machine pattern. Where all inputs to a system are recorded. Both methods seem to rely on designing your application in a very specific way to be able to replay inputs and deterministically get the same outputs.
In some senses, that is true of every scheme; you need to ensure you capture all non-determinism and that can be done with either more capturing or less non-determinism.
However, the restrictions for generic replay-based time-travel debugging is mostly just not using shared memory and, as a corollary, not using multiple threads in a process (multiple processes is okay). Deliberately architecting your system in the way described in the article is largely unnecessary as the overhead of these generic schemes is low, much less work, applies to most codebases that could even attempt deliberate re-architecture, and integrates well with existing tooling and visualizers.
You can even lower these restrictions further to include explicit shared memory if you record those accesses. And you can do everything if you just record all accesses. The overhead of each of these schemes increasing as the amount of recording needed to capture these forms of non-determinism increases.
I had huge success writing a trading system where everything went through the same `on_event(Inputs) -> Outputs` function of the core and a thin shell was translating everything to inputs and the outputs to actions. I actually had a handful of these components communicating via message passing.
This worked rather well as most of the input is async messages anyway, but building anything else this way feels very tiresome.
Usually two methods `onMessage(long timeNow, byte[] buf)` and `onTimer(long timeNow, int timerId)`.
All output sinks and a scheduler need to be passed in on construction of the application.
Then you can record all inputs to a file. Outputs don’t need to be recorded because the inputs can be replayed but it is useful for easier analysis when bugs happen.
I have even worked on systems where there were tools that you could paste recorded input and outputs to and they code generated the source code for a unit test. Super useful for reproducing issues quickly.
But you are spot on in that there is an overhead. For example, if you want to open a TCP socket and then read and write to it, you need to create a separate service and serialise all the inputs and outputs in a way that can be recorded and replayed.
I run Linux on my laptop and desktop and have a MacBook.
I wouldn't recommend it for your laptop unless you're an enthusiast. Suspend/sleep is still janky, power saving is poor and dock support is hit and miss depending on the day. And this is after applying a lot of tweaks.
For desktop it's fantastic and I would recommend. It's not without some issues but what OSes is actually without any issues.
As a ThinkPad Z13 user, I've had none of the issues you've described. I've been running Linux (Fedora + Arch) on it for three years now and it's been a first-class experience. Everything worked out-of-the-box, with literally zero issues. I can't think of a single thing that I'd want to improve, from a laptop user's perspective. For me, this is the definitive Linux laptop experience.
I had a similarly good experience with my previous laptop, an HP Elite Dragonfly. Only niggle was that not all bits of firmware was upgradable via fwupd, so I had to ocassionally boot into Windows to do the firmware updates. But other than that, I don't recollect any issues with it either, suspend/resume/power management all worked as good as Windows. In fact, battery life was better than Windows when I applied powertop's tweaks.
And I'm sure the laptop experience is equally good on "native" Linux laptops, such as the ones from System76, Slimbook, Tuxedo etc.
So personally, I wouldn't make a blanket statement like "I wouldn't recommend it for your laptop unless you're an enthusiast". To be clear, I'm not claiming that the issues you describe don't exist, but at least for laptops known for having official support for Linux, the experience should be good.
I have a Slimbook Elemental 15 (or some such) and it's crap. The keyboard has a backlight you can change the colour of... with an application which is only available on specific distros (it isn't even on the AUR) and even then half the time it doesn't work.
Its audio system is also fucked, if you plug in headphones tte thing will play from the headphones at the volume of the speakers. The system doesn't see the headphones, it only has a generic audio output and switching between speakers and headphones happens IN HARDWARE.
The battery life ain't great, though I don't know about hibernate 'cause I ain't wasting 32 gigs on swap. Suspend seems to work fine, though I don't know how much power it'd drain over the course of, say, a night.
Oh dear. I've been trying to get people to not use this feature for a while.
One thing that has bitten me in the past is that, if you declare your associative arrays within a function, that associative array is ALWAYS global.
Even if you declare it with `local -A` it will still be global.
Despite that, you cannot pass an associative array to a function by value. I say "by value" because while you can't call `foo ${associative_array}` and pick it up in foo with `local arg=$1, you can pass it "by reference" with `foo associative_array` and pick it up in foo with `local -n arg=$1`, but if you give the passed in dereferenced variable a name that is already used in the global scope, it will blow up, eg `local -n associative_array=$1`.
As a general rule for myself when writing bash, if I think one of my colleagues who has an passable knowledge of bash will need to get man pages out to figure out what my code is doing, the bash foo is too strong and it needs to be dumbed down or re-written in another language. Associative arrays almost always hit this bar.
I'm not seeing this local scope leak with bash 5.2.15. The below script works as I'd expect:
#!/bin/bash
declare -A map1=([x]=2)
echo "1. Global scope map1[x]: ${map1[x]}"
func1() {
echo " * Enter func1"
local -A map1
map1[x]=3
echo " Local scope map1[x]: ${map1[x]}"
}
func1
echo "2. Global scope map1[x]: ${map1[x]}"
outputting
1. Global scope map1[x]: 2
* Enter func1
Local scope map1[x]: 3
2. Global scope map1[x]: 2
The local scope leak seems to only happen when you drop down the call stack. See below how I can call func2 from the top level and it's fine, but if I call it from within func1, it will leak.
#!/bin/bash
declare -A map1=([x]=2)
echo "1. Global scope map1[x]: ${map1[x]}"
func1() {
echo " * Enter func1"
local -A map1
map1[x]=3
echo " Local scope map1[x]: ${map1[x]}"
func2
}
func2() {
echo " * Enter func2"
echo " Local scope map1[x]: ${map1[x]}"
}
func1
func2
echo "2. Global scope map1[x]: ${map1[x]}"
outputing:
1. Global scope map1[x]: 2
* Enter func1
Local scope map1[x]: 3
* Enter func2
Local scope map1[x]: 3
* Enter func2
Local scope map1[x]: 2
2. Global scope map1[x]: 2
UPDATE: I did a bit of exploration and it turns out ANY variable declared `local` is in the scope of a function lower down in the call stack. But if you declare a variable as `local` in a called function that shadows the name of a variable in a callee function, it will shadow the callee's name and reset the variable back to the vallee's value when the function returns. I have been writing bash for years and did not realise this is the case. It is even described in the man page:
When local is used within a function, it causes the variable name to have a visible scope restricted to that function and its children.
Thank you. You have taught me two things today. One is a bash feature I did not know existed. The second is a new reason to avoid writing complex bash.
I completed both parts of this on Coursera. It can be a very challenging course at times but it's thoroughly rewarding. One of the key takeaways was a working mental model of how the hardware and low level software that underpins your code works.
I've worked in applications where a lot of IO is required. If performance is something your application cares about then you'll probably end up using direct ByteBuffers which are off heap and you'll likely want to set a sensible value for: -XX:MaxDirectMemorySize
However if this value ever does get exceeded, you need some way of tracking down what allocations happened prior to your OutOfMemory exception.
Though the above article implies the sampling rate is once a second (I guess there's some cost to increasing that rate). Usually you won't be allocating direct memory on the reg since it's expensive to allocate and deallocate relative to heap memory so you kind of want to capture ALL allocations and deallocations. As such a sample based approach is not ideal due to possibly missing some data between samples.
> I've worked in applications where a lot of IO is required. If performance is something your application cares about then you'll probably end up using direct ByteBuffers which are off heap and you'll likely want to set a sensible value for: -XX:MaxDirectMemorySize
It's also worth noting that if your Xmx is larger than 32 GB, you can't use CompressedOOPs, which is a bad deal. Off-heap memory lets you skirt that limitation while still allocating >32GB.
Pre-allocating up front would also have been valid way of doing things.
For the specific use cases I have worked on though, I'm not sure it would have had any additional performance benefits. The specific class of systems I've worked on usually have only a few sockets which open at the start of a day and stay connected until the end of a day. Pre-allocating would make the socket opening process faster but that is not usually the part of an application life cycle that needs optimising. If there were lots of sockets opening and closing or the speed of opening and closing needed optimisation, then what you suggested would be a good idea.
reply