Summary
Every guarded delete in the GemStone Explorer scans the image for what still references the
target. That scan is run once per method environment, one after another, and each one blocks
the extension host. On a stone with gemstone.maxEnvironment above 0 the editor therefore
freezes for N whole-image scans instead of one, and the freeze gets linearly longer with every
environment configured. A progress notification is shown, but it cannot spin — the GCI calls are
synchronous — so what the user sees is a frozen window for N times as long as it needs to be.
This is a performance problem, not a correctness one: the results are right, they just cost far
more than they should. Raised in review of #476 and agreed there as follow-up work rather than
something to hold that PR for.
Where
ExplorerController.scanReferences in client/src/gemstoneExplorer.ts:
const perEnv = this.environmentsToScan().map((env) => scan(env));
environmentsToScan() answers 0..gemstone.maxEnvironment, and scan is one blocking GCI round
trip. All four guarded deletes go through it — remove method, remove class, remove instance
variable, remove class variable — via sendersOf, referencesToClassInDict,
methodsAccessingInstVar and methodsAccessingClassVar in client/src/queries/.
What to do instead
Push the environment loop into the Smalltalk side so one round trip answers for every
environment, with the environment carried on each returned row. The row format already has a
column for it (methodSerialization in client/src/queries/methodSearch.ts emits it, and
MethodSearchResult.environmentId consumes it), so this is a change to how the scan is driven
rather than to what a result looks like.
Points to keep:
- The client already folds rows from several environments into one list with
dedupeMethodResults, which treats class, side, selector and environment as a method's
identity. A server-side loop must keep producing rows that fold the same way.
- The row cap is applied per query today, and
scanReferences reports the scan as truncated
when any single environment comes back full. Whatever replaces it has to keep answering the
same question — "were rows dropped?" — because the confirmation states its count as a floor
rather than as fact when they were.
- The same one-query-per-environment shape exists in the Senders / Implementors / hierarchy
implementors / References commands in client/src/extension.ts, which sweep
0..maxEnvironment in a for loop. Worth fixing together, since a shared scan primitive is
the natural home for it — see the companion cleanup issue.
Acceptance criteria
- A guarded delete costs one round trip regardless of
gemstone.maxEnvironment.
- Freeze time no longer scales with the number of environments configured.
- Results are unchanged: the same methods are found, still carrying the environment they were
found in, and the truncation hedge still appears when rows were dropped.
- The single-environment case (
maxEnvironment at its default of 0) is no slower than today.
Reference
Review comment: #476 (comment)
Summary
Every guarded delete in the GemStone Explorer scans the image for what still references the
target. That scan is run once per method environment, one after another, and each one blocks
the extension host. On a stone with
gemstone.maxEnvironmentabove 0 the editor thereforefreezes for N whole-image scans instead of one, and the freeze gets linearly longer with every
environment configured. A progress notification is shown, but it cannot spin — the GCI calls are
synchronous — so what the user sees is a frozen window for N times as long as it needs to be.
This is a performance problem, not a correctness one: the results are right, they just cost far
more than they should. Raised in review of #476 and agreed there as follow-up work rather than
something to hold that PR for.
Where
ExplorerController.scanReferencesinclient/src/gemstoneExplorer.ts:environmentsToScan()answers0..gemstone.maxEnvironment, andscanis one blocking GCI roundtrip. All four guarded deletes go through it — remove method, remove class, remove instance
variable, remove class variable — via
sendersOf,referencesToClassInDict,methodsAccessingInstVarandmethodsAccessingClassVarinclient/src/queries/.What to do instead
Push the environment loop into the Smalltalk side so one round trip answers for every
environment, with the environment carried on each returned row. The row format already has a
column for it (
methodSerializationinclient/src/queries/methodSearch.tsemits it, andMethodSearchResult.environmentIdconsumes it), so this is a change to how the scan is drivenrather than to what a result looks like.
Points to keep:
dedupeMethodResults, which treats class, side, selector and environment as a method'sidentity. A server-side loop must keep producing rows that fold the same way.
scanReferencesreports the scan as truncatedwhen any single environment comes back full. Whatever replaces it has to keep answering the
same question — "were rows dropped?" — because the confirmation states its count as a floor
rather than as fact when they were.
implementors / References commands in
client/src/extension.ts, which sweep0..maxEnvironmentin aforloop. Worth fixing together, since a shared scan primitive isthe natural home for it — see the companion cleanup issue.
Acceptance criteria
gemstone.maxEnvironment.found in, and the truncation hedge still appears when rows were dropped.
maxEnvironmentat its default of 0) is no slower than today.Reference
Review comment: #476 (comment)