mirror of
https://github.com/go-task/task.git
synced 2025-12-16 19:57:43 +01:00
wip: add synchronized output to prevent prompt interleaving
- Add SyncWriter to synchronize stdout/stderr writes with prompts - Add promptMutex to serialize interactive prompts - Keep rawStdout/rawStderr for BubbleTea to avoid deadlock - Update test Taskfile with nested deps scenario
This commit is contained in:
@@ -57,6 +57,8 @@ type (
|
||||
Stdin io.Reader
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
rawStdout io.Writer // unwrapped stdout for prompts
|
||||
rawStderr io.Writer // unwrapped stderr for prompts
|
||||
|
||||
// Internal
|
||||
Taskfile *ast.Taskfile
|
||||
@@ -71,6 +73,7 @@ type (
|
||||
fuzzyModel *fuzzy.Model
|
||||
fuzzyModelOnce sync.Once
|
||||
|
||||
promptMutex sync.Mutex
|
||||
concurrencySemaphore chan struct{}
|
||||
taskCallCount map[string]*int32
|
||||
mkdirMutexMap map[string]*sync.Mutex
|
||||
|
||||
28
internal/output/sync_writer.go
Normal file
28
internal/output/sync_writer.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package output
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// SyncWriter wraps an io.Writer with a mutex to synchronize writes.
|
||||
// This is used to prevent output from interleaving with interactive prompts.
|
||||
type SyncWriter struct {
|
||||
w io.Writer
|
||||
mu *sync.Mutex
|
||||
}
|
||||
|
||||
// NewSyncWriter creates a new SyncWriter that uses the provided mutex.
|
||||
func NewSyncWriter(w io.Writer, mu *sync.Mutex) *SyncWriter {
|
||||
return &SyncWriter{
|
||||
w: w,
|
||||
mu: mu,
|
||||
}
|
||||
}
|
||||
|
||||
// Write implements io.Writer with synchronized access.
|
||||
func (sw *SyncWriter) Write(p []byte) (n int, err error) {
|
||||
sw.mu.Lock()
|
||||
defer sw.mu.Unlock()
|
||||
return sw.w.Write(p)
|
||||
}
|
||||
11
requires.go
11
requires.go
@@ -27,7 +27,16 @@ func (e *Executor) promptForInteractiveVars(t *ast.Task, call *Call) (bool, erro
|
||||
return false, nil
|
||||
}
|
||||
|
||||
prompter := prompt.New()
|
||||
// Lock to prevent multiple parallel prompts from interleaving
|
||||
e.promptMutex.Lock()
|
||||
defer e.promptMutex.Unlock()
|
||||
|
||||
// Use raw stderr for prompts to avoid deadlock with SyncWriter
|
||||
prompter := &prompt.Prompter{
|
||||
Stdin: e.Stdin,
|
||||
Stdout: e.rawStdout,
|
||||
Stderr: e.rawStderr,
|
||||
}
|
||||
var prompted bool
|
||||
|
||||
for _, requiredVar := range t.Requires.Vars {
|
||||
|
||||
11
setup.go
11
setup.go
@@ -179,6 +179,17 @@ func (e *Executor) setupStdFiles() {
|
||||
if e.Stderr == nil {
|
||||
e.Stderr = os.Stderr
|
||||
}
|
||||
|
||||
// Keep raw references for interactive prompts
|
||||
e.rawStdout = e.Stdout
|
||||
e.rawStderr = e.Stderr
|
||||
|
||||
// Wrap with synchronized writers when interactive mode is enabled
|
||||
// to prevent output from interleaving with prompts
|
||||
if e.Interactive {
|
||||
e.Stdout = output.NewSyncWriter(e.Stdout, &e.promptMutex)
|
||||
e.Stderr = output.NewSyncWriter(e.Stderr, &e.promptMutex)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Executor) setupLogger() {
|
||||
|
||||
1
testdata/interactive_vars/.taskrc.yml
vendored
Normal file
1
testdata/interactive_vars/.taskrc.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
interactive: true
|
||||
71
testdata/interactive_vars/Taskfile.yml
vendored
71
testdata/interactive_vars/Taskfile.yml
vendored
@@ -1,25 +1,66 @@
|
||||
version: "3"
|
||||
|
||||
tasks:
|
||||
simple:
|
||||
requires:
|
||||
vars:
|
||||
- MY_VAR
|
||||
main:
|
||||
desc: Main task with nested deps
|
||||
deps:
|
||||
- dep-a
|
||||
- dep-b
|
||||
cmds:
|
||||
- echo "{{.MY_VAR}}"
|
||||
- echo "Main task done!"
|
||||
|
||||
with-enum:
|
||||
requires:
|
||||
vars:
|
||||
- name: ENV
|
||||
enum: [dev, staging, prod]
|
||||
dep-a:
|
||||
desc: Dependency A
|
||||
deps:
|
||||
- leaf-a1
|
||||
- leaf-a2
|
||||
cmds:
|
||||
- echo "{{.ENV}}"
|
||||
- echo "Dep A done"
|
||||
|
||||
multiple:
|
||||
dep-b:
|
||||
desc: Dependency B
|
||||
deps:
|
||||
- leaf-b1
|
||||
- leaf-b2
|
||||
cmds:
|
||||
- echo "Dep B done"
|
||||
|
||||
leaf-a1:
|
||||
desc: Leaf A1 with enum
|
||||
requires:
|
||||
vars:
|
||||
- VAR1
|
||||
- VAR2
|
||||
- name: VAR_A1
|
||||
enum:
|
||||
- alpha
|
||||
- beta
|
||||
- gamma
|
||||
cmds:
|
||||
- echo "{{.VAR1}} {{.VAR2}}"
|
||||
- echo "Leaf A1 {{.VAR_A1}}"
|
||||
|
||||
leaf-a2:
|
||||
desc: Leaf A2 with text
|
||||
requires:
|
||||
vars:
|
||||
- VAR_A2
|
||||
cmds:
|
||||
- echo "Leaf A2 {{.VAR_A2}}"
|
||||
|
||||
leaf-b1:
|
||||
desc: Leaf B1 with enum
|
||||
requires:
|
||||
vars:
|
||||
- name: VAR_B1
|
||||
enum:
|
||||
- one
|
||||
- two
|
||||
- three
|
||||
cmds:
|
||||
- echo "Leaf B1 {{.VAR_B1}}"
|
||||
|
||||
leaf-b2:
|
||||
desc: Leaf B2 with text
|
||||
requires:
|
||||
vars:
|
||||
- VAR_B2
|
||||
cmds:
|
||||
- echo "Leaf B2 {{.VAR_B2}}"
|
||||
|
||||
Reference in New Issue
Block a user