hyprwm / hyprpaper

Hyprpaper is a blazing fast wayland wallpaper utility with IPC controls.
https://wiki.hyprland.org/Hypr-Ecosystem/hyprpaper/
BSD 3-Clause "New" or "Revised" License
761 stars 55 forks source link

Segfault if ipc requests sent too rapidly #45

Open 2e0byo opened 1 year ago

2e0byo commented 1 year ago

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

lenianiva commented 1 year ago

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.

vaxerski commented 1 year ago

try with 3596630a207a02a0035a0a178a1fdbf2a5f40a30

2e0byo commented 1 year ago

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 commented 1 year ago

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.

lenianiva commented 11 months ago

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.

ssvx commented 6 months ago

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.

lenianiva commented 1 week ago

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