cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

Stop a macro's script when its program is gone, so no goroutine outlives a session

Dami committed 8368e2a
commit
8368e2a69d8af41aa9028f5e4dd7cc772f20ec6b
parent
9dc8ad42b4dd0f31c49d216ce430f8930df7e164
author
Dami <dami.etoile@gmail.com>
4 files changed, +49 -6
Mdocs/architecture.md3++-
Mdocs/limits.md8++++----
Minternal/ui/macrorun.go26+++++++++++++++++++++++++-
Minternal/ui/macroscript_test.go18++++++++++++++++++
M docs/architecture.md +2 -1
M docs/limits.md +4 -4
@@ -28,7 +28,7 @@ it lags, and past a second it stalls.
2828 | Undo | One step of any size: undo costs what the edit cost | 100 steps that each rewrite a whole column: 217 MB held | | Whole-cell before-images, 264 B per cell per step |2929 | JEV | Up to about 1000 JEV cells: 0.3 ms of CPU per answer | 4000 JEV cells: 1.3 ms per answer, 5 s of CPU to answer them all | | Every answer recalculates every JEV cell (they're volatile) |3030 | Formula depth | 10,000 nested parentheses or IFs: under 5 ms | | No explicit limit; recursion grows the stack | Recursive parser and evaluator |31-| Macros | Replaying 1000 recorded actions: 2 ms, one undo step; a script's call to the sheet: about 1.3 us | | Scripts past 10 M Starlark steps: stopped, with the line | One message per call to the sheet, served in batches on the UI goroutine |31+| Macros | Replaying 1000 recorded actions: 2.2 ms, one undo step; a script's call to the sheet: about 1.4 us | | Scripts past 10 M Starlark steps: stopped, with the line | One message per call to the sheet, served in batches on the UI goroutine |3232 | Find, filter, sort, fill | Filter or sort 8191 rows: 1 to 32 ms; find over 213 k cells: 38 ms; fill 8192 rows: 4 ms | Replace all over 213 k cells: 280 ms | | Per-cell string conversion and regexp |3333 3434 ## Sheet size
@@ -199,9 +199,9 @@ step whatever its size (`BenchmarkMacroReplay`, `BenchmarkMacroScript`,
199199 200200 | Benchmark | Time | Per action or call | Allocations |201201 |---|---|---|---|202-| Replay 1000 recorded actions, absolute references (entries, selects, extends, a command) | 2.05 ms | 2.0 us | 30 k, 1.6 MB |203-| The same with relative references (moves, formulas moved with the active cell) | 2.07 ms | 2.1 us | 31 k, 1.7 MB |204-| A script's loop: 5000 `set` and 5000 `get` calls | 13.2 ms | 1.3 us per call | 220 k, 7.8 MB |202+| Replay 1000 recorded actions, absolute references (entries, selects, extends, a command) | 2.16 ms | 2.2 us | 30 k, 1.6 MB |203+| The same with relative references (moves, formulas moved with the active cell) | 2.19 ms | 2.2 us | 31 k, 1.7 MB |204+| A script's loop: 5000 `set` and 5000 `get` calls | 13.7 ms | 1.4 us per call | 220 k, 7.8 MB |205205 206206 Reading a range walks its cells; a range larger than 4096 cells (whole207207 columns) is first trimmed to the used range, which costs a pass over the
M internal/ui/macrorun.go +25 -1
@@ -48,6 +48,8 @@ type macroRun struct {
4848 start time.Time4949 span telemetry.Span5050 print string // the last line the script printed51+52+ orphan *time.Timer // see do; used only on the script's goroutine5153 }5254 5355 type macroDone struct {
@@ -93,7 +95,7 @@ func (m *Model) startMacro(mc sheet.Macro) tea.Cmd {
9395 r.end = m.book().Begin(sheet.Change{Label: "run macro " + mc.Name, Focus: m.selection(), Sheet: m.sheet})9496 r.run = macro.New(mc.Name, mc.Source, macro.Env{9597 Host: scriptHost{m},96- Do: func(fn func()) { r.calls <- fn; <-r.ack },98+ Do: r.do,9799 Print: func(s string) { r.print = s },98100 MaxSteps: macroMaxSteps,99101 })
@@ -106,6 +108,28 @@ func (m *Model) startMacro(mc sheet.Macro) tea.Cmd {
106108 return r.wait()107109 }108110 111+// orphanAfter is how long a script waits for the UI to take a call before112+// deciding the program is gone (it quit, or its SSH session dropped) and113+// stopping, so its goroutine doesn't outlive the program. A live UI114+// always has a wait running, so it takes calls at once.115+var orphanAfter = 30 * time.Second116+117+// do hands fn to the UI goroutine and waits until it has run. It runs on118+// the script's goroutine.119+func (r *macroRun) do(fn func()) {120+ if r.orphan == nil {121+ r.orphan = time.NewTimer(orphanAfter)122+ } else {123+ r.orphan.Reset(orphanAfter)124+ }125+ select {126+ case r.calls <- fn:127+ <-r.ack128+ case <-r.orphan.C:129+ r.run.Cancel() // the call is dropped; the script stops at its next step130+ }131+}132+109133 // wait is the command that waits for the script's next call or its end.110134 func (r *macroRun) wait() tea.Cmd {111135 return func() tea.Msg {
M internal/ui/macroscript_test.go +18 -0