Reduce polling (#2465)

* Move profile and preference polling to polls-on-foreground

* Refetch prefs on feeds screen refresh since polling no longer occurs

* Reduce notifications polling by 50% if there's already an unread

* Disable feed polling if we know we have content

* Disable the hard refresh after 1 hour in case it's the cause of the random feed refresh bug

* Fix types
zio/stable
Paul Frazee 2024-01-10 22:27:14 -08:00 committed by GitHub
parent 0442dcc1a0
commit 7ab4be6f7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 79 additions and 14 deletions

View File

@ -1,11 +1,39 @@
import {QueryClient} from '@tanstack/react-query'
import {AppState, AppStateStatus} from 'react-native'
import {QueryClient, focusManager} from '@tanstack/react-query'
import {isNative} from '#/platform/detection'
focusManager.setEventListener(onFocus => {
if (isNative) {
const subscription = AppState.addEventListener(
'change',
(status: AppStateStatus) => {
focusManager.setFocused(status === 'active')
},
)
return () => subscription.remove()
} else if (typeof window !== 'undefined' && window.addEventListener) {
// these handlers are a bit redundant but focus catches when the browser window
// is blurred/focused while visibilitychange seems to only handle when the
// window minimizes (both of them catch tab changes)
// there's no harm to redundant fires because refetchOnWindowFocus is only
// used with queries that employ stale data times
const handler = () => onFocus()
window.addEventListener('focus', handler, false)
window.addEventListener('visibilitychange', handler, false)
return () => {
window.removeEventListener('visibilitychange', handler)
window.removeEventListener('focus', handler)
}
}
})
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
// NOTE
// refetchOnWindowFocus breaks some UIs (like feeds)
// so we NEVER want to enable this
// so we only selectively want to enable this
// -prf
refetchOnWindowFocus: false,
// Structural sharing between responses makes it impossible to rely on

View File

@ -35,4 +35,5 @@ export interface CachedFeedPage {
usableInFeed: boolean
syncedAt: Date
data: FeedPage | undefined
unreadCount: number
}

View File

@ -25,7 +25,10 @@ type StateContext = string
interface ApiContext {
markAllRead: () => Promise<void>
checkUnread: (opts?: {invalidate?: boolean}) => Promise<void>
checkUnread: (opts?: {
invalidate?: boolean
isPoll?: boolean
}) => Promise<void>
getCachedUnreadPage: () => FeedPage | undefined
}
@ -50,6 +53,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
usableInFeed: false,
syncedAt: new Date(),
data: undefined,
unreadCount: 0,
})
// periodic sync
@ -58,7 +62,10 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
return
}
checkUnreadRef.current() // fire on init
const interval = setInterval(checkUnreadRef.current, UPDATE_INTERVAL)
const interval = setInterval(
() => checkUnreadRef.current?.({isPoll: true}),
UPDATE_INTERVAL,
)
return () => clearInterval(interval)
}, [hasSession])
@ -69,6 +76,12 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
usableInFeed: false,
syncedAt: new Date(),
data: undefined,
unreadCount:
data.event === '30+'
? 30
: data.event === ''
? 0
: parseInt(data.event, 10) || 1,
}
setNumUnread(data.event)
}
@ -95,13 +108,24 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
}
},
async checkUnread({invalidate}: {invalidate?: boolean} = {}) {
async checkUnread({
invalidate,
isPoll,
}: {invalidate?: boolean; isPoll?: boolean} = {}) {
try {
if (!getAgent().session) return
if (AppState.currentState !== 'active') {
return
}
// reduce polling if unread count is set
if (isPoll && cacheRef.current?.unreadCount !== 0) {
// if hit 30+ then don't poll, otherwise reduce polling by 50%
if (cacheRef.current?.unreadCount >= 30 || Math.random() >= 0.5) {
return
}
}
// count
const page = await fetchPage({
cursor: undefined,
@ -133,6 +157,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
usableInFeed: !!invalidate, // will be used immediately
data: page,
syncedAt: !lastIndexed || now > lastIndexed ? now : lastIndexed,
unreadCount,
}
// update & broadcast

View File

@ -31,7 +31,7 @@ export function usePreferencesQuery() {
return useQuery({
staleTime: STALE.SECONDS.FIFTEEN,
structuralSharing: true,
refetchInterval: STALE.SECONDS.FIFTEEN,
refetchOnWindowFocus: true,
queryKey: preferencesQueryKey,
queryFn: async () => {
const agent = getAgent()

View File

@ -35,7 +35,7 @@ export function useProfileQuery({did}: {did: string | undefined}) {
// if you remove it, the UI infinite-loops
// -prf
staleTime: isCurrentAccount ? STALE.SECONDS.THIRTY : STALE.MINUTES.FIVE,
refetchInterval: STALE.MINUTES.FIVE,
refetchOnWindowFocus: true,
queryKey: RQKEY(did || ''),
queryFn: async () => {
const res = await getAgent().getProfile({actor: did || ''})

View File

@ -174,6 +174,7 @@ export function FeedPage({
feed={feed}
feedParams={feedParams}
pollInterval={POLL_FREQ}
disablePoll={hasNew}
scrollElRef={scrollElRef}
onScrolledDownChange={setIsScrolledDown}
onHasNew={setHasNew}

View File

@ -36,7 +36,8 @@ const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
const ERROR_ITEM = {_reactKey: '__error__'}
const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'}
const REFRESH_AFTER = STALE.HOURS.ONE
// DISABLED need to check if this is causing random feed refreshes -prf
// const REFRESH_AFTER = STALE.HOURS.ONE
const CHECK_LATEST_AFTER = STALE.SECONDS.THIRTY
let Feed = ({
@ -46,6 +47,7 @@ let Feed = ({
style,
enabled,
pollInterval,
disablePoll,
scrollElRef,
onScrolledDownChange,
onHasNew,
@ -63,6 +65,7 @@ let Feed = ({
style?: StyleProp<ViewStyle>
enabled?: boolean
pollInterval?: number
disablePoll?: boolean
scrollElRef?: ListRef
onHasNew?: (v: boolean) => void
onScrolledDownChange?: (isScrolledDown: boolean) => void
@ -107,7 +110,7 @@ let Feed = ({
)
const checkForNew = React.useCallback(async () => {
if (!data?.pages[0] || isFetching || !onHasNew || !enabled) {
if (!data?.pages[0] || isFetching || !onHasNew || !enabled || disablePoll) {
return
}
try {
@ -117,7 +120,7 @@ let Feed = ({
} catch (e) {
logger.error('Poll latest failed', {feed, error: String(e)})
}
}, [feed, data, isFetching, onHasNew, enabled])
}, [feed, data, isFetching, onHasNew, enabled, disablePoll])
const myDid = currentAccount?.did || ''
const onPostCreated = React.useCallback(() => {
@ -146,11 +149,12 @@ let Feed = ({
React.useEffect(() => {
if (enabled) {
const timeSinceFirstLoad = Date.now() - lastFetchRef.current
if (timeSinceFirstLoad > REFRESH_AFTER) {
// DISABLED need to check if this is causing random feed refreshes -prf
/*if (timeSinceFirstLoad > REFRESH_AFTER) {
// do a full refresh
scrollElRef?.current?.scrollToOffset({offset: 0, animated: false})
queryClient.resetQueries({queryKey: RQKEY(feed)})
} else if (
} else*/ if (
timeSinceFirstLoad > CHECK_LATEST_AFTER &&
checkForNewRef.current
) {

View File

@ -97,6 +97,7 @@ export function FeedsScreen(_props: Props) {
data: preferences,
isLoading: isPreferencesLoading,
error: preferencesError,
refetch: refetchPreferences,
} = usePreferencesQuery()
const {
data: popularFeeds,
@ -151,9 +152,12 @@ export function FeedsScreen(_props: Props) {
}, [query, debouncedSearch])
const onPullToRefresh = React.useCallback(async () => {
setIsPTR(true)
await refetchPopularFeeds()
await Promise.all([
refetchPreferences().catch(_e => undefined),
refetchPopularFeeds().catch(_e => undefined),
])
setIsPTR(false)
}, [setIsPTR, refetchPopularFeeds])
}, [setIsPTR, refetchPreferences, refetchPopularFeeds])
const onEndReached = React.useCallback(() => {
if (
isPopularFeedsFetching ||

View File

@ -490,6 +490,7 @@ const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
enabled={isFocused}
feed={feed}
pollInterval={60e3}
disablePoll={hasNew}
scrollElRef={scrollElRef}
onHasNew={setHasNew}
onScrolledDownChange={setIsScrolledDown}

View File

@ -646,6 +646,7 @@ const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
enabled={isFocused}
feed={feed}
pollInterval={60e3}
disablePoll={hasNew}
scrollElRef={scrollElRef}
onHasNew={setHasNew}
onScrolledDownChange={setIsScrolledDown}