cat / 012
clone

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

Don't let a dead inherited OTLP endpoint slow quitting

Dami committed 902e724
The last flush waits at most a second, and not at all once a send has
failed: an OTEL_EXPORTER_OTLP_ENDPOINT inherited from the desktop session
may name a collector that isn't up.
commit
902e724f8053380a05a2a336b5c1109b47ec5320
parent
82eb79a0a0b3ee0071cdb1cad8cf68c9a9cac68e
author
Dami <dami.etoile@gmail.com>
4 files changed, +67 -10
MROADMAP.md3+--
Mdocs/observability.md12+++++++++--
Minternal/telemetry/otlp.go27++++++++++++++++++-----
Minternal/telemetry/otlp_test.go35++++++++++++++++++++++++++++++
M ROADMAP.md +1 -2
@@ -102,7 +102,7 @@ work left open. Sizes: S (a day or two), M (about a week), L (weeks).
102102 103103 | Item | Why | Size |104104 |---|---|---|105-| Open 012's XLSX output in real Excel and LibreOffice (a round-trip corpus, run by hand before releases) | The new writer is checked only by excelize and 012's reader | S |105+| Open 012's XLSX output in real Excel and LibreOffice (a round-trip corpus, run by hand before releases). In progress: a first sample is being checked in Excel | The new writer is checked only by excelize and 012's reader | S |106106 | Column and row formats travel with copy, cut and move; formulas reading blanks of a newly formatted column re-infer at once | Left open by step A | S |107107 | Save as onto another existing file asks before replacing (local and `012 serve`) | Only the open file is checked today | S |108108 | XLSX sheet names with characters Excel forbids: rewrite formulas that name them, not just the sheet | Formulas keep the old name today | S |
@@ -110,7 +110,6 @@ work left open. Sizes: S (a day or two), M (about a week), L (weeks).
110110 | Precedent tracing explains when every precedent is on a hidden sheet | Says "no formula" today | S |111111 | Settings > JEV API key checks the key with a test call | Left open by the config work | S |112112 | `012 serve`: open a file from the ssh command line (`ssh -t host file.012` as a request, not exec), and save unsaved work to a recovery file on shutdown or idle timeout | Sessions start empty; unsaved work is lost today | M |113-| Decide whether 012 honors OTEL_* variables it inherits from the desktop session, or only its own config | A GUI-launched 012 exports to whatever the session's variables name | S |114113 115114 ### 2. Spreadsheet features Sheets users reach for116115
M docs/observability.md +10 -2
M internal/telemetry/otlp.go +21 -6
@@ -68,6 +68,7 @@ type exporter struct {
6868 sent [3]int64 // items accepted by the endpoint6969 dropped [3]int64 // items dropped because the queue was full7070 failed [3]int64 // items in requests that failed71+ down bool // the last request failed: the endpoint looks unreachable7172 7273 wake chan struct{}7374 done chan struct{}
@@ -215,11 +216,18 @@ func (e *exporter) run() {
215216 case <-t.C:216217 case <-e.wake:217218 case <-e.done:218- // The last flush shares one timeout, so quitting 012 waits219- // at most that long for a collector that doesn't answer.220- ctx, cancel := context.WithTimeout(context.Background(), e.timeout)221- e.flush(ctx, true)222- cancel()219+ // Quitting waits at most finalTimeout for the last flush,220+ // and not at all when the endpoint already failed: an221+ // inherited OTEL_EXPORTER_OTLP_ENDPOINT may name a collector222+ // that's gone, and quitting shouldn't wait on it.223+ e.mu.Lock()224+ down := e.down225+ e.mu.Unlock()226+ if !down {227+ ctx, cancel := context.WithTimeout(context.Background(), e.finalTimeout())228+ e.flush(ctx, true)229+ cancel()230+ }223231 return224232 }225233 e.flush(context.Background(), false)
@@ -232,10 +240,16 @@ func (e *exporter) shutdown() {
232240 close(e.done)233241 select {234242 case <-e.stopped:235- case <-time.After(e.timeout + time.Second):243+ case <-time.After(e.finalTimeout() + 250*time.Millisecond):236244 }237245 }238246 247+// finalTimeout bounds the flush when 012 quits.248+func (e *exporter) finalTimeout() time.Duration { return min(e.timeout, finalFlush) }249+250+// finalFlush is the most quitting waits for the last flush.251+const finalFlush = time.Second252+239253 func (e *exporter) flush(ctx context.Context, final bool) {240254 e.mu.Lock()241255 logs, spans, windows := e.logs, e.spans, e.windows
@@ -269,6 +283,7 @@ func (e *exporter) send(ctx context.Context, sig, items int, body any) {
269283 url := [3]string{e.urls.logs, e.urls.traces, e.urls.metrics}[sig]270284 err := e.post(ctx, url, body)271285 e.mu.Lock()286+ e.down = err != nil272287 if err != nil {273288 e.failed[sig] += int64(items)274289 } else {
M internal/telemetry/otlp_test.go +35 -0