Skip to content

Commit 27b5699

Browse files
authored
Simplify cache pool contexts (#23280)
The `pooledCache` variable always points to either `root.pooledCache` or the stack cursor that is used to track caches that were resumed from a previous render. We can get rid of it by reading from those instead. This simplifies the code a lot and is harder to mess up, I think.
1 parent 08fce62 commit 27b5699

File tree

3 files changed

+145
-148
lines changed

3 files changed

+145
-148
lines changed

packages/react-reconciler/src/ReactFiberCacheComponent.new.js

Lines changed: 68 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {isPrimaryRenderer} from './ReactFiberHostConfig';
1919
import {createCursor, push, pop} from './ReactFiberStack.new';
2020
import {pushProvider, popProvider} from './ReactFiberNewContext.new';
2121
import * as Scheduler from 'scheduler';
22+
import {getWorkInProgressRoot} from './ReactFiberWorkLoop.new';
2223

2324
export type Cache = {|
2425
controller: AbortController,
@@ -61,13 +62,9 @@ if (__DEV__ && enableCache) {
6162
CacheContext._currentRenderer2 = null;
6263
}
6364

64-
// The cache that newly mounted Cache boundaries should use. It's either
65-
// retrieved from the cache pool, or the result of a refresh.
66-
let pooledCache: Cache | null = null;
67-
68-
// When retrying a Suspense/Offscreen boundary, we override pooledCache with the
69-
// cache from the render that suspended.
70-
const prevFreshCacheOnStack: StackCursor<Cache | null> = createCursor(null);
65+
// When retrying a Suspense/Offscreen boundary, we restore the cache that was
66+
// used during the previous render by placing it here, on the stack.
67+
const resumedCache: StackCursor<Cache | null> = createCursor(null);
7168

7269
// Creates a new empty Cache instance with a ref-count of 0. The caller is responsible
7370
// for retaining the cache once it is in use (retainCache), and releasing the cache
@@ -135,56 +132,70 @@ export function popCacheProvider(workInProgress: Fiber, cache: Cache) {
135132
popProvider(CacheContext, workInProgress);
136133
}
137134

138-
export function requestCacheFromPool(renderLanes: Lanes): Cache {
135+
function peekCacheFromPool(): Cache | null {
139136
if (!enableCache) {
140137
return (null: any);
141138
}
142-
if (pooledCache !== null) {
143-
return pooledCache;
139+
140+
// Check if the cache pool already has a cache we can use.
141+
142+
// If we're rendering inside a Suspense boundary that is currently hidden,
143+
// we should use the same cache that we used during the previous render, if
144+
// one exists.
145+
const cacheResumedFromPreviousRender = resumedCache.current;
146+
if (cacheResumedFromPreviousRender !== null) {
147+
return cacheResumedFromPreviousRender;
144148
}
145-
// Create a fresh cache. The pooled cache must be owned - it is freed
146-
// in releaseRootPooledCache() - but the cache instance handed out
147-
// is retained/released in the commit phase of the component that
148-
// references is (ie the host root, cache boundary, suspense component)
149-
// Ie, pooledCache is conceptually an Option<Arc<Cache>> (owned),
150-
// whereas the return value of this function is a &Arc<Cache> (borrowed).
151-
pooledCache = createCache();
152-
retainCache(pooledCache);
153-
return pooledCache;
149+
150+
// Otherwise, check the root's cache pool.
151+
const root = (getWorkInProgressRoot(): any);
152+
const cacheFromRootCachePool = root.pooledCache;
153+
154+
return cacheFromRootCachePool;
155+
}
156+
157+
export function requestCacheFromPool(renderLanes: Lanes): Cache {
158+
// Similar to previous function, except if there's not already a cache in the
159+
// pool, we allocate a new one.
160+
const cacheFromPool = peekCacheFromPool();
161+
if (cacheFromPool !== null) {
162+
return cacheFromPool;
163+
}
164+
165+
// Create a fresh cache and add it to the root cache pool. A cache can have
166+
// multiple owners:
167+
// - A cache pool that lives on the FiberRoot. This is where all fresh caches
168+
// are originally created (TODO: except during refreshes, until we implement
169+
// this correctly). The root takes ownership immediately when the cache is
170+
// created. Conceptually, root.pooledCache is an Option<Arc<Cache>> (owned),
171+
// and the return value of this function is a &Arc<Cache> (borrowed).
172+
// - One of several fiber types: host root, cache boundary, suspense
173+
// component. These retain and release in the commit phase.
174+
175+
const root = (getWorkInProgressRoot(): any);
176+
const freshCache = createCache();
177+
root.pooledCache = freshCache;
178+
retainCache(freshCache);
179+
if (freshCache !== null) {
180+
root.pooledCacheLanes |= renderLanes;
181+
}
182+
return freshCache;
154183
}
155184

156185
export function pushRootCachePool(root: FiberRoot) {
157186
if (!enableCache) {
158187
return;
159188
}
160-
// When we start rendering a tree, read the pooled cache for this render
161-
// from `root.pooledCache`. If it's currently `null`, we will lazily
162-
// initialize it the first type it's requested. However, we only mutate
163-
// the root itself during the complete/unwind phase of the HostRoot.
164-
const rootCache = root.pooledCache;
165-
if (rootCache != null) {
166-
pooledCache = rootCache;
167-
root.pooledCache = null;
168-
} else {
169-
pooledCache = null;
170-
}
189+
// Note: This function currently does nothing but I'll leave it here for
190+
// code organization purposes in case that changes.
171191
}
172192

173193
export function popRootCachePool(root: FiberRoot, renderLanes: Lanes) {
174194
if (!enableCache) {
175195
return;
176196
}
177-
// The `pooledCache` variable points to the cache that was used for new
178-
// cache boundaries during this render, if any. Move ownership of the
179-
// cache to the root so that parallel transitions may share the same
180-
// cache. We will clear this field once all the transitions that depend
181-
// on it (which we track with `pooledCacheLanes`) have committed.
182-
root.pooledCache = pooledCache;
183-
if (pooledCache !== null) {
184-
root.pooledCacheLanes |= renderLanes;
185-
}
186-
// set to null, conceptually we are moving ownership to the root
187-
pooledCache = null;
197+
// Note: This function currently does nothing but I'll leave it here for
198+
// code organization purposes in case that changes.
188199
}
189200

190201
export function restoreSpawnedCachePool(
@@ -202,51 +213,35 @@ export function restoreSpawnedCachePool(
202213
// will override it.
203214
return null;
204215
} else {
205-
// No refresh. Resume with the previous cache. This will override the cache
206-
// pool so that any new Cache boundaries in the subtree use this one instead
207-
// of requesting a fresh one.
208-
push(prevFreshCacheOnStack, pooledCache, offscreenWorkInProgress);
209-
pooledCache = prevCachePool.pool;
216+
// No refresh. Resume with the previous cache. New Cache boundaries in the
217+
// subtree use this one instead of requesting a fresh one (see
218+
// peekCacheFromPool).
219+
push(resumedCache, prevCachePool.pool, offscreenWorkInProgress);
210220

211221
// Return the cache pool to signal that we did in fact push it. We will
212222
// assign this to the field on the fiber so we know to pop the context.
213223
return prevCachePool;
214224
}
215225
}
216226

217-
// Note: Ideally, `popCachePool` would return this value, and then we would pass
218-
// it to `getSuspendedCachePool`. But factoring reasons, those two functions are
219-
// in different phases/files. They are always called in sequence, though, so we
220-
// can stash the value here temporarily.
221-
let _suspendedPooledCache: Cache | null = null;
222-
223227
export function popCachePool(workInProgress: Fiber) {
224228
if (!enableCache) {
225229
return;
226230
}
227-
_suspendedPooledCache = pooledCache;
228-
pooledCache = prevFreshCacheOnStack.current;
229-
pop(prevFreshCacheOnStack, workInProgress);
231+
pop(resumedCache, workInProgress);
230232
}
231233

232234
export function getSuspendedCachePool(): SpawnedCachePool | null {
233235
if (!enableCache) {
234236
return null;
235237
}
236-
// We check the cache on the stack first, since that's the one any new Caches
237-
// would have accessed.
238-
let pool = pooledCache;
239-
if (pool === null) {
240-
// There's no pooled cache above us in the stack. However, a child in the
241-
// suspended tree may have requested a fresh cache pool. If so, we would
242-
// have unwound it with `popCachePool`.
243-
if (_suspendedPooledCache !== null) {
244-
pool = _suspendedPooledCache;
245-
_suspendedPooledCache = null;
246-
} else {
247-
// There's no suspended cache pool.
248-
return null;
249-
}
238+
// This function is called when a Suspense boundary suspends. It returns the
239+
// cache that would have been used to render fresh data during this render,
240+
// if there was any, so that we can resume rendering with the same cache when
241+
// we receive more data.
242+
const cacheFromPool = peekCacheFromPool();
243+
if (cacheFromPool === null) {
244+
return null;
250245
}
251246

252247
return {
@@ -255,7 +250,7 @@ export function getSuspendedCachePool(): SpawnedCachePool | null {
255250
parent: isPrimaryRenderer
256251
? CacheContext._currentValue
257252
: CacheContext._currentValue2,
258-
pool,
253+
pool: cacheFromPool,
259254
};
260255
}
261256

@@ -264,8 +259,8 @@ export function getOffscreenDeferredCachePool(): SpawnedCachePool | null {
264259
return null;
265260
}
266261

267-
if (pooledCache === null) {
268-
// There's no deferred cache pool.
262+
const cacheFromPool = peekCacheFromPool();
263+
if (cacheFromPool === null) {
269264
return null;
270265
}
271266

@@ -275,6 +270,6 @@ export function getOffscreenDeferredCachePool(): SpawnedCachePool | null {
275270
parent: isPrimaryRenderer
276271
? CacheContext._currentValue
277272
: CacheContext._currentValue2,
278-
pool: pooledCache,
273+
pool: cacheFromPool,
279274
};
280275
}

0 commit comments

Comments
 (0)