fix(config): cap Skia CPU threads to 8, add null checks in webui
- commit
- e8eb7675a447c687f76c3cfc2a014d188433c08f
- author
- brice · · 2025-12-28 19:37 UTC
- committer
- brice · · 2025-12-29 17:28 UTC
- parent
- 39cf1a7
- signature
- unsigned
3 files · +22 -3
M internal/infrastructure/config/profile.go +10 -1
| @@ -2,6 +2,12 @@ package config | |||
| 2 | 2 | ||
| 3 | 3 | import "runtime" | |
| 4 | 4 | ||
| 5 | + | const ( | |
| 6 | + | // maxSkiaCPUThreads is the maximum number of Skia CPU painting threads | |
| 7 | + | // supported by WebKitGTK (0-8 range). | |
| 8 | + | maxSkiaCPUThreads = 8 | |
| 9 | + | ) | |
| 10 | + | ||
| 5 | 11 | // ResolvedPerformanceSettings contains the computed performance settings | |
| 6 | 12 | // after profile resolution. These are the actual values to apply. | |
| 7 | 13 | type ResolvedPerformanceSettings struct { | |
| @@ -93,11 +99,14 @@ func resolveLiteProfile() ResolvedPerformanceSettings { | |||
| 93 | 99 | ||
| 94 | 100 | // resolveMaxProfile returns settings optimized for maximum responsiveness. | |
| 95 | 101 | func resolveMaxProfile() ResolvedPerformanceSettings { | |
| 96 | - | // Use half of available CPUs, minimum 4 | |
| 102 | + | // Use half of available CPUs, minimum 4, maximum 8 (WebKitGTK Skia limit) | |
| 97 | 103 | cpuThreads := runtime.NumCPU() / 2 | |
| 98 | 104 | if cpuThreads < 4 { | |
| 99 | 105 | cpuThreads = 4 | |
| 100 | 106 | } | |
| 107 | + | if cpuThreads > maxSkiaCPUThreads { | |
| 108 | + | cpuThreads = maxSkiaCPUThreads | |
| 109 | + | } | |
| 101 | 110 | ||
| 102 | 111 | return ResolvedPerformanceSettings{ | |
| 103 | 112 | SkiaCPUPaintingThreads: cpuThreads, | |
| @@ -2,6 +2,12 @@ package config | |||||
| 2 | 2 | ||||
| 3 | import "runtime" | 3 | import "runtime" | ||
| 4 | 4 | ||||
| 5 | + | const ( | |||
| 6 | + | // maxSkiaCPUThreads is the maximum number of Skia CPU painting threads | |||
| 7 | + | // supported by WebKitGTK (0-8 range). | |||
| 8 | + | maxSkiaCPUThreads = 8 | |||
| 9 | + | ) | |||
| 10 | + | ||||
| 5 | // ResolvedPerformanceSettings contains the computed performance settings | 11 | // ResolvedPerformanceSettings contains the computed performance settings | ||
| 6 | // after profile resolution. These are the actual values to apply. | 12 | // after profile resolution. These are the actual values to apply. | ||
| 7 | type ResolvedPerformanceSettings struct { | 13 | type ResolvedPerformanceSettings struct { | ||
| @@ -93,11 +99,14 @@ func resolveLiteProfile() ResolvedPerformanceSettings { | |||||
| 93 | 99 | ||||
| 94 | // resolveMaxProfile returns settings optimized for maximum responsiveness. | 100 | // resolveMaxProfile returns settings optimized for maximum responsiveness. | ||
| 95 | func resolveMaxProfile() ResolvedPerformanceSettings { | 101 | func resolveMaxProfile() ResolvedPerformanceSettings { | ||
| 96 | - | // Use half of available CPUs, minimum 4 | 102 | + | // Use half of available CPUs, minimum 4, maximum 8 (WebKitGTK Skia limit) |
| 97 | cpuThreads := runtime.NumCPU() / 2 | 103 | cpuThreads := runtime.NumCPU() / 2 | ||
| 98 | if cpuThreads < 4 { | 104 | if cpuThreads < 4 { | ||
| 99 | cpuThreads = 4 | 105 | cpuThreads = 4 | ||
| 100 | } | 106 | } | ||
| 107 | + | if cpuThreads > maxSkiaCPUThreads { | |||
| 108 | + | cpuThreads = maxSkiaCPUThreads | |||
| 109 | + | } | |||
| 101 | 110 | ||||
| 102 | return ResolvedPerformanceSettings{ | 111 | return ResolvedPerformanceSettings{ | ||
| 103 | SkiaCPUPaintingThreads: cpuThreads, | 112 | SkiaCPUPaintingThreads: cpuThreads, | ||
M internal/infrastructure/config/profile_test.go +4 -0
| @@ -75,10 +75,14 @@ func TestResolvePerformanceProfile_Max(t *testing.T) { | |||
| 75 | 75 | result := ResolvePerformanceProfile(cfg) | |
| 76 | 76 | ||
| 77 | 77 | // Max should have high resource allocation | |
| 78 | + | // CPU threads: NumCPU/2, clamped to [4, 8] range (WebKitGTK Skia limit) | |
| 78 | 79 | expectedCPUThreads := runtime.NumCPU() / 2 | |
| 79 | 80 | if expectedCPUThreads < 4 { | |
| 80 | 81 | expectedCPUThreads = 4 | |
| 81 | 82 | } | |
| 83 | + | if expectedCPUThreads > 8 { | |
| 84 | + | expectedCPUThreads = 8 | |
| 85 | + | } | |
| 82 | 86 | if result.SkiaCPUPaintingThreads != expectedCPUThreads { | |
| 83 | 87 | t.Errorf("expected SkiaCPUPaintingThreads=%d, got %d", expectedCPUThreads, result.SkiaCPUPaintingThreads) | |
| 84 | 88 | } | |
| @@ -75,10 +75,14 @@ func TestResolvePerformanceProfile_Max(t *testing.T) { | |||||
| 75 | result := ResolvePerformanceProfile(cfg) | 75 | result := ResolvePerformanceProfile(cfg) | ||
| 76 | 76 | ||||
| 77 | // Max should have high resource allocation | 77 | // Max should have high resource allocation | ||
| 78 | + | // CPU threads: NumCPU/2, clamped to [4, 8] range (WebKitGTK Skia limit) | |||
| 78 | expectedCPUThreads := runtime.NumCPU() / 2 | 79 | expectedCPUThreads := runtime.NumCPU() / 2 | ||
| 79 | if expectedCPUThreads < 4 { | 80 | if expectedCPUThreads < 4 { | ||
| 80 | expectedCPUThreads = 4 | 81 | expectedCPUThreads = 4 | ||
| 81 | } | 82 | } | ||
| 83 | + | if expectedCPUThreads > 8 { | |||
| 84 | + | expectedCPUThreads = 8 | |||
| 85 | + | } | |||
| 82 | if result.SkiaCPUPaintingThreads != expectedCPUThreads { | 86 | if result.SkiaCPUPaintingThreads != expectedCPUThreads { | ||
| 83 | t.Errorf("expected SkiaCPUPaintingThreads=%d, got %d", expectedCPUThreads, result.SkiaCPUPaintingThreads) | 87 | t.Errorf("expected SkiaCPUPaintingThreads=%d, got %d", expectedCPUThreads, result.SkiaCPUPaintingThreads) | ||
| 84 | } | 88 | } | ||
M webui/src/pages/ConfigPage.svelte +8 -2
| @@ -427,6 +427,7 @@ | |||
| 427 | 427 | </div> | |
| 428 | 428 | ||
| 429 | 429 | <!-- Profile selector --> | |
| 430 | + | {#if config.performance} | |
| 430 | 431 | <div class="space-y-4"> | |
| 431 | 432 | {#each PERFORMANCE_PROFILES as profile (profile.value)} | |
| 432 | 433 | <label | |
| @@ -447,9 +448,14 @@ | |||
| 447 | 448 | </label> | |
| 448 | 449 | {/each} | |
| 449 | 450 | </div> | |
| 451 | + | {:else} | |
| 452 | + | <div class="text-sm text-muted-foreground"> | |
| 453 | + | Performance settings not available. This may be due to a configuration error. | |
| 454 | + | </div> | |
| 455 | + | {/if} | |
| 450 | 456 | ||
| 451 | 457 | <!-- Custom profile note --> | |
| 452 | - | {#if config.performance.profile === "custom"} | |
| 458 | + | {#if config.performance?.profile === "custom"} | |
| 453 | 459 | <div class="rounded-md border border-border bg-muted/30 px-4 py-3 text-sm text-muted-foreground"> | |
| 454 | 460 | <p> | |
| 455 | 461 | Custom profile settings are configured in your <code class="rounded bg-muted px-1 py-0.5">config.toml</code> file. | |
| @@ -459,7 +465,7 @@ | |||
| 459 | 465 | {/if} | |
| 460 | 466 | ||
| 461 | 467 | <!-- Resolved values display --> | |
| 462 | - | {#if config.performance.resolved} | |
| 468 | + | {#if config.performance?.resolved} | |
| 463 | 469 | <div class="space-y-3"> | |
| 464 | 470 | <div class="text-sm font-semibold uppercase tracking-[0.2em] text-muted-foreground"> | |
| 465 | 471 | Resolved Settings (applied on restart) | |
| @@ -427,6 +427,7 @@ | |||||
| 427 | </div> | 427 | </div> | ||
| 428 | 428 | ||||
| 429 | <!-- Profile selector --> | 429 | <!-- Profile selector --> | ||
| 430 | + | {#if config.performance} | |||
| 430 | <div class="space-y-4"> | 431 | <div class="space-y-4"> | ||
| 431 | {#each PERFORMANCE_PROFILES as profile (profile.value)} | 432 | {#each PERFORMANCE_PROFILES as profile (profile.value)} | ||
| 432 | <label | 433 | <label | ||
| @@ -447,9 +448,14 @@ | |||||
| 447 | </label> | 448 | </label> | ||
| 448 | {/each} | 449 | {/each} | ||
| 449 | </div> | 450 | </div> | ||
| 451 | + | {:else} | |||
| 452 | + | <div class="text-sm text-muted-foreground"> | |||
| 453 | + | Performance settings not available. This may be due to a configuration error. | |||
| 454 | + | </div> | |||
| 455 | + | {/if} | |||
| 450 | 456 | ||||
| 451 | <!-- Custom profile note --> | 457 | <!-- Custom profile note --> | ||
| 452 | - | {#if config.performance.profile === "custom"} | 458 | + | {#if config.performance?.profile === "custom"} |
| 453 | <div class="rounded-md border border-border bg-muted/30 px-4 py-3 text-sm text-muted-foreground"> | 459 | <div class="rounded-md border border-border bg-muted/30 px-4 py-3 text-sm text-muted-foreground"> | ||
| 454 | <p> | 460 | <p> | ||
| 455 | Custom profile settings are configured in your <code class="rounded bg-muted px-1 py-0.5">config.toml</code> file. | 461 | Custom profile settings are configured in your <code class="rounded bg-muted px-1 py-0.5">config.toml</code> file. | ||
| @@ -459,7 +465,7 @@ | |||||
| 459 | {/if} | 465 | {/if} | ||
| 460 | 466 | ||||
| 461 | <!-- Resolved values display --> | 467 | <!-- Resolved values display --> | ||
| 462 | - | {#if config.performance.resolved} | 468 | + | {#if config.performance?.resolved} |
| 463 | <div class="space-y-3"> | 469 | <div class="space-y-3"> | ||
| 464 | <div class="text-sm font-semibold uppercase tracking-[0.2em] text-muted-foreground"> | 470 | <div class="text-sm font-semibold uppercase tracking-[0.2em] text-muted-foreground"> | ||
| 465 | Resolved Settings (applied on restart) | 471 | Resolved Settings (applied on restart) | ||