I've been digging into a software issue - figuring out just why the "global random" track feature (using ashuffle) tends to offer up the same series of "random" tracks each time it's used. I think I understand the problem now, and I've figured out a couple of possible workarounds/solutions.
ashuffle uses a standard software pseudo-random-number-generator function (the POSIX "rand()" function) to drive its selection of tracks. Like all such generators, rand() is completely deterministic - if you start it out in a known state it'll generate the same set of "random" numbers. ashuffle tries to avoid this, by deliberately "seeding" the function with a unique value each time the program starts up. ashuffle's choice for this value would work well on a standard PC, but not on a Pi running RuneAudio... because it uses the current value of the time.
Since the Pi doesn't have a hardware clock, it sets the kernel time-of-day counter to the same value each time it boots (the exact value apparently depends on the software build version). Since the RPi boots up quickly and with little timing variation from one boot to the next, the time-of-day counter (measured in seconds from the epoch, I believe) will usually be the same when ashuffle samples it, and so ashuffle's behavior becomes very predictable.
The RuneAudio distribution of Arch Linux does start up a daemon (ntpd.service) which attempts to set the clock from NTP servers on the Internet. However, this takes at least a few seconds - it's an operation that happens in the background - and it usually doesn't complete by the time that ashuffle starts up.
One possible solution to this is to ensure that a better attempt is made to set the system clock, before ashuffle starts up. This can be done by making a few changes to the system service definitions. One possible solution:
- "systemctl enable ntpdate.service". This enables a "one shot" time-setting service at boot, querying the time servers and setting the clock and exiting. It's done before the existing "ntpd.service" daemon is allowed to start, so the two will not conflict.
- Edit the "ashuffle.service" file. Add a "Wants: ntpdate.service" line, and add "ntpdate.service" to the "After:" line.
When the system boots, systemd will fire off the one-shot "ntpdate.service" to try to set the clock (it'll either succeed or fail in a few seconds) in parallel with most of the other service startups. The existing "ntpd.service" daemon will be held off until "ntpdate.service" has finished. Most of the runeaudio software will start up immediately, as usual. However, if you've enabled Global Random, then runeaudio will run a "systemctl start ashuffled.service" command. This service won't be allowed to actually start until the ntpdate.service has either succeeded (in which case the clock is set and ashufffle will get a good value for the time) or failed and timed out (in which case ashuffle will behave as it does today).
I don't know how long the timeout is.
Another approach would be to store the current time to a file on the SD card at shutdown, and then restore it (set the clock) early in the boot process. This could be done with a single service, which runs "before" ntpdate.service and ntpd.service. Its stop action would run
- Code: Select all
date +%m%d%y%H%M.%S > /my/magic/file
Its start action would check for the existence of that file, and then run
- Code: Select all
date $(cat /my/magic/file)
That solution has the advantage that it will work reasonably well even if there's no Internet connectivity - the clock will always move forwards between boots, even if it was never set correctly in the first place.
Yet another, entirely different approach would be to modify ashuffle to use a different method for seeding the rand() function, or use a different random number generator entirely (e.g. the Linux kernel /dev/urandom). One could add a new --seed option to the command line, to pass in a seed value, and then have a startup script figure out such a value and pass it in. The value doesn't have to be cryptographically strong, just different each time... the script could maintain a "boot counter" in a file on the SD card, and seed ashuffle with values like 1, 2, 3, ... on subsequent boots. Or, the script could do something like
- Code: Select all
cat /var/log/runeaudio/* | wc -c
and figure that the total sizes of the logs would vary a lot from boot to boot.
Using /dev/urandom is tempting, but I think risky. The Linux kernel entropy pool is notorious for being low in quality immediately after boot, because not enough has happened to trigger the built-in entropy sources (timing-related, mostly). It's possible to save the pool at shutdown and restore it on reboot, but I don't know if Arch does that, and it wouldn't be dependable if one doesn't remember to shut down cleanly.