Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Duplicate of #22510 to see if CI will run #22555

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
c72b1da
Release pooled cache reference in complete/unwind
josephsavona Sep 29, 2021
8d5b37b
Expand Cache type w ref count
josephsavona Sep 29, 2021
78c4bdc
Add AbortController to cache object and abort when refcount=0
josephsavona Sep 29, 2021
93020d4
Add getCacheSignal() and release pooled cache in commitRoot()
josephsavona Sep 30, 2021
9031457
Extract commit root cache cleanup into a function, handle error case
josephsavona Sep 30, 2021
b8e67c3
[wip] partial passive effects stage cleanup of cache instances
josephsavona Oct 1, 2021
bf85913
small cleanup
josephsavona Oct 1, 2021
5521d7d
new caches can be created from various sources (newly mouned cache co…
josephsavona Oct 1, 2021
f506e77
code comments; first test of cleanup
josephsavona Oct 2, 2021
2af984a
wip many tests passing
josephsavona Oct 4, 2021
17c2b44
existing tests cleanup as expected
josephsavona Oct 5, 2021
e5c2575
cleanup, lint, sync fork
josephsavona Oct 5, 2021
650f108
cleanup test
josephsavona Oct 5, 2021
0bf386c
update snapshots
josephsavona Oct 5, 2021
ac7de05
put back accidentally removed logic, handle suspense, more tests
josephsavona Oct 7, 2021
d4b62c2
more cleanup, sync forks
josephsavona Oct 7, 2021
f4f6023
test cleanup and comments
josephsavona Oct 7, 2021
6fd2486
more cleanup, schedule the abort
josephsavona Oct 7, 2021
92d6e7c
sync fork
josephsavona Oct 7, 2021
7339ef5
more cleanup
josephsavona Oct 7, 2021
52067ea
new test for cleanup of abandoned transitions
josephsavona Oct 7, 2021
20ce291
handle aborted transactions
josephsavona Oct 8, 2021
3d61c1d
comments
josephsavona Oct 8, 2021
07ca3ca
Add comments/todos, move pooled cache release, add failing test cases…
josephsavona Oct 8, 2021
daba131
finalish comments
josephsavona Oct 12, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/react-dom/src/server/ReactPartialRendererHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,10 @@ export function resetHooksState(): void {
workInProgressHook = null;
}

function getCacheSignal() {
invariant(false, 'Not implemented.');
}

function getCacheForType<T>(resourceType: () => T): T {
invariant(false, 'Not implemented.');
}
Expand Down Expand Up @@ -550,6 +554,7 @@ export const Dispatcher: DispatcherType = {
};

if (enableCache) {
Dispatcher.getCacheSignal = getCacheSignal;
Dispatcher.getCacheForType = getCacheForType;
Dispatcher.useCacheRefresh = useCacheRefresh;
}
86 changes: 77 additions & 9 deletions packages/react-reconciler/src/ReactFiberCacheComponent.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,13 @@ import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';
import {isPrimaryRenderer} from './ReactFiberHostConfig';
import {createCursor, push, pop} from './ReactFiberStack.new';
import {pushProvider, popProvider} from './ReactFiberNewContext.new';
import * as Scheduler from 'scheduler';

export type Cache = Map<() => mixed, mixed>;
export type Cache = {|
controller: AbortController,
data: Map<() => mixed, mixed>,
refCount: number,
|};

export type CacheComponentState = {|
+parent: Cache,
Expand All @@ -31,6 +36,13 @@ export type SpawnedCachePool = {|
+pool: Cache,
|};

// Intentionally not named imports because Rollup would
// use dynamic dispatch for CommonJS interop named imports.
const {
unstable_scheduleCallback: scheduleCallback,
unstable_NormalPriority: NormalPriority,
} = Scheduler;

export const CacheContext: ReactContext<Cache> = enableCache
? {
$$typeof: REACT_CONTEXT_TYPE,
Expand All @@ -57,6 +69,49 @@ let pooledCache: Cache | null = null;
// cache from the render that suspended.
const prevFreshCacheOnStack: StackCursor<Cache | null> = createCursor(null);

// Creates a new empty Cache instance with a ref-count of 0. The caller is responsible
// for retaining the cache once it is in use (retainCache), and releasing the cache
// once it is no longer needed (releaseCache).
export function createCache(): Cache {
const cache: Cache = {
controller: new AbortController(),
data: new Map(),
refCount: 0,
};

return cache;
}

export function retainCache(cache: Cache) {
if (__DEV__) {
if (cache.controller.signal.aborted) {
console.warn(
'A cache instance was retained after it was already freed. ' +
'This likely indicates a bug in React.',
);
}
}
cache.refCount++;
}

// Cleanup a cache instance, potentially freeing it if there are no more references
export function releaseCache(cache: Cache) {
cache.refCount--;
if (__DEV__) {
if (cache.refCount < 0) {
console.warn(
'A cache instance was released after it was already freed. ' +
'This likely indicates a bug in React.',
);
}
}
if (cache.refCount === 0) {
scheduleCallback(NormalPriority, () => {
cache.controller.abort();
});
}
}

export function pushCacheProvider(workInProgress: Fiber, cache: Cache) {
if (!enableCache) {
return;
Expand All @@ -78,8 +133,14 @@ export function requestCacheFromPool(renderLanes: Lanes): Cache {
if (pooledCache !== null) {
return pooledCache;
}
// Create a fresh cache.
pooledCache = new Map();
// Create a fresh cache. The pooled cache must be owned - it is freed
// in releaseRootPooledCache() - but the cache instance handed out
// is retained/released in the commit phase of the component that
// references is (ie the host root, cache boundary, suspense component)
// Ie, pooledCache is conceptually an Option<Arc<Cache>> (owned),
// whereas the return value of this function is a &Arc<Cache> (borrowed).
pooledCache = createCache();
retainCache(pooledCache);
return pooledCache;
}

Expand All @@ -91,22 +152,30 @@ export function pushRootCachePool(root: FiberRoot) {
// from `root.pooledCache`. If it's currently `null`, we will lazily
// initialize it the first type it's requested. However, we only mutate
// the root itself during the complete/unwind phase of the HostRoot.
pooledCache = root.pooledCache;
const rootCache = root.pooledCache;
if (rootCache != null) {
pooledCache = rootCache;
root.pooledCache = null;
} else {
pooledCache = null;
}
}

export function popRootCachePool(root: FiberRoot, renderLanes: Lanes) {
if (!enableCache) {
return;
}
// The `pooledCache` variable points to the cache that was used for new
// cache boundaries during this render, if any. Stash it on the root so that
// parallel transitions may share the same cache. We will clear this field
// once all the transitions that depend on it (which we track with
// `pooledCacheLanes`) have committed.
// cache boundaries during this render, if any. Move ownership of the
// cache to the root so that parallel transitions may share the same
// cache. We will clear this field once all the transitions that depend
// on it (which we track with `pooledCacheLanes`) have committed.
root.pooledCache = pooledCache;
if (pooledCache !== null) {
root.pooledCacheLanes |= renderLanes;
}
// set to null, conceptually we are moving ownership to the root
pooledCache = null;
}

export function restoreSpawnedCachePool(
Expand Down Expand Up @@ -155,7 +224,6 @@ export function getSuspendedCachePool(): SpawnedCachePool | null {
if (!enableCache) {
return null;
}

// We check the cache on the stack first, since that's the one any new Caches
// would have accessed.
let pool = pooledCache;
Expand Down
86 changes: 77 additions & 9 deletions packages/react-reconciler/src/ReactFiberCacheComponent.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,13 @@ import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';
import {isPrimaryRenderer} from './ReactFiberHostConfig';
import {createCursor, push, pop} from './ReactFiberStack.old';
import {pushProvider, popProvider} from './ReactFiberNewContext.old';
import * as Scheduler from 'scheduler';

export type Cache = Map<() => mixed, mixed>;
export type Cache = {|
controller: AbortController,
data: Map<() => mixed, mixed>,
refCount: number,
|};

export type CacheComponentState = {|
+parent: Cache,
Expand All @@ -31,6 +36,13 @@ export type SpawnedCachePool = {|
+pool: Cache,
|};

// Intentionally not named imports because Rollup would
// use dynamic dispatch for CommonJS interop named imports.
const {
unstable_scheduleCallback: scheduleCallback,
unstable_NormalPriority: NormalPriority,
} = Scheduler;

export const CacheContext: ReactContext<Cache> = enableCache
? {
$$typeof: REACT_CONTEXT_TYPE,
Expand All @@ -57,6 +69,49 @@ let pooledCache: Cache | null = null;
// cache from the render that suspended.
const prevFreshCacheOnStack: StackCursor<Cache | null> = createCursor(null);

// Creates a new empty Cache instance with a ref-count of 0. The caller is responsible
// for retaining the cache once it is in use (retainCache), and releasing the cache
// once it is no longer needed (releaseCache).
export function createCache(): Cache {
const cache: Cache = {
controller: new AbortController(),
data: new Map(),
refCount: 0,
};

return cache;
}

export function retainCache(cache: Cache) {
if (__DEV__) {
if (cache.controller.signal.aborted) {
console.warn(
'A cache instance was retained after it was already freed. ' +
'This likely indicates a bug in React.',
);
}
}
cache.refCount++;
}

// Cleanup a cache instance, potentially freeing it if there are no more references
export function releaseCache(cache: Cache) {
cache.refCount--;
if (__DEV__) {
if (cache.refCount < 0) {
console.warn(
'A cache instance was released after it was already freed. ' +
'This likely indicates a bug in React.',
);
}
}
if (cache.refCount === 0) {
scheduleCallback(NormalPriority, () => {
cache.controller.abort();
});
}
}

export function pushCacheProvider(workInProgress: Fiber, cache: Cache) {
if (!enableCache) {
return;
Expand All @@ -78,8 +133,14 @@ export function requestCacheFromPool(renderLanes: Lanes): Cache {
if (pooledCache !== null) {
return pooledCache;
}
// Create a fresh cache.
pooledCache = new Map();
// Create a fresh cache. The pooled cache must be owned - it is freed
// in releaseRootPooledCache() - but the cache instance handed out
// is retained/released in the commit phase of the component that
// references is (ie the host root, cache boundary, suspense component)
// Ie, pooledCache is conceptually an Option<Arc<Cache>> (owned),
// whereas the return value of this function is a &Arc<Cache> (borrowed).
pooledCache = createCache();
retainCache(pooledCache);
return pooledCache;
}

Expand All @@ -91,22 +152,30 @@ export function pushRootCachePool(root: FiberRoot) {
// from `root.pooledCache`. If it's currently `null`, we will lazily
// initialize it the first type it's requested. However, we only mutate
// the root itself during the complete/unwind phase of the HostRoot.
pooledCache = root.pooledCache;
const rootCache = root.pooledCache;
if (rootCache != null) {
pooledCache = rootCache;
root.pooledCache = null;
} else {
pooledCache = null;
}
}

export function popRootCachePool(root: FiberRoot, renderLanes: Lanes) {
if (!enableCache) {
return;
}
// The `pooledCache` variable points to the cache that was used for new
// cache boundaries during this render, if any. Stash it on the root so that
// parallel transitions may share the same cache. We will clear this field
// once all the transitions that depend on it (which we track with
// `pooledCacheLanes`) have committed.
// cache boundaries during this render, if any. Move ownership of the
// cache to the root so that parallel transitions may share the same
// cache. We will clear this field once all the transitions that depend
// on it (which we track with `pooledCacheLanes`) have committed.
root.pooledCache = pooledCache;
if (pooledCache !== null) {
root.pooledCacheLanes |= renderLanes;
}
// set to null, conceptually we are moving ownership to the root
pooledCache = null;
}

export function restoreSpawnedCachePool(
Expand Down Expand Up @@ -155,7 +224,6 @@ export function getSuspendedCachePool(): SpawnedCachePool | null {
if (!enableCache) {
return null;
}

// We check the cache on the stack first, since that's the one any new Caches
// would have accessed.
let pool = pooledCache;
Expand Down
Loading