hyperion-project / hyperion.ng

The successor to Hyperion aka Hyperion Next Generation
https://hyperion-project.org/
MIT License
2.96k stars 373 forks source link

Crashes with SIGSEGV and SIGABRT #1448

Open tremby opened 2 years ago

tremby commented 2 years ago

Bug report

Steps to reproduce

Send a lot of color commands.

Here's my current code. It's using this visualizer as an external binary: https://github.com/karlstav/cava then modifying the data from that a little and turning it into commands for Hyperion.

Config for cava:

[general]
framerate = 40
autosens = 1
bars = 10

[input]
method = pulse
source = alsa_output.platform-soc_sound.stereo-fallback.monitor

[output]
method = raw
channels = stereo
raw_target = /dev/stdout
bit_format = 8bit

[smoothing]
integral = 5
gravity = 200

Node.js code (it's a work in progress):

import net from "net";
import { spawn } from "child_process";
import process from "process";

const PRIORITY = 5;

// Which bar should be displayed for each LED, in order of LEDs
const LED_MAP = [5, 6, 7, 8, 9, 0, 1, 2, 3, 4];

// Hue of each LED
const HUE_MAP = [0, 0.2, 0.4, 0.6, 0.8, 0.8, 0.6, 0.4, 0.2, 0];

const hyperion = net.createConnection({ port: 19444 }, () => {
        console.log("Connected to Hyperion");
});
hyperion.on("end", (data) => {
        console.log("Disconnected from Hyperion");
});

process.on("SIGINT", () => {
        hyperion.write(JSON.stringify({
                command: "clear",
                priority: PRIORITY,
        }) + "\n");
        hyperion.destroy();
        process.exit();
});

const cava = spawn("/home/pi/src/cava/cava", ["-p", "/home/pi/visualizer.conf"], {
        stdio: ["ignore", "pipe", "inherit"],
});

const pending = [];
let partial = null;

cava.stdout.on("data", (data) => {
        let pointer = 0;
        const totalLength = data.length;

        while (pointer < totalLength) {
                const missingBytes = partial == null ? 10 : 10 - partial.length;
                const remaining = totalLength - pointer;
                if (remaining >= missingBytes) {
                        pending.push(partial == null ? data.slice(pointer, missingBytes) : Buffer.concat([partial, data.slice(pointer, missingBytes)]));
                        pointer += missingBytes;
                        partial = null;
                        continue;
                }
                if (partial == null)
                        partial = data.slice(pointer);
                else
                        partial = Buffer.concat([partial, data.slice(pointer)]);
                pointer += remaining;
        }

        if (pending.length)
                setImmediate(update);
});

function update() {
        if (pending.length == 0) return;

        // Average all pending entries
        const averages = pending.reduce((acc, buffer) => {
                const intensities = [];
                const buflen = buffer.length;
                for (let i = 0; i < buflen; i++)
                        intensities.push(buffer.readUInt8(i));
                return acc.map((sum, i) => sum + intensities[i]);
        }, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]).map((sum) => sum / pending.length);

        // Empty pending data
        pending.length = 0;

        const values = [];
        for (const [ledIndex, sampleIndex] of LED_MAP.entries()) {
                const color = hsltorgb(HUE_MAP[ledIndex], 1, (averages[sampleIndex] / 255) ** 2);
                values.push(...color);
        }

        hyperion.write(JSON.stringify({
                command: "color",
                color: values,
                priority: PRIORITY,
                origin: "visualizer.mjs",
        }) + "\n");
}

// h 0 through 1
// s 0 through 1
// l 0 through 1
function hsltorgb(h, s, l) {
        const c = (1 - Math.abs(2 * l - 1)) * s;
        const hh = h * 6;
        const x = c * (1 - Math.abs(hh % 2 - 1));
        const p = Math.floor(hh);
        const m = Math.abs(l - c / 2);

        const rgb = (
                p === 0 ? [c, x, 0] :
                p === 1 ? [x, c, 0] :
                p === 2 ? [0, c, x] :
                p === 3 ? [0, x, c] :
                p === 4 ? [x, 0, c] :
                p === 5 ? [c, 0, x] :
                [0, 0, 0]
        );
        return [
                Math.round(255 * (rgb[0] + m)),
                Math.round(255 * (rgb[1] + m)),
                Math.round(255 * (rgb[2] + m)),
        ];
}

What is expected?

No crash.

What is actually happening?

Within a couple of minutes, sometimes much faster, Hyperion crashes.

Samples from my syslog:

Mar 24 20:30:45 loungebox hyperiond[16843]: Hyperion caught signal :SIGSEGV
Mar 24 20:30:45 loungebox hyperiond[16843]: 2022-03-24T20:30:45.346  CORE                  : <ERROR> #011/lib/arm-linux-gnueabihf/libc.so.6(__default_rt_sa_restorer+0) [0xb5461120]
Mar 24 20:30:45 loungebox [16843]: #011/lib/arm-linux-gnueabihf/libc.so.6(__default_rt_sa_restorer+0) [0xb5461120]
Mar 24 20:30:45 loungebox systemd[1]: hyperion@pi.service: Main process exited, code=killed, status=11/SEGV
Mar 24 20:30:45 loungebox systemd[1]: hyperion@pi.service: Failed with result 'signal'.
Mar 24 20:57:43 loungebox hyperiond[4192]: Hyperion caught signal :SIGSEGV
Mar 24 20:57:43 loungebox hyperiond[4192]: 2022-03-24T20:57:43.921  CORE                  : <ERROR> #011/lib/arm-linux-gnueabihf/libc.so.6(__default_rt_sa_restorer+0) [0xb5406120]
Mar 24 20:57:43 loungebox [4192]: #011/lib/arm-linux-gnueabihf/libc.so.6(__default_rt_sa_restorer+0) [0xb5406120]
Mar 24 20:57:43 loungebox systemd[1]: hyperion@pi.service: Main process exited, code=killed, status=11/SEGV
Mar 24 20:57:43 loungebox systemd[1]: hyperion@pi.service: Failed with result 'signal'.
Mar 24 20:59:53 loungebox hyperiond[6938]: double free or corruption (!prev)
Mar 24 20:59:53 loungebox hyperiond[6938]: Hyperion caught signal :SIGABRT
Mar 24 20:59:53 loungebox hyperiond[6938]: 2022-03-24T20:59:53.210  CORE                  : <ERROR> #011/lib/arm-linux-gnueabihf/libc.so.6(__default_rt_sa_restorer+0) [0xb5421120]
Mar 24 20:59:53 loungebox [6938]: #011/lib/arm-linux-gnueabihf/libc.so.6(__default_rt_sa_restorer+0) [0xb5421120]
Mar 24 20:59:53 loungebox systemd[1]: hyperion@pi.service: Main process exited, code=killed, status=6/ABRT

System


Hyperion Server: 
- Build:           (HEAD detached at 2.0.12) (GitHub-dc6aa4d/df14958-1637501177)
- Build time:      Nov 21 2021 17:25:08
- Git Remote:      https://github.com/hyperion-project/hyperion.ng
- Version:         2.0.12
- UI Lang:         en (BrowserLang: en)
- UI Access:       default
- Avail Capt:      dispmanx,v4l2,framebuffer,qt
- Config path:     /home/pi/.hyperion
- Database:        read/write

Hyperion Server OS: 
- Distribution:   Raspbian GNU/Linux 10 (buster)
- Architecture:   arm
- CPU Model:      ARMv7 Processor rev 3 (v7l)
- CPU Type:       Raspberry Pi 4 Model B Rev 1.4
- CPU Revision:   b03114
- CPU Hardware:   BCM2711
- Kernel:         linux (5.10.103-v7l+ (WS: 32))
- Root/Admin:     false
- Qt Version:     5.11.3
- Python Version: 3.7.3
- Browser:        Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:98.0) Gecko/20100101 Firefox/98.0
- ```
Lord-Grey commented 1 year ago

Sorry, for only coming back now. To which extend does the situation occur, if you handle the responses per request properly ? Currently, you are just flooding requests without reading responses (that is not how the API works)

tremby commented 1 year ago

I didn't know I needed to read responses -- I don't much care about them in this case and I didn't see anything in the docs saying I need to so it didn't occur to me. So do I understand right that after every write I need to read until there's nothing else to read?