@@ -48,6 +48,12 @@ namespace foundry_local {
4848 state_ = SessionState::Starting;
4949 activeSettings_ = settings_;
5050
51+ // Validate queue capacity early
52+ if (activeSettings_.push_queue_capacity <= 0 ) {
53+ state_ = SessionState::Created;
54+ throw Exception (" push_queue_capacity must be greater than 0." , *logger_);
55+ }
56+
5157 // Build the start command
5258 CoreInteropRequest req (" audio_stream_start" );
5359 req.AddParam (" Model" , modelId_);
@@ -77,11 +83,12 @@ namespace foundry_local {
7783 throw Exception (" audio_stream_start returned an empty session handle." , *logger_);
7884 }
7985
86+ // Validate queue capacity
87+ const size_t queueCapacity = static_cast <size_t >(activeSettings_.push_queue_capacity );
88+
8089 // Create the queues
81- pushQueue_ = std::make_unique<Internal::ThreadSafeQueue<AudioChunk>>(
82- static_cast <size_t >(activeSettings_.push_queue_capacity ));
83- resultQueue_ = std::make_unique<Internal::ThreadSafeQueue<LiveAudioTranscriptionResponse>>(
84- static_cast <size_t >(activeSettings_.push_queue_capacity ));
90+ pushQueue_ = std::make_unique<Internal::ThreadSafeQueue<AudioChunk>>(queueCapacity);
91+ resultQueue_ = std::make_unique<Internal::ThreadSafeQueue<LiveAudioTranscriptionResponse>>(queueCapacity);
8592
8693 state_ = SessionState::Started;
8794
@@ -93,7 +100,11 @@ namespace foundry_local {
93100 {
94101 std::lock_guard<std::mutex> lock (mutex_);
95102 if (state_ != SessionState::Started) {
96- throw Exception (" Session is not started. Call Start() first." , *logger_);
103+ throw Exception (
104+ state_ == SessionState::Stopped
105+ ? " Session has already been stopped."
106+ : " Session is not started. Call Start() first." ,
107+ *logger_);
97108 }
98109 }
99110
@@ -118,8 +129,16 @@ namespace foundry_local {
118129 return TranscriptionStatus::Result;
119130 case Internal::DequeueStatus::Timeout:
120131 return TranscriptionStatus::Timeout;
121- case Internal::DequeueStatus::Closed:
132+ case Internal::DequeueStatus::Closed: {
133+ // Return the final result from Stop() if available
134+ std::lock_guard<std::mutex> lock (mutex_);
135+ if (hasFinalResult_) {
136+ result = std::move (finalResult_);
137+ hasFinalResult_ = false ;
138+ return TranscriptionStatus::Result;
139+ }
122140 return TranscriptionStatus::Closed;
141+ }
123142 case Internal::DequeueStatus::Error:
124143 return TranscriptionStatus::Error;
125144 default :
@@ -144,9 +163,15 @@ namespace foundry_local {
144163 pushQueue_->Close ();
145164 }
146165
166+ // Close the result queue to unblock any blocked Push() in the worker thread,
167+ // preventing a deadlock when joining below.
168+ if (resultQueue_) {
169+ resultQueue_->Close ();
170+ }
171+
147172 lock.unlock ();
148173
149- // Wait for the push thread to finish
174+ // Wait for the push thread to finish (safe now — worker is unblocked)
150175 if (pushThread_.joinable ()) {
151176 pushThread_.join ();
152177 }
@@ -158,23 +183,20 @@ namespace foundry_local {
158183
159184 auto response = core_->call (req.Command (), *logger_, &json);
160185
161- // Enqueue the final transcription result from the stop response, then close
162- if (resultQueue_ ) {
163- if (response. HasError () ) {
186+ // Store the final result or error for retrieval via TryGetNext
187+ if (response. HasError () ) {
188+ if (resultQueue_ ) {
164189 resultQueue_->CloseWithError (" audio_stream_stop failed: " + response.error );
165190 }
166- else {
167- if (!response.data .empty ()) {
168- try {
169- auto finalResult = LiveAudioTranscriptionResponse::FromJson (response.data );
170- resultQueue_->Push (std::move (finalResult));
171- }
172- catch (const std::exception& e) {
173- logger_->Log (LogLevel::Warning,
174- std::string (" Failed to parse final transcription response: " ) + e.what ());
175- }
176- }
177- resultQueue_->Close ();
191+ }
192+ else if (!response.data .empty ()) {
193+ try {
194+ finalResult_ = LiveAudioTranscriptionResponse::FromJson (response.data );
195+ hasFinalResult_ = true ;
196+ }
197+ catch (const std::exception& e) {
198+ logger_->Log (LogLevel::Warning,
199+ std::string (" Failed to parse final transcription response: " ) + e.what ());
178200 }
179201 }
180202
@@ -204,7 +226,10 @@ namespace foundry_local {
204226
205227 if (response.HasError ()) {
206228 auto coreError = CoreErrorResponse::TryParse (response.error );
207- std::string msg = coreError.has_value () ? coreError->message : response.error ;
229+ std::string msg =
230+ (coreError.has_value () && !coreError->message .empty ())
231+ ? coreError->message
232+ : response.error ;
208233
209234 logger_->Log (LogLevel::Error, " audio_stream_push failed: " + msg);
210235 pushQueue_->Close ();
@@ -219,7 +244,11 @@ namespace foundry_local {
219244 if (!response.data .empty ()) {
220245 try {
221246 auto result = LiveAudioTranscriptionResponse::FromJson (response.data );
222- resultQueue_->Push (std::move (result));
247+ if (!resultQueue_->TryPush (std::move (result))) {
248+ logger_->Log (
249+ LogLevel::Warning,
250+ " Dropping transcription result because the result queue is full." );
251+ }
223252 }
224253 catch (const std::exception& e) {
225254 logger_->Log (LogLevel::Warning,
0 commit comments