hyprpaper icon indicating copy to clipboard operation
hyprpaper copied to clipboard

Segfault if ipc requests sent too rapidly

Open 2e0byo opened this issue 2 years ago • 7 comments

If I send ipc requests too rapidly, hyprpaper segfaults in a variety of places (curiously, often in vnsprint), or gets a bus error.

I have a script to load a random wallpaper:

#!/usr/bin/python
from pathlib import Path
from random import choices
from subprocess import run

cmdbase = ["hyprctl", "hyprpaper"]

def load_backgrounds(mapping: dict[str, Path]):
    mapping: dict[str, str] = {k: str(v) for k, v in mapping.items()}
    for bg in mapping.values():
        run(cmdbase + ["preload", bg])
    for monitor, bg in mapping.items():
        run(cmdbase + ["wallpaper", f"{monitor},{bg}"])
    run(cmdbase + ["unload", "all"])


if __name__ == "__main__":
    imgs = Path("~/google-drive/Desktop Backgrounds").expanduser().resolve()
    monitors = {"DP-1", "DP-2"}
    extns = {".jpg", ".jpeg", ".png"}
    available = [x for x in imgs.glob("*") if x.suffix.lower() in extns]
    backgrounds = dict(zip(monitors, choices(available, k=len(monitors))))
    load_backgrounds(backgrounds)

If I run this in a loop at the terminal it will segfault after ~3 iterations. If I add a sleep, i.e.

while true; do
    random_wallpaper.py
    sleep 0.5

I can control the behaviour somewhat. With 0.5 it dies only occasionally, and usually with sigbus. With 0.1 it segfaults pretty quickly. Obviously looping over wallpapers isn't very useful, but I can't guarantee that a user won't hit the 'reload' button in quick succession.

For the time being I can work around it with a file lock. Should the socket code block until the load operation is finished?

version

Latest master with my case-insensitive patch, i.e. 0a85097519be78d4529a9a526fb8f6a52474ef5a

2e0byo avatar Feb 24 '23 11:02 2e0byo

Is there a temporary workaround to this problem? I switch wallpapers pretty frequently since I have a different one for each workspace, and hyprpaper would crash a couple times a day.

lenianiva avatar May 08 '23 03:05 lenianiva

try with 3596630a207a02a0035a0a178a1fdbf2a5f40a30

vaxerski avatar May 08 '23 18:05 vaxerski

Now we die normally:

zwlr_layer_surface_v1@24: error 0: wrong configure serial: 425141
[Thread 0x7ffff6a706c0 (LWP 2092053) exited]
[Inferior 1 (process 2092049) exited normally]

The particular serial changes so it could be memory corruption. If you tell me what to set a breakpoint on I'll see if I can get a proper backtrace.

This was a segfault before and I couldn't reproduce this time, so I think the segfault is cured, even if the problem has just moved.

2e0byo avatar May 09 '23 09:05 2e0byo

Is there a temporary workaround to this problem? I switch wallpapers pretty frequently since I have a different one for each workspace, and hyprpaper would crash a couple times a day.

You can set it up to restart e.g. with systemd. I think there's an example in the linked issue. OTOH I don't have any noticeable crashes with the script above ticking every 5 minutes after adding a delay and a lock:

from time import sleep

from filelock import FileLock
lock = FileLock("/tmp/hyprpaper.lock")


def load_backgrounds(mapping: dict[str, Path]):
    with lock:
       # as above
        sleep(0.5)

No need to use python if you don't want to, this is trivial in e.g. bash. Using a file lock might be the trick if you're flipping between workspaces fast enough to get there before hyprpaper has settled down from the last switch.

2e0byo avatar May 09 '23 09:05 2e0byo

Now we die normally:

zwlr_layer_surface_v1@24: error 0: wrong configure serial: 425141
[Thread 0x7ffff6a706c0 (LWP 2092053) exited]
[Inferior 1 (process 2092049) exited normally]

The particular serial changes so it could be memory corruption. If you tell me what to set a breakpoint on I'll see if I can get a proper backtrace.

This was a segfault before and I couldn't reproduce this time, so I think the segfault is cured, even if the problem has just moved.

I think there's a race condition here:

[Event] Configuring with serial 659167
[Event] Configuring with serial 659167 done
[LOG] configure for DP-1
Configuring with serial 659167
Configuring with serial 659167 finished
[LOG] Image data for DP-1: /home/aniva/.config/hypr/wallpapers/7.jpg at [-0.00, 0.00], scale: 1.00 (original image size: [3840, 2160])
[LOG] Submitting viewport dest size 3840x2160 for 60000d20
[Event] Configuring with serial 659171
[Event] Configuring with serial 659171 done
[LOG] configure for DP-1
[LOG] handlePreferredScale: 1.00 for 7f3e60000d20
[LOG] handlePreferredScale: 1.00 for 7f3e60000d20
Configuring with serial 659171
Configuring with serial 659171 finished
[LOG] Image data for DP-1: /home/aniva/.config/hypr/wallpapers/7.jpg at [-0.00, 0.00], scale: 1.00 (original image size: [3840, 2160])
[LOG] Submitting viewport dest size 3840x2160 for 60000d20
zwlr_layer_surface_v1@41: error 0: wrong configure serial: 659167

The [Event] messages are sent from the event handler and the Configure with serial ... messages are sent from CHyprpaper::recheckMonitor. It seems like the ack_configure event for 167 has not reached the client when the event for 171 is received by hyprpaper.

I'm not sure how can we solve this. Even setting the configuration serial as atomic doesn't mitigate this problem.

lenianiva avatar Sep 20 '23 03:09 lenianiva

Arrived here via search for the issue after i experienced it by accidentally hitting the [1] key when switching to workspace 2 by pressing [Super]+[2].

So by following the Hyprland wiki to set up different wallpapers for each workspace and then cycle them too fast: crash.

The claim "Hyprpaper is a blazing fast wallpaper utility" seems a bit weird given that this issue happens when you "switch too fast" ... 😅

Right now i use a script to subscribe to the Hyprland socket and only switch the wallpapers after a timeout during which no workspace change happens. It works but doesn't feel "blazing fast" tbf.

//edit: After reducing the DelaySec to 0.1 it feels relatively fast (enough)... so taking the chance to be laughed at, here's my script...

#!/bin/bash

# Script vars (no touch)
ScriptDir=$(dirname "$0")
TimerPid=0

# Config vars (do touch)
DelaySec=0.1
JsonFile="$ScriptDir/wallpapers.json"
CurFile="/tmp/wallpaperd"

# Save current terminal settings
SttyBackup=$(stty -g)
# Disable echoing of control characters (^C)
stty -echoctl

hyprctl hyprpaper unload all


# Load Wallpapers associative array from the JSON file
declare -A Wallpapers
FirstPaper=""
while IFS="=" read -r key value; do
	if [[ $FirstPaper == "" ]]; then
		FirstPaper=$value
	fi
	Wallpapers["$key"]="$value"
	echo "preloading $value"
	hyprctl hyprpaper preload "$value"
done < <(jq -r "to_entries|map(\"\(.key)=\(.value|tostring)\")|.[]" $JsonFile)

WorkspaceName=$(hyprctl -j activeworkspace | jq -r '.name')
echo "Current workspace '$WorkspaceName'"
WallpaperValue="${Wallpapers[$WorkspaceName]}"
if [[ -z $WallpaperValue ]]; then
	WallpaperValue="$FirstPaper"
fi
hyprctl hyprpaper wallpaper "DP-2,$WallpaperValue"
echo "$WallpaperValue" > "$CurFile"

# Handle termination / ctrl+c
CleanUp () {
	echo "Cleaning up..."
	if [ -f "$CurFile" ]; then
		rm $CurFile
	fi
	stty "$SttyBackup"
	exit 0
}

# Use trap to call CleanUp when a SIGINT is received
trap CleanUp SIGINT SIGTERM

# Function to change wallpaper
WallpaperSet () {
	local CurrentPaper=""
	local WorkspaceName="$1"
	local WallpaperValue="${Wallpapers[$WorkspaceName]}"
	if [ -f "$CurFile" ]; then
		CurrentPaper=$(< "$CurFile")
		echo "CurrentPaper is $CurrentPaper"
	fi

	if [[ -n "$WallpaperValue" && $WallpaperValue != $CurrentPaper ]]; then
		echo "Set wallpaper '$WallpaperValue'"
		echo "$WallpaperValue" > "$CurFile"
		# Use hyprctl to change the wallpaper
		hyprctl hyprpaper wallpaper "DP-2,$WallpaperValue"
	else
		echo "Same wallpaper, skipping..."
	fi
}

WallpaperCue () {
	local WorkspaceName="$1"
	[[ 0 < $TimerPid ]] && kill -0 $TimerPid > /dev/null 2>&1 && kill "$TimerPid"
	(sleep $DelaySec; WallpaperSet "$WorkspaceName") &
	TimerPid=$!
}

# Subscribe to Hyprland socket and process the stream
socat -u UNIX-CONNECT:/tmp/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock - | while read -r line; do
	#echo $line
	if [[ "$line" == "activespecial>>special:"* ]]; then
		SpecialWorkspace="${line#activespecial>>}"  # Removes everything up to and including ">>"
		SpecialWorkspace="${SpecialWorkspace%%,*}"  # Removes everything after and including ","
		echo "Special workspace name '$SpecialWorkspace'"
		WallpaperCue "$SpecialWorkspace"
	fi
	if [[ "$line" == "activespecial>>,"* ]]; then
		echo "Reset special workspace to '$WorkspaceName'"
		WallpaperCue "$WorkspaceName"
	fi
	if [[ "$line" == "workspace>>"* ]]; then
		WorkspaceName="${line#workspace>>}"
		echo "Workspace name '$WorkspaceName'"

		# Check if the workspace name exists in the Wallpapers array
		if [[ -n "${Wallpapers[$WorkspaceName]}" ]]; then
			WallpaperCue "$WorkspaceName"
		fi
	fi
done

For configuration it expects a wallpapers.json in the same directory (see config vars) in this format:

{
	"1": "~/Pictures/Wallpaper/something.png",
	"2": "~/Pictures/Wallpaper/somethingelse.webp",
	"special:magic": "~/Pictures/Wallpaper/whatever.jpg"
}

Replace the array keys with your workspace names.. special workplaces work too..

//edit2: I also tried to use hyprctl hyprpaper listactive for initialisation but it only says "invalid command". Not sure if i'm stupid or if it's bugged or if my version (hyprpaper 0.6.0-3) just doesn't feature it yet.

ssvx avatar Mar 05 '24 13:03 ssvx

The problem is still here on 0.7.0:

[LOG] configure for DP-1
[LOG] handlePreferredScale: 1.00 for 7c4c00003660
[LOG] handlePreferredScale: 1.00 for 7c4c00003660
[LOG] Image data for DP-1: /home/aniva/.config/hypr/wallpapers/5.jpg at [-0.00, 0.00], scale: 1.00 (original image size: [3840, 2160])
[LOG] Submitting viewport dest size 3840x2160 for 3660
zwlr_layer_surface_v1@814: error 0: wrong configure serial: 467542

fish: Job 1, 'hyprpaper &' has ended

This happens if I switch wallpapers twice within a fraction of a second

lenianiva avatar Aug 30 '24 16:08 lenianiva