perf(app): skip capture demultiplexing when hints are off
- commit
- cf52ba6c0b0fe6d65c68fa69fe3df9ee3265a471
- author
- brice · · 2026-10-19 15:34 UTC
- parent
- 3bb35a7
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
3 files · +15 -8
M internal/app/hintoptin_test.go +4 -0
| @@ -21,6 +21,8 @@ func TestHintPipesOffByDefault(t *testing.T) { | |||
| 21 | 21 | require.Nil(t, off.hintScans) | |
| 22 | 22 | require.Nil(t, off.hintResults) | |
| 23 | 23 | require.Nil(t, off.hintCaptured) | |
| 24 | + | // No demultiplexing: the outputs reply on the wayland channel itself. | |
| 25 | + | require.Equal(t, off.captured, off.capturedAll) | |
| 24 | 26 | ch := off.core() | |
| 25 | 27 | require.Nil(t, ch.HintScans) | |
| 26 | 28 | require.Nil(t, ch.Hints) | |
| @@ -28,6 +30,7 @@ func TestHintPipesOffByDefault(t *testing.T) { | |||
| 28 | 30 | require.NotNil(t, on.hintScans) | |
| 29 | 31 | require.NotNil(t, on.hintResults) | |
| 30 | 32 | require.NotNil(t, on.hintCaptured) | |
| 33 | + | require.NotEqual(t, on.captured, on.capturedAll) | |
| 31 | 34 | } | |
| 32 | 35 | ||
| 33 | 36 | func TestHintOffStartsNoHintGoroutine(t *testing.T) { | |
| @@ -43,6 +46,7 @@ func TestHintOffStartsNoHintGoroutine(t *testing.T) { | |||
| 43 | 46 | dump := string(buf[:runtime.Stack(buf, true)]) | |
| 44 | 47 | require.NotContains(t, dump, "adapters/hints.") | |
| 45 | 48 | require.NotContains(t, dump, "adapters/atspi.") | |
| 49 | + | require.NotContains(t, dump, "routeCaptured") | |
| 46 | 50 | select { | |
| 47 | 51 | case <-done: | |
| 48 | 52 | case <-time.After(3 * time.Second): | |
| @@ -21,6 +21,8 @@ func TestHintPipesOffByDefault(t *testing.T) { | |||||
| 21 | require.Nil(t, off.hintScans) | 21 | require.Nil(t, off.hintScans) | ||
| 22 | require.Nil(t, off.hintResults) | 22 | require.Nil(t, off.hintResults) | ||
| 23 | require.Nil(t, off.hintCaptured) | 23 | require.Nil(t, off.hintCaptured) | ||
| 24 | + | // No demultiplexing: the outputs reply on the wayland channel itself. | |||
| 25 | + | require.Equal(t, off.captured, off.capturedAll) | |||
| 24 | ch := off.core() | 26 | ch := off.core() | ||
| 25 | require.Nil(t, ch.HintScans) | 27 | require.Nil(t, ch.HintScans) | ||
| 26 | require.Nil(t, ch.Hints) | 28 | require.Nil(t, ch.Hints) | ||
| @@ -28,6 +30,7 @@ func TestHintPipesOffByDefault(t *testing.T) { | |||||
| 28 | require.NotNil(t, on.hintScans) | 30 | require.NotNil(t, on.hintScans) | ||
| 29 | require.NotNil(t, on.hintResults) | 31 | require.NotNil(t, on.hintResults) | ||
| 30 | require.NotNil(t, on.hintCaptured) | 32 | require.NotNil(t, on.hintCaptured) | ||
| 33 | + | require.NotEqual(t, on.captured, on.capturedAll) | |||
| 31 | } | 34 | } | ||
| 32 | 35 | ||||
| 33 | func TestHintOffStartsNoHintGoroutine(t *testing.T) { | 36 | func TestHintOffStartsNoHintGoroutine(t *testing.T) { | ||
| @@ -43,6 +46,7 @@ func TestHintOffStartsNoHintGoroutine(t *testing.T) { | |||||
| 43 | dump := string(buf[:runtime.Stack(buf, true)]) | 46 | dump := string(buf[:runtime.Stack(buf, true)]) | ||
| 44 | require.NotContains(t, dump, "adapters/hints.") | 47 | require.NotContains(t, dump, "adapters/hints.") | ||
| 45 | require.NotContains(t, dump, "adapters/atspi.") | 48 | require.NotContains(t, dump, "adapters/atspi.") | ||
| 49 | + | require.NotContains(t, dump, "routeCaptured") | |||
| 46 | select { | 50 | select { | ||
| 47 | case <-done: | 51 | case <-done: | ||
| 48 | case <-time.After(3 * time.Second): | 52 | case <-time.After(3 * time.Second): | ||
M internal/app/session.go +2 -3
| @@ -346,10 +346,9 @@ func (s *session) run(ctx context.Context, inject func(chan<- ports.InputEvent)) | |||
| 346 | 346 | log.Warn().Err(err).Msg("capture allowlist not watched, restart to apply changes") | |
| 347 | 347 | } | |
| 348 | 348 | }() | |
| 349 | - | workers.Add(1) | |
| 350 | - | go func() { defer workers.Done(); routeCaptured(ctx, p.capturedAll, p.captured, p.internalCaptured()) }() | |
| 351 | 349 | if p.hintScans != nil { | |
| 352 | - | workers.Add(1) | |
| 350 | + | workers.Add(2) | |
| 351 | + | go func() { defer workers.Done(); routeCaptured(ctx, p.capturedAll, p.captured, p.internalCaptured()) }() | |
| 353 | 352 | go func() { | |
| 354 | 353 | defer workers.Done() | |
| 355 | 354 | // Run only returns when ctx is done: nothing to report. | |
| @@ -346,10 +346,9 @@ func (s *session) run(ctx context.Context, inject func(chan<- ports.InputEvent)) | |||||
| 346 | log.Warn().Err(err).Msg("capture allowlist not watched, restart to apply changes") | 346 | log.Warn().Err(err).Msg("capture allowlist not watched, restart to apply changes") | ||
| 347 | } | 347 | } | ||
| 348 | }() | 348 | }() | ||
| 349 | - | workers.Add(1) | |||
| 350 | - | go func() { defer workers.Done(); routeCaptured(ctx, p.capturedAll, p.captured, p.internalCaptured()) }() | |||
| 351 | if p.hintScans != nil { | 349 | if p.hintScans != nil { | ||
| 352 | - | workers.Add(1) | 350 | + | workers.Add(2) |
| 351 | + | go func() { defer workers.Done(); routeCaptured(ctx, p.capturedAll, p.captured, p.internalCaptured()) }() | |||
| 353 | go func() { | 352 | go func() { | ||
| 354 | defer workers.Done() | 353 | defer workers.Done() | ||
| 355 | // Run only returns when ctx is done: nothing to report. | 354 | // Run only returns when ctx is done: nothing to report. | ||
M internal/app/wiring.go +9 -5
| @@ -53,9 +53,10 @@ type pipes struct { | |||
| 53 | 53 | captures chan ports.CaptureRequest | |
| 54 | 54 | // Every admitted capture owns one reply slot until routeCaptured takes | |
| 55 | 55 | // it. Keep this capacity tied to admission so output routing never waits. | |
| 56 | - | // The outputs reply on capturedAll; routeCaptured splits the replies by | |
| 56 | + | // With hint mode on, the outputs reply on capturedAll and routeCaptured splits the replies by | |
| 57 | 57 | // ports.CaptureIDInternal into captured (the wayland consumer) and | |
| 58 | - | // hintCaptured (internal consumers). | |
| 58 | + | // hintCaptured (internal consumers). With hint mode off, capturedAll is | |
| 59 | + | // captured itself and there is no routeCaptured. | |
| 59 | 60 | capturedAll chan ports.CaptureDone | |
| 60 | 61 | captured chan ports.CaptureDone | |
| 61 | 62 | // hintCaptured carries internal capture replies to the hints adapter. | |
| @@ -110,9 +111,8 @@ func newPipes(realSession, hint bool) *pipes { | |||
| 110 | 111 | ||
| 111 | 112 | frames: make(chan ports.OutputFrame, 8), | |
| 112 | 113 | ||
| 113 | - | captures: make(chan ports.CaptureRequest, ports.MaxCaptureInflight), | |
| 114 | - | capturedAll: make(chan ports.CaptureDone, ports.MaxCaptureInflight), | |
| 115 | - | captured: make(chan ports.CaptureDone, ports.MaxCaptureInflight), | |
| 114 | + | captures: make(chan ports.CaptureRequest, ports.MaxCaptureInflight), | |
| 115 | + | captured: make(chan ports.CaptureDone, ports.MaxCaptureInflight), | |
| 116 | 116 | ||
| 117 | 117 | outputFormats: make(chan ports.OutputFormats, 8), | |
| 118 | 118 | outputHeads: make(chan ports.OutputHeads, 8), | |
| @@ -126,7 +126,11 @@ func newPipes(realSession, hint bool) *pipes { | |||
| 126 | 126 | ||
| 127 | 127 | castCommands: make(chan ports.CastCommand, 64), | |
| 128 | 128 | } | |
| 129 | + | // Without hint mode every reply is for the wayland consumer: the outputs | |
| 130 | + | // reply on its channel directly and nothing demultiplexes. | |
| 131 | + | p.capturedAll = p.captured | |
| 129 | 132 | if hint { | |
| 133 | + | p.capturedAll = make(chan ports.CaptureDone, ports.MaxCaptureInflight) | |
| 130 | 134 | p.hintScans = make(chan ports.HintScanRequest, 1) | |
| 131 | 135 | p.hintResults = make(chan ports.HintScanResult, 1) | |
| 132 | 136 | p.hintCaptured = make(chan ports.CaptureDone, 1) | |
| @@ -53,9 +53,10 @@ type pipes struct { | |||||
| 53 | captures chan ports.CaptureRequest | 53 | captures chan ports.CaptureRequest | ||
| 54 | // Every admitted capture owns one reply slot until routeCaptured takes | 54 | // Every admitted capture owns one reply slot until routeCaptured takes | ||
| 55 | // it. Keep this capacity tied to admission so output routing never waits. | 55 | // it. Keep this capacity tied to admission so output routing never waits. | ||
| 56 | - | // The outputs reply on capturedAll; routeCaptured splits the replies by | 56 | + | // With hint mode on, the outputs reply on capturedAll and routeCaptured splits the replies by |
| 57 | // ports.CaptureIDInternal into captured (the wayland consumer) and | 57 | // ports.CaptureIDInternal into captured (the wayland consumer) and | ||
| 58 | - | // hintCaptured (internal consumers). | 58 | + | // hintCaptured (internal consumers). With hint mode off, capturedAll is |
| 59 | + | // captured itself and there is no routeCaptured. | |||
| 59 | capturedAll chan ports.CaptureDone | 60 | capturedAll chan ports.CaptureDone | ||
| 60 | captured chan ports.CaptureDone | 61 | captured chan ports.CaptureDone | ||
| 61 | // hintCaptured carries internal capture replies to the hints adapter. | 62 | // hintCaptured carries internal capture replies to the hints adapter. | ||
| @@ -110,9 +111,8 @@ func newPipes(realSession, hint bool) *pipes { | |||||
| 110 | 111 | ||||
| 111 | frames: make(chan ports.OutputFrame, 8), | 112 | frames: make(chan ports.OutputFrame, 8), | ||
| 112 | 113 | ||||
| 113 | - | captures: make(chan ports.CaptureRequest, ports.MaxCaptureInflight), | 114 | + | captures: make(chan ports.CaptureRequest, ports.MaxCaptureInflight), |
| 114 | - | capturedAll: make(chan ports.CaptureDone, ports.MaxCaptureInflight), | 115 | + | captured: make(chan ports.CaptureDone, ports.MaxCaptureInflight), |
| 115 | - | captured: make(chan ports.CaptureDone, ports.MaxCaptureInflight), | |||
| 116 | 116 | ||||
| 117 | outputFormats: make(chan ports.OutputFormats, 8), | 117 | outputFormats: make(chan ports.OutputFormats, 8), | ||
| 118 | outputHeads: make(chan ports.OutputHeads, 8), | 118 | outputHeads: make(chan ports.OutputHeads, 8), | ||
| @@ -126,7 +126,11 @@ func newPipes(realSession, hint bool) *pipes { | |||||
| 126 | 126 | ||||
| 127 | castCommands: make(chan ports.CastCommand, 64), | 127 | castCommands: make(chan ports.CastCommand, 64), | ||
| 128 | } | 128 | } | ||
| 129 | + | // Without hint mode every reply is for the wayland consumer: the outputs | |||
| 130 | + | // reply on its channel directly and nothing demultiplexes. | |||
| 131 | + | p.capturedAll = p.captured | |||
| 129 | if hint { | 132 | if hint { | ||
| 133 | + | p.capturedAll = make(chan ports.CaptureDone, ports.MaxCaptureInflight) | |||
| 130 | p.hintScans = make(chan ports.HintScanRequest, 1) | 134 | p.hintScans = make(chan ports.HintScanRequest, 1) | ||
| 131 | p.hintResults = make(chan ports.HintScanResult, 1) | 135 | p.hintResults = make(chan ports.HintScanResult, 1) | ||
| 132 | p.hintCaptured = make(chan ports.CaptureDone, 1) | 136 | p.hintCaptured = make(chan ports.CaptureDone, 1) | ||