Skip to content

Commit d6b215b

Browse files
committed
refactor inference polling to use a generic helper function based on context and ticker.
use duration flags for timeout and intervals.
1 parent 8559812 commit d6b215b

7 files changed

Lines changed: 330 additions & 79 deletions

File tree

cmd/audio_inference.go

Lines changed: 27 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ func init() {
4141
f.Int("bitrate", 0, "Bitrate in kbps (32-320, compressed formats only)")
4242
f.String("preset", "", "Named preset to apply")
4343
f.Bool("dry-run", false, "Print the API request without executing")
44-
f.Int("poll-interval", 5, "Polling interval in seconds for async results")
45-
f.Int("timeout", 300, "Maximum wait time in seconds for audio generation")
44+
f.Duration("poll-interval", defaultPollInterval, "Polling interval in seconds for async results")
45+
f.Duration("timeout", defaultAudioGenerationTimeout, "Maximum wait time in seconds for audio generation")
4646
f.Duration("download-timeout", defaultAudioDownloadTimeout, "timeout to use when downloading audio inference results")
4747

4848
audioInferenceCmd.RegisterFlagCompletionFunc("output-format", func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) { //nolint:errcheck,gosec
@@ -98,8 +98,8 @@ func runAudioInference(cmd *cobra.Command, args []string) error {
9898
dryRun, _ := cmd.Flags().GetBool("dry-run")
9999
sampleRate, _ := cmd.Flags().GetInt("sample-rate")
100100
bitrate, _ := cmd.Flags().GetInt("bitrate")
101-
pollInterval, _ := cmd.Flags().GetInt("poll-interval")
102-
timeout, _ := cmd.Flags().GetInt("timeout")
101+
pollInterval, _ := cmd.Flags().GetDuration("poll-interval")
102+
timeout, _ := cmd.Flags().GetDuration("timeout")
103103
downloadTimeout, _ := cmd.Flags().GetDuration("download-timeout")
104104

105105
// Validation
@@ -143,11 +143,12 @@ func runAudioInference(cmd *cobra.Command, args []string) error {
143143
// Submit
144144
s := output.NewSpinner(" Submitting audio generation task...")
145145
s.Start()
146-
defer s.Stop()
147146

148147
client := api.NewClient(key, config.GetBaseURL(), flagVerbose)
149148
_, err := client.AudioInference(context.Background(), req)
150149
if err != nil {
150+
s.Stop()
151+
151152
if api.IsAuthError(err) {
152153
output.Error("Authentication failed. Run 'runware auth login' to set your API key.")
153154
return err
@@ -158,45 +159,40 @@ func runAudioInference(cmd *cobra.Command, args []string) error {
158159
s.Suffix(" Generating audio (this may take a few minutes)...")
159160

160161
taskUUID := req.TaskUUID
161-
deadline := time.Now().Add(time.Duration(timeout) * time.Second)
162-
interval := time.Duration(pollInterval) * time.Second
163-
var results []api.AudioInferenceResult
164-
165-
for time.Now().Before(deadline) {
166-
rawData, err := client.GetResponse(context.Background(), taskUUID)
167-
if err != nil {
168-
if flagVerbose {
169-
fmt.Fprintf(os.Stderr, "Poll: %s\n", err) //nolint:errcheck,gosec
170-
}
171-
time.Sleep(interval)
172-
continue
173-
}
174162

175-
for _, raw := range rawData {
163+
pollCtx, cancel := context.WithTimeout(cmd.Context(), timeout)
164+
defer cancel()
165+
166+
results, err := api.PollResults(
167+
pollCtx,
168+
client,
169+
taskUUID,
170+
pollInterval,
171+
flagVerbose,
172+
func(raw json.RawMessage) (api.AudioInferenceResult, bool) {
176173
var r api.AudioInferenceResult
177174
if err := json.Unmarshal(raw, &r); err != nil {
178-
continue
179-
}
180-
if r.AudioURL != "" {
181-
results = append(results, r)
175+
return r, false
182176
}
183-
}
184-
185-
if len(results) > 0 {
186-
break
187-
}
188-
189-
time.Sleep(interval)
177+
return r, r.AudioURL != ""
178+
},
179+
)
180+
if err != nil {
181+
s.Stop()
182+
output.Error("Audo generation failed")
183+
return err
190184
}
191185

192186
if len(results) == 0 {
187+
s.Stop()
193188
output.Error("Audio generation timed out or returned no results")
194-
return fmt.Errorf("no audio results after %ds", timeout)
189+
return fmt.Errorf("no audio results after %v", timeout)
195190
}
196191

197192
// JSON/YAML output
198193
format := output.ParseFormat(getFormat())
199194
if format != output.FormatTable {
195+
s.Stop()
200196
return output.Print(format, results, nil, nil)
201197
}
202198

@@ -215,9 +211,7 @@ func runAudioInference(cmd *cobra.Command, args []string) error {
215211
rows = append(rows, row)
216212
}
217213

218-
// Manually stop the spinner to ensure clean output of results.
219214
s.Stop()
220-
221215
return output.Print(format, results, headers, rows)
222216
}
223217

@@ -254,8 +248,6 @@ func runAudioInference(cmd *cobra.Command, args []string) error {
254248
rows = append(rows, row)
255249
}
256250

257-
// Manually stop the spinner to ensure clean output of results.
258251
s.Stop()
259-
260252
return output.Print(format, results, headers, rows)
261253
}

cmd/constants.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ const (
1414
)
1515

1616
const (
17+
// Poll intervals.
18+
defaultPollInterval = 5 * time.Second
19+
20+
// Generation timeouts.
21+
defaultAudioGenerationTimeout = 5 * time.Minute
22+
defaultVideoGenerationTimeout = 10 * time.Minute
23+
24+
// Download timeouts.
1725
defaultImageDownloadTimeout = 1 * time.Minute
1826
defaultAudioDownloadTimeout = 5 * time.Minute
1927
defaultVideoDownloadTimeout = 10 * time.Minute

cmd/image_inference.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -245,12 +245,12 @@ func runImageInference(cmd *cobra.Command, args []string) error {
245245
// Start spinner
246246
s := output.NewSpinner(" Generating image...")
247247
s.Start()
248-
defer s.Stop()
249248

250249
client := api.NewClient(key, config.GetBaseURL(), flagVerbose)
251250

252251
results, err := client.ImageInference(ctx, req)
253252
if err != nil {
253+
s.Stop()
254254
if api.IsAuthError(err) {
255255
output.Error("Authentication failed. Run 'runware auth login' to set your API key.")
256256
return err
@@ -259,13 +259,15 @@ func runImageInference(cmd *cobra.Command, args []string) error {
259259
}
260260

261261
if len(results) == 0 {
262+
s.Stop()
262263
output.Error("No images returned")
263264
return fmt.Errorf("empty result")
264265
}
265266

266267
// JSON/YAML output
267268
format := output.ParseFormat(getFormat())
268269
if format != output.FormatTable {
270+
s.Stop()
269271
return output.Print(format, results, nil, nil)
270272
}
271273

@@ -277,9 +279,7 @@ func runImageInference(cmd *cobra.Command, args []string) error {
277279
rows = append(rows, []any{i + 1, r.ImageURL, r.Seed})
278280
}
279281

280-
// Manually stop the spinner to ensure clean output of results.
281282
s.Stop()
282-
283283
return output.Print(format, results, headers, rows)
284284
}
285285

@@ -309,9 +309,7 @@ func runImageInference(cmd *cobra.Command, args []string) error {
309309
rows = append(rows, []any{i + 1, destPath, r.Seed})
310310
}
311311

312-
// Manually stop the spinner to ensure clean output of results.
313312
s.Stop()
314-
315313
return output.Print(format, results, headers, rows)
316314
}
317315

cmd/text_inference.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,19 +140,18 @@ func runTextInference(cmd *cobra.Command, args []string) error {
140140
// Submit
141141
s := output.NewSpinner(" Generating text...")
142142
s.Start()
143-
defer s.Stop()
144143

145144
client := api.NewClient(key, config.GetBaseURL(), flagVerbose)
146145
results, err := client.TextInference(context.Background(), req)
147146
if err != nil {
147+
s.Stop()
148148
if api.IsAuthError(err) {
149149
output.Error("Authentication failed. Run 'runware auth login' to set your API key.")
150150
return err
151151
}
152152
return err
153153
}
154154

155-
// Manually stop the spinner to ensure clean output of results.
156155
s.Stop()
157156

158157
if len(results) == 0 {

cmd/video_inference.go

Lines changed: 25 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ func init() {
4747
f.Bool("include-cost", false, "Include cost info in response")
4848
f.String("preset", "", "Named preset to apply")
4949
f.Bool("dry-run", false, "Print the API request without executing")
50-
f.Int("poll-interval", 5, "Polling interval in seconds for async results")
51-
f.Int("timeout", 600, "Maximum wait time in seconds for video generation")
50+
f.Duration("poll-interval", defaultPollInterval, "Polling interval for async results")
51+
f.Duration("timeout", defaultVideoGenerationTimeout, "Maximum wait time for video generation")
5252
f.Duration("download-timeout", defaultVideoDownloadTimeout, "timeout to use when downloading video inference results")
5353

5454
videoInferenceCmd.RegisterFlagCompletionFunc("output-format", func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) { //nolint:errcheck,gosec
@@ -119,8 +119,8 @@ func runVideoInference(cmd *cobra.Command, args []string) error {
119119
dryRun, _ := cmd.Flags().GetBool("dry-run")
120120
sourcePath, _ := cmd.Flags().GetString("source")
121121
sourceLastPath, _ := cmd.Flags().GetString("source-last")
122-
pollInterval, _ := cmd.Flags().GetInt("poll-interval")
123-
timeout, _ := cmd.Flags().GetInt("timeout")
122+
pollInterval, _ := cmd.Flags().GetDuration("poll-interval")
123+
timeout, _ := cmd.Flags().GetDuration("timeout")
124124
downloadTimeout, _ := cmd.Flags().GetDuration("download-timeout")
125125

126126
// Build request
@@ -192,12 +192,12 @@ func runVideoInference(cmd *cobra.Command, args []string) error {
192192
// Submit the video generation task
193193
s := output.NewSpinner(" Submitting video generation task...")
194194
s.Start()
195-
defer s.Stop()
196195

197196
client := api.NewClient(key, config.GetBaseURL(), flagVerbose)
198197

199198
submitResults, err := client.VideoInference(context.Background(), req)
200199
if err != nil {
200+
s.Stop()
201201
if api.IsAuthError(err) {
202202
output.Error("Authentication failed. Run 'runware auth login' to set your API key.")
203203
return err
@@ -213,48 +213,39 @@ func runVideoInference(cmd *cobra.Command, args []string) error {
213213
taskUUID = submitResults[0].TaskUUID
214214
}
215215

216-
deadline := time.Now().Add(time.Duration(timeout) * time.Second)
217-
interval := time.Duration(pollInterval) * time.Second
218-
var results []api.VideoInferenceResult
216+
pollCtx, cancel := context.WithTimeout(cmd.Context(), timeout)
217+
defer cancel()
219218

220-
for time.Now().Before(deadline) {
221-
rawData, err := client.GetResponse(context.Background(), taskUUID)
222-
if err != nil {
223-
// getResponse may return an error if results aren't ready yet — keep polling
224-
if flagVerbose {
225-
fmt.Fprintf(os.Stderr, "Poll: %s\n", err) //nolint:errcheck,gosec
226-
}
227-
time.Sleep(interval)
228-
continue
229-
}
230-
231-
// Parse results
232-
for _, raw := range rawData {
219+
results, err := api.PollResults(
220+
pollCtx,
221+
client,
222+
taskUUID,
223+
pollInterval,
224+
flagVerbose,
225+
func(raw json.RawMessage) (api.VideoInferenceResult, bool) {
233226
var r api.VideoInferenceResult
234227
if err := json.Unmarshal(raw, &r); err != nil {
235-
continue
236-
}
237-
// Check if we have a video URL (indicates completion)
238-
if r.VideoURL != "" || r.MediaURL != "" {
239-
results = append(results, r)
228+
return r, false
240229
}
241-
}
242-
243-
if len(results) > 0 {
244-
break
245-
}
246-
247-
time.Sleep(interval)
230+
return r, r.VideoURL != "" || r.MediaURL != ""
231+
},
232+
)
233+
if err != nil {
234+
s.Stop()
235+
output.Error("Video generation failed")
236+
return err
248237
}
249238

250239
if len(results) == 0 {
240+
s.Stop()
251241
output.Error("Video generation timed out or returned no results")
252-
return fmt.Errorf("no video results after %ds", timeout)
242+
return fmt.Errorf("no video results after %v", timeout)
253243
}
254244

255245
// JSON/YAML output
256246
format := output.ParseFormat(getFormat())
257247
if format != output.FormatTable {
248+
s.Stop()
258249
return output.Print(format, results, nil, nil)
259250
}
260251

@@ -277,9 +268,7 @@ func runVideoInference(cmd *cobra.Command, args []string) error {
277268
headers = append(headers, tableHeaderCost)
278269
}
279270

280-
// Manually stop the spinner to ensure clean output of results.
281271
s.Stop()
282-
283272
return output.Print(format, results, headers, rows)
284273
}
285274

@@ -328,7 +317,6 @@ func runVideoInference(cmd *cobra.Command, args []string) error {
328317
return fmt.Errorf("all %d video downloads failed", len(results))
329318
}
330319

331-
// Manually stop the spinner to ensure clean output of results.
332320
s.Stop()
333321

334322
if err := output.Print(format, results, headers, rows); err != nil {

internal/api/poll.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package api
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"errors"
7+
"fmt"
8+
"os"
9+
"time"
10+
)
11+
12+
// PollResults polls for generic results from a task.
13+
func PollResults[T any](ctx context.Context, client Client, taskUUID string, interval time.Duration, verbose bool, parse func(json.RawMessage) (T, bool)) ([]T, error) {
14+
ticker := time.NewTicker(interval)
15+
defer ticker.Stop()
16+
17+
var results []T
18+
for {
19+
rawData, err := client.GetResponse(ctx, taskUUID)
20+
if err != nil {
21+
var apiErr APIError
22+
if IsAuthError(err) || errors.As(err, &apiErr) {
23+
return nil, err
24+
}
25+
if verbose {
26+
fmt.Fprintf(os.Stderr, "Poll: %s\n", err) //nolint:errcheck,gosec
27+
}
28+
} else {
29+
for _, raw := range rawData {
30+
if r, ok := parse(raw); ok {
31+
results = append(results, r)
32+
}
33+
}
34+
if len(results) > 0 {
35+
return results, nil
36+
}
37+
}
38+
39+
select {
40+
case <-ctx.Done():
41+
return nil, nil
42+
case <-ticker.C:
43+
}
44+
}
45+
}

0 commit comments

Comments
 (0)