From 4c5fc7883f7701900546bd5912bcf7042d6e4708 Mon Sep 17 00:00:00 2001 From: Ashoat Tevosyan Date: Thu, 3 Oct 2024 02:55:33 -0400 Subject: [PATCH] [lib] Batch BaseAutoJoinCommunityHandler Summary: This diff updates `BaseAutoJoinCommunityHandler` to work in batches of 5, and to avoid triggering a `joinCommunity` for a single community more than once. Test Plan: Varun tested this in his local environment, where he has set up a reproduction of the production environment where we have a lot of communities tagged with Farcaster channels, and he tested signing in as a user with a lot of channels Reviewers: varun, will Reviewed By: varun Subscribers: tomek Differential Revision: https://phab.comm.dev/D13583 --- .../base-auto-join-community-handler.react.js | 175 +++++++++++------- 1 file changed, 111 insertions(+), 64 deletions(-) diff --git a/lib/components/base-auto-join-community-handler.react.js b/lib/components/base-auto-join-community-handler.react.js index 638e3811f4..da0a1a5d91 100644 --- a/lib/components/base-auto-join-community-handler.react.js +++ b/lib/components/base-auto-join-community-handler.react.js @@ -30,17 +30,26 @@ import { usingCommServicesAccessToken, createDefaultHTTPRequestHeaders, } from '../utils/services-utils.js'; +import sleep from '../utils/sleep.js'; + +type JoinStatus = 'inactive' | 'joining' | 'joined'; type CommunityToAutoJoin = { + +batch: number, +communityID: string, +keyserverOverride: ?KeyserverOverride, - +joinStatus: 'inactive' | 'joining' | 'joined', + +joinStatus: JoinStatus, }; -type CommunitiesToAutoJoin = { +type CommunityDatas = { +[communityID: string]: CommunityToAutoJoin, }; +type CommunitiesToAutoJoin = { + +curBatch: number, + +communityDatas: CommunityDatas, +}; + type Props = { +calendarQuery: () => CalendarQuery, }; @@ -66,7 +75,7 @@ function BaseAutoJoinCommunityHandler(props: Props): React.Node { state => state.keyserverStore.keyserverInfos, ); - const [communitiesToAutoJoin, setCommunitiesToAutoJoin] = + const [communitiesToAutoJoin, baseSetCommunitiesToAutoJoin] = React.useState(); const prevCanQueryRef = React.useRef(); @@ -106,7 +115,9 @@ function BaseAutoJoinCommunityHandler(props: Props): React.Node { channel => channel.id, ); - const promises: { [string]: Promise } = {}; + const promises: { + [string]: Promise>, + } = {}; for (const channelID of followedFarcasterChannelIDs) { promises[channelID] = (async () => { @@ -149,14 +160,19 @@ function BaseAutoJoinCommunityHandler(props: Props): React.Node { const filteredCommunitiesObj = _pickBy(Boolean)(communitiesObj); - const communitesToJoin: { ...CommunitiesToAutoJoin } = {}; + const communityDatas: { ...CommunityDatas } = {}; + let i = 0; for (const key in filteredCommunitiesObj) { - const communityID = filteredCommunitiesObj[key].communityID; - communitesToJoin[communityID] = filteredCommunitiesObj[key]; + const communityObject = filteredCommunitiesObj[key]; + const communityID = communityObject.communityID; + communityDatas[communityID] = { + ...communityObject, + batch: Math.floor(i++ / 5), + }; } - setCommunitiesToAutoJoin(communitesToJoin); + baseSetCommunitiesToAutoJoin({ communityDatas, curBatch: 0 }); })(); }, [ threadInfos, @@ -169,18 +185,66 @@ function BaseAutoJoinCommunityHandler(props: Props): React.Node { canQuery, ]); + const potentiallyIncrementBatch: ( + ?CommunitiesToAutoJoin, + ) => ?CommunitiesToAutoJoin = React.useCallback(input => { + if (!input) { + return input; + } + + let shouldIncrementBatch = false; + const { curBatch, communityDatas } = input; + for (const communityToAutoJoin of Object.values(communityDatas)) { + const { batch, joinStatus } = communityToAutoJoin; + + if (batch !== curBatch) { + continue; + } + + if (joinStatus !== 'joined') { + // One of the current batch isn't complete yet + return input; + } + + // We have at least one complete in the current batch + shouldIncrementBatch = true; + } + + // If we get here, all of the current batch is complete + if (shouldIncrementBatch) { + return { communityDatas, curBatch: curBatch + 1 }; + } + + return input; + }, []); + + const setCommunitiesToAutoJoin: SetState = + React.useCallback( + next => { + if (typeof next !== 'function') { + baseSetCommunitiesToAutoJoin(potentiallyIncrementBatch(next)); + return; + } + baseSetCommunitiesToAutoJoin(prev => { + const result = next(prev); + return potentiallyIncrementBatch(result); + }); + }, + [potentiallyIncrementBatch], + ); + const joinHandlers = React.useMemo(() => { if (!communitiesToAutoJoin) { return null; } - return Object.keys(communitiesToAutoJoin).map(id => { - const communityToAutoJoin = communitiesToAutoJoin[id]; + const { curBatch, communityDatas } = communitiesToAutoJoin; - const { communityID, keyserverOverride, joinStatus } = - communityToAutoJoin; + return Object.values(communityDatas).map(communityData => { + const { batch, communityID, keyserverOverride, joinStatus } = + communityData; - if (joinStatus === 'joined') { + if (batch !== curBatch || joinStatus === 'joined') { return null; } @@ -190,12 +254,12 @@ function BaseAutoJoinCommunityHandler(props: Props): React.Node { communityID={communityID} keyserverOverride={keyserverOverride} calendarQuery={calendarQuery} - communitiesToAutoJoin={communitiesToAutoJoin} + joinStatus={joinStatus} setCommunitiesToAutoJoin={setCommunitiesToAutoJoin} /> ); }); - }, [calendarQuery, communitiesToAutoJoin]); + }, [calendarQuery, communitiesToAutoJoin, setCommunitiesToAutoJoin]); return joinHandlers; } @@ -204,7 +268,7 @@ type JoinHandlerProps = { +communityID: string, +keyserverOverride: ?KeyserverOverride, +calendarQuery: () => CalendarQuery, - +communitiesToAutoJoin: CommunitiesToAutoJoin, + +joinStatus: JoinStatus, +setCommunitiesToAutoJoin: SetState, }; @@ -213,7 +277,7 @@ function JoinHandler(props: JoinHandlerProps) { communityID, keyserverOverride, calendarQuery, - communitiesToAutoJoin, + joinStatus, setCommunitiesToAutoJoin, } = props; @@ -233,59 +297,42 @@ function JoinHandler(props: JoinHandlerProps) { defaultSubscription: defaultThreadSubscription, }); - React.useEffect(() => { - const joinStatus = communitiesToAutoJoin[communityID]?.joinStatus; - if (joinStatus !== 'inactive') { - return; - } - - void joinCommunity(); - }, [ - communitiesToAutoJoin, - communityID, - joinCommunity, - setCommunitiesToAutoJoin, - ]); - - React.useEffect(() => { - if (step !== 'add_keyserver') { - return; - } - - setCommunitiesToAutoJoin(prev => { - if (!prev) { - return null; - } + const setJoinStatus = React.useCallback( + (newJoinStatus: JoinStatus) => { + setCommunitiesToAutoJoin(prev => { + if (!prev) { + return null; + } - return { - ...prev, - [communityID]: { - ...prev[communityID], - joinStatus: 'joining', - }, - }; - }); - }, [communityID, setCommunitiesToAutoJoin, step]); + return { + ...prev, + communityDatas: { + ...prev.communityDatas, + [communityID]: { + ...prev.communityDatas[communityID], + joinStatus: newJoinStatus, + }, + }, + }; + }); + }, + [communityID, setCommunitiesToAutoJoin], + ); React.useEffect(() => { - if (step !== 'finished') { + if (joinStatus !== 'inactive') { return; } - - setCommunitiesToAutoJoin(prev => { - if (!prev) { - return null; + void (async () => { + try { + setJoinStatus('joining'); + await sleep(1000); + await joinCommunity(); + } finally { + setJoinStatus('joined'); } - - return { - ...prev, - [communityID]: { - ...prev[communityID], - joinStatus: 'joined', - }, - }; - }); - }, [communityID, step, setCommunitiesToAutoJoin]); + })(); + }, [joinStatus, communityID, setJoinStatus, joinCommunity]); return null; }