Skip to content

Resident server pegs ~100% CPU and save/flush hangs indefinitely on formula-heavy .xlsx (macOS arm64) #187

Description

@nightskat

Summary

On macOS (Apple Silicon), editing an .xlsx that contains many expensive formulas (whole-column SUMIFS, expanding-range COUNTIF, SUMPRODUCT+COUNTIF) makes the auto-spawned __resident-serve__ process peg ~100% CPU and any flush-to-disk operation (save, close, or set under OFFICECLI_RESIDENT_FLUSH=each) hang indefinitely. Recovering by killing the resident loses all unflushed edits.

Environment

  • officecli 1.0.132 (Homebrew bottle)
  • macOS arm64 (Apple M1, 8 GB RAM)
  • .NET runtime (Homebrew dotnet dependency)

Impact

  • A plain set returns success but the change does not reach disk within the documented 2–10s idle-autoflush window; an external reader (openpyxl / Excel / LibreOffice) keeps seeing the pre-edit file. officecli get reads from the resident's memory so it does show the new value — which makes it easy to believe the write persisted when it hasn't.
  • save / close never return (observed >2 min; SIGKILL required).
  • After SIGKILL, the unflushed edit is gone → silent data-loss risk for any "edit then hand the file off" workflow.

Reproduction

Fully synthetic — no external data. Generate the workbook:

import openpyxl
wb = openpyxl.Workbook()
pln = wb.active; pln.title = "PLN"
pln.append(["Branch","PGD","CIF","Ten","Loai","MaHD","DuNo","KyHan"])
for r in range(2, 2002):
    pln.append(["HP","PGD1",f"C{r%300}","KH","INDIV",f"HD{r}",(r*13)%9_000_000_000,
                "NGAN_HAN" if r%2 else "TRUNG_DAI_HAN"])
hd = wb.create_sheet("HD"); hd.append(["CIF","Ten","MaHD","MaHT","SoTien"])
for r in range(2, 502): hd.append([f"C{r%300}","KH",f"HD{r}",f"HT{r}",(r*7)%9_000_000_000])
nt = wb.create_sheet("NOTE"); nt.append(["CIF","Ten","Flag"])
for r in range(2,302): nt.append([f"C{r}","KH","LAM_KINH_DOANH"])
rep = wb.create_sheet("REP"); rep["A1"]="HP"
for r in range(2, 502):
    rep.cell(r,11, f"=HD!$C{r}"); rep.cell(r,10, f"=HD!$A{r}")
    rep.cell(r,2,  f'=IFERROR(INDEX(PLN!$B:$B,MATCH($K{r},PLN!$F:$F,0)),"")')
    rep.cell(r,8,  f'=IF($A$1="","",SUMIFS(PLN!$G:$G,PLN!$F:$F,$K{r},PLN!$C:$C,$J{r},PLN!$A:$A,"HP")/1E6)')
    rep.cell(r,9,  f'=IF($A$1="","",SUMIFS(PLN!$G:$G,PLN!$F:$F,$K{r},PLN!$C:$C,$J{r},PLN!$H:$H,"NGAN_HAN",PLN!$A:$A,"HP")/1E6)')
    rep.cell(r,12, f'=IF($A$1="","",SUMIFS(PLN!$G:$G,PLN!$F:$F,$K{r},PLN!$C:$C,$J{r},PLN!$H:$H,"TRUNG_DAI_HAN",PLN!$A:$A,"HP")/1E6)')
    rep.cell(r,15, f'=IFERROR(VLOOKUP($J{r},NOTE!$A:$C,3,0),"CHUA")')
    rep.cell(r,16, f'=SUMIF(HD!$A:$A,$J{r},HD!$E:$E)/1E6')
qc = wb.create_sheet("QC")
qc["B2"]='=SUMPRODUCT((HD!$A$2:$A$2000<>"")*(COUNTIF(PLN!$F$2:$F$2000,HD!$C$2:$C$2000)=0))'
for r in range(2, 2002):
    qc.cell(r,4, f'=IF(AND(PLN!$F{r}<>"",PLN!$A{r}="HP",PLN!$E{r}="INDIV",PLN!$G{r}>0,COUNTIF(HD!$C:$C,PLN!$F{r})=0),PLN!$F{r},"")')
    qc.cell(r,5, f'=IF($D{r}="","",IF(COUNTIF($D$2:D{r},$D{r})=1,1,0))')
    qc.cell(r,7, f'=IF(AND(HD!$A{r}<>"",SUMIF(HD!$A:$A,HD!$A{r},HD!$E:$E)/1E6>=5,IFERROR(VLOOKUP(HD!$A{r},NOTE!$A:$C,3,0),"CHUA")="CHUA"),HD!$A{r},"")')
    qc.cell(r,8, f'=IF($G{r}="","",IF(COUNTIF($G$2:G{r},$G{r})=1,1,0))')
wb.save("faithful.xlsx")   # ~12,000 formula cells across 5 sheets

Then:

officecli set faithful.xlsx /REP/A1 --prop value="HP"   # returns in <1s, "success"
ps aux | grep __resident-serve__                        # resident now ~93–104% CPU
officecli save faithful.xlsx                            # never returns (>2 min) → had to SIGKILL

Contrast (rules out "too many cells")

A workbook with 11,500 trivial formulas (=IF($A$1="","",A1+1)) does not reproduce it: resident sits at ~0.5% CPU and save is instant. So the trigger is formula evaluation cost (whole-column ranges + O(n²) expanding COUNTIF), not formula count. A small file with the same trivial formula flushes fine within the idle window.

Guess at the cause

It looks like the resident performs a full synchronous recalculation of the whole workbook on flush (or on a polling tick), and for these whole-column / O(n²) formulas that recalc is pathologically slow or re-entrant (CPU stays pegged even while "idle"). Options that would likely help: time-box / make the recalc incremental, or skip recalc-on-flush when the caller only set literal values (no formula edits).

Workaround for other users (until fixed)

For bulk edits to formula-heavy workbooks, write cells with a library that does a deterministic disk write (e.g. openpyxl) and recalculate with an independent engine (e.g. soffice --headless --convert-to). Reserve officecli for small, targeted edits and always save/close + verify with an external reader before handing the file off.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions