diff options
| -rw-r--r-- | idatui/domain.py | 36 | ||||
| -rw-r--r-- | server/patch_server.py | 24 |
2 files changed, 30 insertions, 30 deletions
diff --git a/idatui/domain.py b/idatui/domain.py index 2cfd9d9..aedade9 100644 --- a/idatui/domain.py +++ b/idatui/domain.py @@ -1011,30 +1011,26 @@ class ListingModel: want_digest = self._page_digest[p] want_rows = self._page_rows[p] want = [(h.ea, h.kind) for h in self._heads[lo:hi]] - # Ask whether the page still renders as the client holds it. The worker - # builds the rows either way (there is no knowing a line is unchanged - # without rendering it), but skipping the pickling, the transfer, the - # unpickling and the Head rebuild is about 40% of what a page costs -- - # and after a rename almost every page is unchanged. - if want_digest is not None: - try: - probe = self._prog.client.call( - "heads", addr=hex(addr), count=self.PAGE, annotate=True, - digest=True) - except Exception: # noqa: BLE001 -- an older worker has no digest - probe = None - if (isinstance(probe, dict) and probe.get("digest") == want_digest - and probe.get("count") == want_rows): - with self._lock: - if self._text_gen == gen and len(self._heads) >= hi: - for k in range(lo, hi): - self._head_gen[k] = gen - return p + 1 + # Tell the worker what we already hold. It builds the rows either way + # (there is no knowing a line is unchanged without rendering it), but if + # they still hash to the same value it keeps them: the pickling, the + # transfer, the unpickling and the Head rebuild are about 40% of what a + # page costs, and after a rename almost every page is unchanged. Sending + # the expectation rather than asking first means a page that HAS changed + # still costs one round trip. try: payload = self._prog.client.call( - "heads", addr=hex(addr), count=self.PAGE, annotate=True) + "heads", addr=hex(addr), count=self.PAGE, annotate=True, + expect="" if want_digest is None else str(want_digest)) except Exception: # noqa: BLE001 -- keep the old text rather than blank return p + 1 + if (isinstance(payload, dict) and "heads" not in payload + and payload.get("count") == want_rows): + with self._lock: + if self._text_gen == gen and len(self._heads) >= hi: + for k in range(lo, hi): + self._head_gen[k] = gen + return p + 1 rows = payload.get("heads", []) if isinstance(payload, dict) else [] page = self._build_page(rows) with self._lock: diff --git a/server/patch_server.py b/server/patch_server.py index a991158..e860676 100644 --- a/server/patch_server.py +++ b/server/patch_server.py @@ -700,7 +700,7 @@ def heads( end: Annotated[str, "Optional exclusive end address; default = segment end"] = "", back: Annotated[bool, "Walk backwards: return the count heads ENDING just before addr, in forward order"] = False, annotate: Annotated[bool, "Emit IDA-style function boundary banner rows (kind sep/funchdr)"] = False, - digest: Annotated[bool, "Return only a digest+count of the rows, not the rows themselves"] = False, + expect: Annotated[str, "Digest a caller already holds: the rows are omitted when they still hash to it"] = "", ) -> dict: """Walk item heads from ``addr`` as a flat listing: every head is rendered (code OR data OR undefined) via generate_disasm_line and stepped with @@ -824,15 +824,19 @@ def heads( rows.extend(_rows_for(ea, f)) # a struct head expands into member rows ea = _advance(ea, f) cursor = {"next": hex(ea)} if more else {"done": True} - out = {"addr": str(addr), "cursor": cursor, - "digest": _idatui_rows_digest(rows), "count": len(rows)} - # ``digest`` mode answers "is this page still exactly what you have?" without - # shipping it. The rows are built either way -- generate_disasm_line is the - # floor and there is no way to know a line is unchanged without rendering it - # -- but pickling several hundred rows with their colour spans, unpickling - # them and rebuilding Heads is about 40% of what a page costs, and after a - # rename almost every page comes back identical. - if not digest: + dig = _idatui_rows_digest(rows) + out = {"addr": str(addr), "cursor": cursor, "digest": dig, "count": len(rows)} + # ``expect`` says "I already hold a page that hashed to this". The rows are + # built either way -- generate_disasm_line is the floor and there is no way + # to know a line is unchanged without rendering it -- but pickling several + # hundred rows with their colour spans, unpickling them and rebuilding Heads + # is about 40% of what a page costs, and after a rename almost every page + # comes back identical. + # + # It carries the expected value rather than being a yes/no "digest mode" so + # that a page which HAS changed still costs one round trip: asking first and + # fetching afterwards made every changed page two. + if not (expect and str(dig) == expect): out["heads"] = rows return out |
