fix daemon's concurrency issues
This commit is contained in:
+36
-19
@@ -24,6 +24,8 @@ type Daemon struct {
|
|||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
|
|
||||||
pipeline pipeline.Pipeline
|
pipeline pipeline.Pipeline
|
||||||
|
|
||||||
|
wg sync.WaitGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(n notify.Notifier) *Daemon {
|
func New(n notify.Notifier) *Daemon {
|
||||||
@@ -41,12 +43,25 @@ func New(n notify.Notifier) *Daemon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Daemon) status() pipeline.Status {
|
func (d *Daemon) status() pipeline.Status {
|
||||||
|
d.mu.RLock()
|
||||||
|
defer d.mu.RUnlock()
|
||||||
if d.pipeline == nil {
|
if d.pipeline == nil {
|
||||||
return pipeline.Idle
|
return pipeline.Idle
|
||||||
}
|
}
|
||||||
return d.pipeline.Status()
|
return d.pipeline.Status()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Daemon) stopPipeline() {
|
||||||
|
d.mu.Lock()
|
||||||
|
p := d.pipeline
|
||||||
|
d.pipeline = nil
|
||||||
|
d.mu.Unlock()
|
||||||
|
|
||||||
|
if p != nil {
|
||||||
|
p.Stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Daemon) Run() error {
|
func (d *Daemon) Run() error {
|
||||||
if err := bus.CheckExistingDaemon(); err != nil {
|
if err := bus.CheckExistingDaemon(); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -73,10 +88,11 @@ func (d *Daemon) Run() error {
|
|||||||
d.cancel()
|
d.cancel()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Close the listener when context is done
|
|
||||||
go func() {
|
go func() {
|
||||||
<-d.ctx.Done()
|
<-d.ctx.Done()
|
||||||
ln.Close()
|
if err := ln.Close(); err != nil {
|
||||||
|
log.Printf("Error closing listener: %v", err)
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
log.Printf("Daemon started, listening on socket")
|
log.Printf("Daemon started, listening on socket")
|
||||||
@@ -85,18 +101,21 @@ func (d *Daemon) Run() error {
|
|||||||
c, err := ln.Accept()
|
c, err := ln.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if d.ctx.Err() != nil {
|
if d.ctx.Err() != nil {
|
||||||
log.Printf("Shutdown requested")
|
log.Printf("Shutdown requested, waiting for connections to finish")
|
||||||
|
d.wg.Wait()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
log.Printf("Accept error: %v", err)
|
log.Printf("Accept error: %v", err)
|
||||||
return fmt.Errorf("accept failed: %w", err)
|
return fmt.Errorf("accept failed: %w", err)
|
||||||
}
|
}
|
||||||
|
d.wg.Add(1)
|
||||||
go d.handle(c)
|
go d.handle(c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Daemon) handle(c net.Conn) {
|
func (d *Daemon) handle(c net.Conn) {
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
|
defer d.wg.Done()
|
||||||
|
|
||||||
line, err := bufio.NewReader(c).ReadString('\n')
|
line, err := bufio.NewReader(c).ReadString('\n')
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -129,36 +148,34 @@ func (d *Daemon) handle(c net.Conn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Daemon) toggle() {
|
func (d *Daemon) toggle() {
|
||||||
var actionChan chan<- pipeline.Action
|
|
||||||
|
|
||||||
switch d.status() {
|
switch d.status() {
|
||||||
case pipeline.Idle:
|
case pipeline.Idle:
|
||||||
p := pipeline.New()
|
p := pipeline.New()
|
||||||
p.Run(d.ctx)
|
p.Run(d.ctx)
|
||||||
|
|
||||||
|
d.mu.Lock()
|
||||||
d.pipeline = p
|
d.pipeline = p
|
||||||
|
d.mu.Unlock()
|
||||||
|
|
||||||
go d.notifier.RecordingStarted()
|
go d.notifier.RecordingStarted()
|
||||||
|
|
||||||
case pipeline.Recording:
|
case pipeline.Recording:
|
||||||
|
d.stopPipeline() // aborted during recording (chunks not sent to transcriber yet)
|
||||||
go d.notifier.Aborted()
|
go d.notifier.Aborted()
|
||||||
if d.pipeline != nil {
|
|
||||||
d.pipeline.Stop() // aborted during recording (chunks not sent to transcriber yet)
|
|
||||||
d.pipeline = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
case pipeline.Transcribing:
|
case pipeline.Transcribing:
|
||||||
go d.notifier.RecordingEnded()
|
d.mu.RLock()
|
||||||
actionChan = d.pipeline.Actions()
|
if d.pipeline != nil {
|
||||||
|
actionChan := d.pipeline.GetActionCh()
|
||||||
|
d.mu.RUnlock()
|
||||||
actionChan <- pipeline.Inject
|
actionChan <- pipeline.Inject
|
||||||
|
} else {
|
||||||
|
d.mu.RUnlock()
|
||||||
|
}
|
||||||
|
go d.notifier.RecordingEnded()
|
||||||
|
|
||||||
case pipeline.Injecting:
|
case pipeline.Injecting:
|
||||||
|
d.stopPipeline() // aborted during injection
|
||||||
go d.notifier.Aborted()
|
go d.notifier.Aborted()
|
||||||
|
|
||||||
if d.pipeline != nil {
|
|
||||||
d.pipeline.Stop() // aborted during injection
|
|
||||||
d.pipeline = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
d.mu.Unlock()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,5 +56,6 @@ type Nop struct{}
|
|||||||
|
|
||||||
func (Nop) RecordingStarted() {}
|
func (Nop) RecordingStarted() {}
|
||||||
func (Nop) RecordingEnded() {}
|
func (Nop) RecordingEnded() {}
|
||||||
|
func (Nop) Aborted() {}
|
||||||
func (Nop) Transcribing() {}
|
func (Nop) Transcribing() {}
|
||||||
func (Nop) Error(msg string) {}
|
func (Nop) Error(msg string) {}
|
||||||
|
|||||||
@@ -27,14 +27,20 @@ type Pipeline interface {
|
|||||||
Run(ctx context.Context)
|
Run(ctx context.Context)
|
||||||
Stop()
|
Stop()
|
||||||
Status() Status
|
Status() Status
|
||||||
Actions() chan<- Action
|
GetActionCh() chan<- Action
|
||||||
}
|
}
|
||||||
|
|
||||||
type pipeline struct {
|
type pipeline struct {
|
||||||
status Status
|
status Status
|
||||||
actionCh chan Action
|
actionCh chan Action
|
||||||
|
|
||||||
|
mu sync.RWMutex
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
|
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
|
|
||||||
|
stopOnce sync.Once
|
||||||
|
running bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() Pipeline {
|
func New() Pipeline {
|
||||||
@@ -44,40 +50,79 @@ func New() Pipeline {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *pipeline) Status() Status {
|
func (p *pipeline) Status() Status {
|
||||||
|
p.mu.RLock()
|
||||||
|
defer p.mu.RUnlock()
|
||||||
return p.status
|
return p.status
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *pipeline) Actions() chan<- Action {
|
func (p *pipeline) setStatus(status Status) {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
p.status = status
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pipeline) setCancel(cancel context.CancelFunc) {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
p.cancel = cancel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pipeline) getCancel() context.CancelFunc {
|
||||||
|
p.mu.RLock()
|
||||||
|
defer p.mu.RUnlock()
|
||||||
|
return p.cancel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pipeline) GetActionCh() chan<- Action {
|
||||||
|
p.mu.RLock()
|
||||||
|
defer p.mu.RUnlock()
|
||||||
return p.actionCh
|
return p.actionCh
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *pipeline) Stop() {
|
func (p *pipeline) Stop() {
|
||||||
if p.cancel != nil {
|
p.stopOnce.Do(func() {
|
||||||
p.cancel()
|
cancel := p.getCancel()
|
||||||
|
if cancel != nil {
|
||||||
|
cancel()
|
||||||
}
|
}
|
||||||
|
})
|
||||||
p.wg.Wait()
|
p.wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *pipeline) Run(ctx context.Context) {
|
func (p *pipeline) Run(ctx context.Context) {
|
||||||
|
p.mu.Lock()
|
||||||
|
if p.running {
|
||||||
|
p.mu.Unlock()
|
||||||
|
log.Printf("Pipeline: Already running, ignoring Run() call")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.running = true
|
||||||
|
p.mu.Unlock()
|
||||||
|
|
||||||
runCtx, cancel := context.WithTimeout(ctx, 5*time.Minute)
|
runCtx, cancel := context.WithTimeout(ctx, 5*time.Minute)
|
||||||
p.cancel = cancel
|
p.setCancel(cancel)
|
||||||
|
|
||||||
p.wg.Add(1)
|
p.wg.Add(1)
|
||||||
go p.run(runCtx)
|
go p.run(runCtx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *pipeline) run(ctx context.Context) {
|
func (p *pipeline) run(ctx context.Context) {
|
||||||
defer func() {
|
defer func() {
|
||||||
|
log.Printf("Pipeline: defered run")
|
||||||
|
p.mu.Lock()
|
||||||
|
p.running = false
|
||||||
|
p.mu.Unlock()
|
||||||
p.wg.Done()
|
p.wg.Done()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
log.Printf("Pipeline: Starting recording")
|
log.Printf("Pipeline: Starting recording")
|
||||||
p.status = Recording
|
p.setStatus(Recording)
|
||||||
|
|
||||||
recorder := recording.NewDefaultRecorder()
|
recorder := recording.NewDefaultRecorder()
|
||||||
frameCh, errCh, err := recorder.Start(ctx)
|
frameCh, errCh, err := recorder.Start(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Pipeline: Recording error: %v", err)
|
log.Printf("Pipeline: Recording error: %v", err)
|
||||||
p.status = Idle
|
p.setStatus(Idle)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer recorder.Stop()
|
defer recorder.Stop()
|
||||||
@@ -93,12 +138,12 @@ func (p *pipeline) run(ctx context.Context) {
|
|||||||
log.Printf("Pipeline: Received frame #%d - Size: %d bytes, Timestamp: %v, Total bytes so far: %d",
|
log.Printf("Pipeline: Received frame #%d - Size: %d bytes, Timestamp: %v, Total bytes so far: %d",
|
||||||
frameCount, len(frame.Data), frame.Timestamp.Format("15:04:05.000"), totalBytes)
|
frameCount, len(frame.Data), frame.Timestamp.Format("15:04:05.000"), totalBytes)
|
||||||
|
|
||||||
p.status = Transcribing
|
p.setStatus(Transcribing)
|
||||||
|
|
||||||
case err := <-errCh:
|
case err := <-errCh:
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Pipeline: Recording error: %v", err)
|
log.Printf("Pipeline: Recording error: %v", err)
|
||||||
p.status = Idle
|
p.setStatus(Idle)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +156,13 @@ func (p *pipeline) run(ctx context.Context) {
|
|||||||
if err := recorder.Stop(); err != nil {
|
if err := recorder.Stop(); err != nil {
|
||||||
log.Printf("Pipeline: Error stopping recorder: %v", err)
|
log.Printf("Pipeline: Error stopping recorder: %v", err)
|
||||||
}
|
}
|
||||||
p.status = Injecting
|
p.setStatus(Injecting)
|
||||||
|
|
||||||
|
// Simulate injection work then return to idle
|
||||||
|
log.Printf("Pipeline: Simulating injection work")
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
log.Printf("Pipeline: Injection work done, returning to idle")
|
||||||
|
p.setStatus(Idle)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user