1
0
forked from wrenn/wrenn
Files
wrenn-releases/envd/internal/services/process/handler/multiplex.go
pptx704 4b2ff279f7 Add terminal tab to capsule detail page and fix envd process lookup bugs
- Add multi-session Terminal tab with xterm.js (session tabs, close, reconnect)
- Keep terminal mounted across tab switches to preserve sessions
- Persist active tab in URL (?tab=terminal) so refresh stays on terminal
- Buffer keystrokes (50ms) to reduce per-character RPC overhead
- Add WebSocket auth via ?token= query param for browser WS connections
- Enable ws:true in Vite dev proxy for WebSocket support

envd fixes (pre-existing bugs exposed by multi-session terminals):
- Fix getProcess tag Range: inverted return values caused early stop when
  multiple tagged processes existed, making SendInput fail with "not found"
- Fix multiplexer deadlock: blocking send to cancelled fork's unbuffered
  channel prevented process cleanup. Now uses buffered channels (cap 64)
  with non-blocking fallback
2026-04-11 04:27:16 +06:00

80 lines
1.2 KiB
Go

// SPDX-License-Identifier: Apache-2.0
package handler
import (
"sync"
"sync/atomic"
)
type MultiplexedChannel[T any] struct {
Source chan T
channels []chan T
mu sync.RWMutex
exited atomic.Bool
}
func NewMultiplexedChannel[T any](buffer int) *MultiplexedChannel[T] {
c := &MultiplexedChannel[T]{
channels: nil,
Source: make(chan T, buffer),
}
go func() {
for v := range c.Source {
c.mu.RLock()
for _, cons := range c.channels {
select {
case cons <- v:
default:
// Consumer not reading — skip to prevent deadlock
}
}
c.mu.RUnlock()
}
c.exited.Store(true)
for _, cons := range c.channels {
close(cons)
}
}()
return c
}
func (m *MultiplexedChannel[T]) Fork() (chan T, func()) {
if m.exited.Load() {
ch := make(chan T)
close(ch)
return ch, func() {}
}
m.mu.Lock()
defer m.mu.Unlock()
consumer := make(chan T, 64)
m.channels = append(m.channels, consumer)
return consumer, func() {
m.remove(consumer)
}
}
func (m *MultiplexedChannel[T]) remove(consumer chan T) {
m.mu.Lock()
defer m.mu.Unlock()
for i, ch := range m.channels {
if ch == consumer {
m.channels = append(m.channels[:i], m.channels[i+1:]...)
return
}
}
}