Skip to content

Commit b592b1b

Browse files
committed
fix(core): fix race condition in resource() (#59851)
The refactoring of `resource()` to use `linkedSignal()` introduced the potential for a race condition where resources would get stuck and not update in response to a request change. This occurred under a specific condition: 1. The request changes while the resource is still in loading state 2. The resource resolves the previous load before its `effect()` reacts to the request change. In practice, the window for this race is small, because the request change in (1) will schedule the effect in (2) immediately. However, it's easier to trigger this sequencing in tests, especially when one resource depends on the output of another. To fix the race condition, the resource impl is refactored to track the request in its state, and ignore resolved values or streams for stale requests. This refactoring actually makes the resource code simpler and easier to follow as well. Fixes #59842 PR Close #59851
1 parent 8ee91bc commit b592b1b

File tree

5 files changed

+157
-83
lines changed

5 files changed

+157
-83
lines changed

goldens/public-api/core/index.api.md

+7-2
Original file line numberDiff line numberDiff line change
@@ -1447,6 +1447,7 @@ export type Predicate<T> = (value: T) => boolean;
14471447
// @public
14481448
export interface PromiseResourceOptions<T, R> extends BaseResourceOptions<T, R> {
14491449
loader: ResourceLoader<T, R>;
1450+
stream?: never;
14501451
}
14511452

14521453
// @public
@@ -1649,11 +1650,14 @@ export enum ResourceStatus {
16491650
}
16501651

16511652
// @public
1652-
export type ResourceStreamingLoader<T, R> = (param: ResourceLoaderParams<R>) => PromiseLike<Signal<{
1653+
export type ResourceStreamingLoader<T, R> = (param: ResourceLoaderParams<R>) => PromiseLike<Signal<ResourceStreamItem<T>>>;
1654+
1655+
// @public (undocumented)
1656+
export type ResourceStreamItem<T> = {
16531657
value: T;
16541658
} | {
16551659
error: unknown;
1656-
}>>;
1660+
};
16571661

16581662
// @public
16591663
export const RESPONSE_INIT: InjectionToken<ResponseInit | null>;
@@ -1771,6 +1775,7 @@ export type StaticProvider = ValueProvider | ExistingProvider | StaticClassProvi
17711775

17721776
// @public
17731777
export interface StreamingResourceOptions<T, R> extends BaseResourceOptions<T, R> {
1778+
loader?: never;
17741779
stream: ResourceStreamingLoader<T, R>;
17751780
}
17761781

packages/core/rxjs-interop/src/rx_resource.ts

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export function rxResource<T, R>(opts: RxResourceOptions<T, R>): ResourceRef<T |
4747
opts?.injector || assertInInjectionContext(rxResource);
4848
return resource<T, R>({
4949
...opts,
50+
loader: undefined,
5051
stream: (params) => {
5152
let sub: Subscription;
5253

packages/core/src/resource/api.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ export type ResourceLoader<T, R> = (param: ResourceLoaderParams<R>) => PromiseLi
169169
*/
170170
export type ResourceStreamingLoader<T, R> = (
171171
param: ResourceLoaderParams<R>,
172-
) => PromiseLike<Signal<{value: T} | {error: unknown}>>;
172+
) => PromiseLike<Signal<ResourceStreamItem<T>>>;
173173

174174
/**
175175
* Options to the `resource` function, for creating a resource.
@@ -212,6 +212,11 @@ export interface PromiseResourceOptions<T, R> extends BaseResourceOptions<T, R>
212212
* Loading function which returns a `Promise` of the resource's value for a given request.
213213
*/
214214
loader: ResourceLoader<T, R>;
215+
216+
/**
217+
* Cannot specify `stream` and `loader` at the same time.
218+
*/
219+
stream?: never;
215220
}
216221

217222
/**
@@ -225,9 +230,19 @@ export interface StreamingResourceOptions<T, R> extends BaseResourceOptions<T, R
225230
* request, which can change over time as new values are received from a stream.
226231
*/
227232
stream: ResourceStreamingLoader<T, R>;
233+
234+
/**
235+
* Cannot specify `stream` and `loader` at the same time.
236+
*/
237+
loader?: never;
228238
}
229239

230240
/**
231241
* @experimental
232242
*/
233243
export type ResourceOptions<T, R> = PromiseResourceOptions<T, R> | StreamingResourceOptions<T, R>;
244+
245+
/**
246+
* @experimental
247+
*/
248+
export type ResourceStreamItem<T> = {value: T} | {error: unknown};

0 commit comments

Comments
 (0)