perf: prevent memory leak, data race and speed up ComputeCursorCol processing (#15)
- fix(wrapper): memory leak and data race (https://github.com/versenilvis/IRIS/commit/4361febfc92858f34ff2a2b77df8298addbefa75) - perf(overlay):speed up ComputeCursorCol processing (https://github.com/versenilvis/IRIS/commit/918b067cfc9f71c4fa52e1573865ab3612acb7d8)
This commit is contained in:
@@ -117,6 +117,11 @@ func ComputeCursorCol(data []byte) int {
|
|||||||
i++
|
i++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if b < 0x7f {
|
||||||
|
col++
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
r, size := utf8.DecodeRune(data[i:])
|
r, size := utf8.DecodeRune(data[i:])
|
||||||
w := lipgloss.Width(string(r))
|
w := lipgloss.Width(string(r))
|
||||||
col += w
|
col += w
|
||||||
|
|||||||
+40
-37
@@ -82,7 +82,7 @@ func runWrapper() {
|
|||||||
var naiveBuffer string
|
var naiveBuffer string
|
||||||
cursorOffset := 0
|
cursorOffset := 0
|
||||||
var bufferMu sync.Mutex
|
var bufferMu sync.Mutex
|
||||||
var userNavigated bool
|
var userNavigated atomic.Bool
|
||||||
var renderMenuNow func()
|
var renderMenuNow func()
|
||||||
|
|
||||||
r, w, err := os.Pipe() // pipe for ipc communication from shell to iris
|
r, w, err := os.Pipe() // pipe for ipc communication from shell to iris
|
||||||
@@ -164,8 +164,8 @@ func runWrapper() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if c.Process != nil {
|
if c.Process != nil {
|
||||||
cwd, err := os.Readlink(fmt.Sprintf("/proc/%d/cwd", c.Process.Pid))
|
cwd, linkErr := os.Readlink(fmt.Sprintf("/proc/%d/cwd", c.Process.Pid))
|
||||||
if err != nil {
|
if linkErr != nil {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
|
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
|
||||||
out, errCmd := exec.CommandContext(ctx, "lsof", "-p", fmt.Sprintf("%d", c.Process.Pid), "-a", "-d", "cwd", "-F", "n").Output()
|
out, errCmd := exec.CommandContext(ctx, "lsof", "-p", fmt.Sprintf("%d", c.Process.Pid), "-a", "-d", "cwd", "-F", "n").Output()
|
||||||
cancel()
|
cancel()
|
||||||
@@ -173,13 +173,13 @@ func runWrapper() {
|
|||||||
for _, line := range strings.Split(string(out), "\n") {
|
for _, line := range strings.Split(string(out), "\n") {
|
||||||
if strings.HasPrefix(line, "n") {
|
if strings.HasPrefix(line, "n") {
|
||||||
cwd = strings.TrimSpace(line[1:])
|
cwd = strings.TrimSpace(line[1:])
|
||||||
err = nil
|
linkErr = nil
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err == nil {
|
if linkErr == nil {
|
||||||
_ = os.Chdir(cwd)
|
_ = os.Chdir(cwd)
|
||||||
}
|
}
|
||||||
_ = syscall.Kill(c.Process.Pid, syscall.SIGKILL)
|
_ = syscall.Kill(c.Process.Pid, syscall.SIGKILL)
|
||||||
@@ -188,9 +188,9 @@ func runWrapper() {
|
|||||||
|
|
||||||
restoreTerminal()
|
restoreTerminal()
|
||||||
execArgs := []string{os.Args[0]}
|
execArgs := []string{os.Args[0]}
|
||||||
if logDir, err := config.CachePath(); err == nil {
|
if logDir, pathErr := config.CachePath(); pathErr == nil {
|
||||||
argsFile := filepath.Join(logDir, "reload-args")
|
argsFile := filepath.Join(logDir, "reload-args")
|
||||||
if data, err := os.ReadFile(argsFile); err == nil {
|
if data, readErr := os.ReadFile(argsFile); readErr == nil {
|
||||||
lines := strings.Split(string(data), "\n")
|
lines := strings.Split(string(data), "\n")
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
trimmed := strings.TrimSpace(line)
|
trimmed := strings.TrimSpace(line)
|
||||||
@@ -216,6 +216,18 @@ func runWrapper() {
|
|||||||
pendingUpdate = startBackgroundUpdateCheck()
|
pendingUpdate = startBackgroundUpdateCheck()
|
||||||
updatePrinted := false
|
updatePrinted := false
|
||||||
|
|
||||||
|
shellPGID, err := unix.Getpgid(core.ShellPID)
|
||||||
|
if err != nil {
|
||||||
|
shellPGID = core.ShellPID
|
||||||
|
}
|
||||||
|
isExecuting := func() bool {
|
||||||
|
pgrp, err := unix.IoctlGetInt(int(ptmx.Fd()), unix.TIOCGPGRP)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return pgrp != shellPGID
|
||||||
|
}
|
||||||
|
|
||||||
// bridge pty output to actual stdout
|
// bridge pty output to actual stdout
|
||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -242,9 +254,12 @@ func runWrapper() {
|
|||||||
|
|
||||||
bufferMu.Lock()
|
bufferMu.Lock()
|
||||||
nbEmpty := naiveBuffer == ""
|
nbEmpty := naiveBuffer == ""
|
||||||
|
navigated := userNavigated.Load()
|
||||||
bufferMu.Unlock()
|
bufferMu.Unlock()
|
||||||
|
|
||||||
if nbEmpty && !userNavigated {
|
if isExecuting() {
|
||||||
|
lastPromptBuf = nil
|
||||||
|
} else if nbEmpty && !navigated {
|
||||||
lastPromptBuf = append(lastPromptBuf, buf[:n]...)
|
lastPromptBuf = append(lastPromptBuf, buf[:n]...)
|
||||||
if idx := bytes.LastIndexByte(lastPromptBuf, '\n'); idx >= 0 {
|
if idx := bytes.LastIndexByte(lastPromptBuf, '\n'); idx >= 0 {
|
||||||
lastPromptBuf = append([]byte(nil), lastPromptBuf[idx+1:]...)
|
lastPromptBuf = append([]byte(nil), lastPromptBuf[idx+1:]...)
|
||||||
@@ -261,18 +276,6 @@ func runWrapper() {
|
|||||||
disableGhostText.Store(!config.Get().UI.GhostText)
|
disableGhostText.Store(!config.Get().UI.GhostText)
|
||||||
var renderOverlay func()
|
var renderOverlay func()
|
||||||
|
|
||||||
isExecuting := func() bool {
|
|
||||||
pgrp, err := unix.IoctlGetInt(int(ptmx.Fd()), unix.TIOCGPGRP)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
shellPGID, err := unix.Getpgid(core.ShellPID)
|
|
||||||
if err != nil {
|
|
||||||
return pgrp != core.ShellPID
|
|
||||||
}
|
|
||||||
return pgrp != shellPGID
|
|
||||||
}
|
|
||||||
|
|
||||||
// listen for suggestion requests from shell scripts via the ipc pipe
|
// listen for suggestion requests from shell scripts via the ipc pipe
|
||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -376,7 +379,7 @@ func runWrapper() {
|
|||||||
modeCopy := activeMode
|
modeCopy := activeMode
|
||||||
activeModeMu.RUnlock()
|
activeModeMu.RUnlock()
|
||||||
|
|
||||||
navCopy := userNavigated
|
navCopy := userNavigated.Load()
|
||||||
|
|
||||||
runes := []rune(bufCopy)
|
runes := []rune(bufCopy)
|
||||||
if offsetCopy > 0 && offsetCopy <= len(runes) {
|
if offsetCopy > 0 && offsetCopy <= len(runes) {
|
||||||
@@ -435,7 +438,7 @@ func runWrapper() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if userNavigated {
|
if userNavigated.Load() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -505,7 +508,7 @@ func runWrapper() {
|
|||||||
|
|
||||||
if overlay.Visible && (inputSlice[i+2] == 'A' || inputSlice[i+2] == 'B') {
|
if overlay.Visible && (inputSlice[i+2] == 'A' || inputSlice[i+2] == 'B') {
|
||||||
intercepted = true
|
intercepted = true
|
||||||
userNavigated = true
|
userNavigated.Store(true)
|
||||||
overlay.UserNavigated = true
|
overlay.UserNavigated = true
|
||||||
|
|
||||||
// clear ghost text synchronously
|
// clear ghost text synchronously
|
||||||
@@ -596,7 +599,7 @@ func runWrapper() {
|
|||||||
cursorOffset = 0
|
cursorOffset = 0
|
||||||
bufferMu.Unlock()
|
bufferMu.Unlock()
|
||||||
|
|
||||||
userNavigated = true
|
userNavigated.Store(true)
|
||||||
overlay.UserNavigated = true
|
overlay.UserNavigated = true
|
||||||
|
|
||||||
writeStdout([]byte(overlay.Render()))
|
writeStdout([]byte(overlay.Render()))
|
||||||
@@ -648,7 +651,7 @@ func runWrapper() {
|
|||||||
cursorOffset = len(naiveBuffer)
|
cursorOffset = len(naiveBuffer)
|
||||||
}
|
}
|
||||||
shouldOverlayDraw = true
|
shouldOverlayDraw = true
|
||||||
userNavigated = false
|
userNavigated.Store(false)
|
||||||
}
|
}
|
||||||
bufferMu.Unlock()
|
bufferMu.Unlock()
|
||||||
isLeftRightArrow = true
|
isLeftRightArrow = true
|
||||||
@@ -668,7 +671,7 @@ func runWrapper() {
|
|||||||
cursorOffset = 0
|
cursorOffset = 0
|
||||||
}
|
}
|
||||||
shouldOverlayDraw = true
|
shouldOverlayDraw = true
|
||||||
userNavigated = false
|
userNavigated.Store(false)
|
||||||
}
|
}
|
||||||
bufferMu.Unlock()
|
bufferMu.Unlock()
|
||||||
isLeftRightArrow = true
|
isLeftRightArrow = true
|
||||||
@@ -710,14 +713,14 @@ func runWrapper() {
|
|||||||
saveMode(activeMode)
|
saveMode(activeMode)
|
||||||
activeModeMu.Unlock()
|
activeModeMu.Unlock()
|
||||||
logger.Debugf("Intercepted Ctrl+R, toggled mode to %q", activeMode)
|
logger.Debugf("Intercepted Ctrl+R, toggled mode to %q", activeMode)
|
||||||
if userNavigated {
|
if userNavigated.Load() {
|
||||||
bufferMu.Lock()
|
bufferMu.Lock()
|
||||||
naiveBuffer = overlay.TypedQuery
|
naiveBuffer = overlay.TypedQuery
|
||||||
cursorOffset = 0
|
cursorOffset = 0
|
||||||
bufferMu.Unlock()
|
bufferMu.Unlock()
|
||||||
_, _ = ptmx.Write(append([]byte{0x15}, overlay.TypedQuery...))
|
_, _ = ptmx.Write(append([]byte{0x15}, overlay.TypedQuery...))
|
||||||
}
|
}
|
||||||
userNavigated = false
|
userNavigated.Store(false)
|
||||||
overlay.UserNavigated = false
|
overlay.UserNavigated = false
|
||||||
shouldOverlayDraw = true
|
shouldOverlayDraw = true
|
||||||
// enter: enter behavior is a bit different from tab suggestions in code editor
|
// enter: enter behavior is a bit different from tab suggestions in code editor
|
||||||
@@ -746,7 +749,7 @@ func runWrapper() {
|
|||||||
bufferMu.Unlock()
|
bufferMu.Unlock()
|
||||||
disableGhostText.Store(false)
|
disableGhostText.Store(false)
|
||||||
shouldOverlayDraw = false
|
shouldOverlayDraw = false
|
||||||
userNavigated = false
|
userNavigated.Store(false)
|
||||||
continue
|
continue
|
||||||
} else if b == 0x09 { // tab: select suggestions
|
} else if b == 0x09 { // tab: select suggestions
|
||||||
intercepted = true
|
intercepted = true
|
||||||
@@ -776,7 +779,7 @@ func runWrapper() {
|
|||||||
shouldOverlayDraw = true // <- rerender after tab to choose, if you set to false,
|
shouldOverlayDraw = true // <- rerender after tab to choose, if you set to false,
|
||||||
// when you press tab continually, it will print all folder from menu suggestions
|
// when you press tab continually, it will print all folder from menu suggestions
|
||||||
// and make the cursor jump to next line
|
// and make the cursor jump to next line
|
||||||
userNavigated = false
|
userNavigated.Store(false)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -793,7 +796,7 @@ func runWrapper() {
|
|||||||
shouldOverlayDraw = true
|
shouldOverlayDraw = true
|
||||||
}
|
}
|
||||||
bufferMu.Unlock()
|
bufferMu.Unlock()
|
||||||
userNavigated = false
|
userNavigated.Store(false)
|
||||||
case 0x05: // ctrl+e: move to end of line
|
case 0x05: // ctrl+e: move to end of line
|
||||||
bufferMu.Lock()
|
bufferMu.Lock()
|
||||||
cursorOffset = 0
|
cursorOffset = 0
|
||||||
@@ -801,7 +804,7 @@ func runWrapper() {
|
|||||||
shouldOverlayDraw = true
|
shouldOverlayDraw = true
|
||||||
}
|
}
|
||||||
bufferMu.Unlock()
|
bufferMu.Unlock()
|
||||||
userNavigated = false
|
userNavigated.Store(false)
|
||||||
|
|
||||||
case 127, 0x08: // backspace: remove character
|
case 127, 0x08: // backspace: remove character
|
||||||
bufferMu.Lock()
|
bufferMu.Lock()
|
||||||
@@ -824,7 +827,7 @@ func runWrapper() {
|
|||||||
}
|
}
|
||||||
bufferMu.Unlock()
|
bufferMu.Unlock()
|
||||||
shouldOverlayDraw = true
|
shouldOverlayDraw = true
|
||||||
userNavigated = false
|
userNavigated.Store(false)
|
||||||
case 0x17: // ctrl+w: delete the last word in the buffer
|
case 0x17: // ctrl+w: delete the last word in the buffer
|
||||||
bufferMu.Lock()
|
bufferMu.Lock()
|
||||||
trimBuf := strings.TrimRight(naiveBuffer, " ")
|
trimBuf := strings.TrimRight(naiveBuffer, " ")
|
||||||
@@ -837,10 +840,10 @@ func runWrapper() {
|
|||||||
cursorOffset = 0
|
cursorOffset = 0
|
||||||
bufferMu.Unlock()
|
bufferMu.Unlock()
|
||||||
shouldOverlayDraw = true
|
shouldOverlayDraw = true
|
||||||
userNavigated = false
|
userNavigated.Store(false)
|
||||||
case 0x0c: // ctrl+l: clear screen but keep buffer and redraw menu
|
case 0x0c: // ctrl+l: clear screen but keep buffer and redraw menu
|
||||||
shouldOverlayDraw = true
|
shouldOverlayDraw = true
|
||||||
userNavigated = false
|
userNavigated.Store(false)
|
||||||
case '\r', '\n', 0x03, 0x15: // enter, ctrl+c, ctrl+u: clear buffer on line reset
|
case '\r', '\n', 0x03, 0x15: // enter, ctrl+c, ctrl+u: clear buffer on line reset
|
||||||
bufferMu.Lock()
|
bufferMu.Lock()
|
||||||
naiveBuffer = ""
|
naiveBuffer = ""
|
||||||
@@ -851,7 +854,7 @@ func runWrapper() {
|
|||||||
activeModeMu.Unlock()
|
activeModeMu.Unlock()
|
||||||
disableGhostText.Store(false)
|
disableGhostText.Store(false)
|
||||||
writeStdout([]byte(overlay.ClearAndDisable()))
|
writeStdout([]byte(overlay.ClearAndDisable()))
|
||||||
userNavigated = false
|
userNavigated.Store(false)
|
||||||
default:
|
default:
|
||||||
// track normal printable characters in the buffer for matching
|
// track normal printable characters in the buffer for matching
|
||||||
if b >= 32 && b <= 126 {
|
if b >= 32 && b <= 126 {
|
||||||
@@ -892,7 +895,7 @@ func runWrapper() {
|
|||||||
}
|
}
|
||||||
bufferMu.Unlock()
|
bufferMu.Unlock()
|
||||||
shouldOverlayDraw = true
|
shouldOverlayDraw = true
|
||||||
userNavigated = false
|
userNavigated.Store(false)
|
||||||
overlay.UserNavigated = false
|
overlay.UserNavigated = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user