// Copyright (c) 2026 Storm Dragon // SPDX-License-Identifier: GPL-3.0-or-later package app import ( "context" "time" ) // Bound protection even if the backend never reports completion. The deadline // starts at admission, covering time spent in the bounded pending queue too. const uninterruptibleSpeechTimeout = 30 * time.Second func (worker *speechWorker) has_uninterruptible_locked() bool { if worker.uninterruptibleContext != nil && worker.uninterruptibleContext.Err() == nil { return true } for _, request := range worker.pending { if request.Uninterruptible && request.context != nil && request.context.Err() == nil { return true } } return false } // Preserve unrelated queued speech, just as ordinary presentation revocation // does. The existing cancellation barrier prevents CANCEL overtaking it. func (worker *speechWorker) stop_uninterruptible() { worker.mutex.Lock() defer worker.mutex.Unlock() retained := worker.pending[:0] for _, request := range worker.pending { if request.Uninterruptible { completeSpeechRequest(request, context.Canceled) } else { retained = append(retained, request) } } clear(worker.pending[len(retained):]) worker.pending = retained if worker.uninterruptibleCancel == nil || !worker.running { return } worker.uninterruptibleCancel() worker.presentationDelivery = nil if worker.beepCancel != nil { worker.beepCancel() } worker.generation++ worker.interrupting = true select { case worker.interrupt <- struct{}{}: default: } }