feat(shell): add parallel bash shell subsystem auto-started per workspace
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
package shell
|
||||
|
||||
import "sync"
|
||||
|
||||
// Subscription is a consumer handle for a shell output stream.
|
||||
type Subscription interface {
|
||||
Output() <-chan []byte
|
||||
Close() error
|
||||
}
|
||||
|
||||
// subscription is a buffered, closable fan-out channel for a Session.
|
||||
type subscription struct {
|
||||
session *Session
|
||||
ch chan []byte
|
||||
once sync.Once
|
||||
mu sync.Mutex
|
||||
closed bool
|
||||
}
|
||||
|
||||
func newSubscription(sess *Session) *subscription {
|
||||
return &subscription{
|
||||
session: sess,
|
||||
ch: make(chan []byte, 32),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *subscription) Output() <-chan []byte {
|
||||
return s.ch
|
||||
}
|
||||
|
||||
func (s *subscription) Close() error {
|
||||
s.once.Do(func() {
|
||||
s.mu.Lock()
|
||||
s.closed = true
|
||||
close(s.ch)
|
||||
s.mu.Unlock()
|
||||
s.session.removeSubscription(s)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// closeChan closes the underlying channel exactly once without removing the
|
||||
// subscription from the session's subscriber list. It is used by the manager
|
||||
// when the shell exits.
|
||||
func (s *subscription) closeChan() {
|
||||
s.once.Do(func() {
|
||||
s.mu.Lock()
|
||||
s.closed = true
|
||||
close(s.ch)
|
||||
s.mu.Unlock()
|
||||
})
|
||||
}
|
||||
|
||||
// send performs a non-blocking send to the subscription channel. If the
|
||||
// channel is full or already closed, the chunk is dropped.
|
||||
func (s *subscription) send(chunk []byte) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.closed {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case s.ch <- chunk:
|
||||
default:
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user