v 友们帮忙看看这段我几年前写的 定时刷新 token 的 js 代码,为什么会内存泄露? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
rookie2luochao
V2EX    程序员

v 友们帮忙看看这段我几年前写的 定时刷新 token 的 js 代码,为什么会内存泄露?

  •  1
     
  •   rookie2luochao
    rookie-luochao 2024-05-14 10:43:24 +08:00 3701 次点击
    这是一个创建于 518 天前的主题,其中的信息可能已经有所发展或是发生改变。

    背景是这样,这段代码就是 token 过期前 2 分钟刷新下 token ,但是它会在我切换到其他网页之后(这个刷新 token 的网页不关闭,后台运行),过很长一段时间在切回来就可能爆内存(可能 12 小时,可能 24 小时?),但是时间太短不会爆内存,所以我一直没找到很好的办法去测试定位

    timer 是 rxjs 的定时器操作符

    export function RefreshTokenComp({ refreshTokenActor, }: { refreshTokenActor: RequestActor<{ refreshToken: string; }>; }) { const [access$, updateAccess] = useAccessMgr(); const access = useObservable(access$) || {}; const { refresh_token, expireAt } = access; const [refreshTokenRequest] = useRequest(refreshTokenActor, { onSuccess({ arg }) { updateAccess(fromOAuthToken(arg.data)); }, }); useEffect(() => { if (!refresh_token) return; const expiresIn = moment(expireAt).diff(moment(), "s") - 120; // token 过期前二分钟左右刷新 const sub = timer(expiresIn * 1000).subscribe(() => { refreshTokenRequest({ refreshToken: refresh_token }); }); return () => { sub.unsubscribe(); }; }, [refresh_token]); return null; } 
    34 条回复    2024-05-15 09:43:49 +08:00
    wpyfawkes
        1
    wpyfawkes  
       2024-05-14 10:57:19 +08:00
    我不是太懂 Javascript,以下是 Claude 的回复

    函数实现
    函数实现看起来是正确的。它使用了 useEffect 来处理 token 的刷新,这是 React 中常见的做法。然而,这段代码可能存在潜在的内存泄漏问题。在 useEffect 的清理函数中,我们取消了定时器的订阅,但是如果 refresh_token 在定时器触发之前改变,那么定时器可能会被意外地触发两次。我们应该在 useEffect 的依赖数组中添加 expiresIn 。
    Laobai
        2
    Laobai  
       2024-05-14 10:59:51 +08:00
    大概率是定时器创建了,没有被销毁
    rookie2luochao
        3
    rookie2luochao  
    OP
       2024-05-14 11:02:35 +08:00
    @Laobai 肯定,我就是不知道怎么样导致它没有销毁,我以为写了 sub.unsubscribe() 就算销毁了
    rookie2luochao
        4
    rookie2luochao  
    OP
       2024-05-14 11:05:55 +08:00
    @wpyfawkes 哈哈哈,这个是收费的吗,我去试试按这个建议改进一下呢
    Brain777
        5
    Brain777  
       2024-05-14 11:06:59 +08:00
    const sub = timer(expiresIn * 1000).subscribe(() => {
    refreshTokenRequest({ refreshToken: refresh_token });
    // 感觉应该需要在刷新之后清理定时器订阅
    sub.unsubscribe();
    });
    rookie2luochao
        6
    rookie2luochao  
    OP
       2024-05-14 11:13:42 +08:00
    @wpyfawkes 应该是 expireAt ,这个假 AI
    rookie2luochao
        7
    rookie2luochao  
    OP
       2024-05-14 11:16:19 +08:00
    @Brain777 从代码上面看我觉得可以不用,但是像你说的刷新之后多清除一下,能避免一些想不到的情况
    lisxour
        8
    lisxour  
       2024-05-14 11:41:34 +08:00
    可以直接用浏览器自带的分析工具,对比两次快照,看下有什么东西是一直加的。

    rabbbit
        9
    rabbbit  
       2024-05-14 12:18:45 +08:00
    没看出啥问题,timer 换成 setTimeout 试试还会有问题吗?
    renmu
        10
    renmu  
       2024-05-14 13:06:45 +08:00 via Android
    我猜是切换到其他标签页后,页面的定时器没有再执行,当你切回来后,页面没有执行的定时器一起被执行了,然后爆了
    ZENGQH
        11
    ZENGQH  
       2024-05-14 14:06:01 +08:00
    切换标签后 导致的计时器异常,有个属性可以判断是否为当前便签页
    vanchKong
        12
    vanchKong  
       2024-05-14 14:41:23 +08:00
    以我浅薄的 rn 开发经历,我是这么写的:
    useEffect(() => {
    let timer = setInterval(() => {
    ...
    }, 2000)

    return () => {
    clearInterval(timer)
    }
    }, [])
    不知道是不是我没用懂 react
    yuuko
        13
    yuuko  
       2024-05-14 15:31:10 +08:00
    没看出啥问题,找到问题后能踢我一脚吗
    XV5V7stzN1ns0TPL
        14
    XV5V7stzN1ns0TPL  
       2024-05-14 15:39:28 +08:00
    在 Javascript 应用程序中,内存泄漏可能由多种原因引起,特别是当使用异步操作和事件监听器时。在你提供的代码片段中,有几个可能导致内存泄漏的潜在因素:

    1. **定时器订阅未正确清理**:代码中使用了 RxJS 的 `timer` 创建了一个定时器,并订阅了它。在组件卸载时,通过调用 `sub.unsubscribe()` 来取消订阅,这是正确的做法。然而,如果组件卸载不完全或者 `unsubscribe` 调用时机不当,订阅可能不会被正确清理。

    2. **闭包陷阱**:如果 `useEffect` 的回调函数或其返回的清理函数形成了闭包,它们可能会捕获并保留组件的状态,即使组件不再渲染。

    3. **组件状态未清除**:如果组件状态(如 `access`)在组件卸载后仍然被某些引用或事件监听器保留,这可能会导致内存泄漏。

    4. **全局事件监听器**:如果代码中有全局事件监听器,并且这些监听器引用了组件的状态或回调,它们可能会阻止组件被垃圾回收。

    5. **异步更新队列**:Javascript 的异步更新队列可能在组件卸载后仍然尝试更新组件状态,导致内存泄漏。

    要解决这个问题,你可以尝试以下方法:

    - **确保组件卸载**:使用 React DevTools 或类似工具检查组件是否真的被卸载了。

    - **使用条件渲染**:只在需要时渲染组件,避免不必要的挂载和卸载。

    - **优化状态管理**:避免在组件状态中存储大量数据或长生命周期的对象。

    - **使用 WeakMap 或 WeakRef**:如果需要存储对组件的引用,使用 `WeakMap` 或 `WeakRef` 可以减少内存泄漏的风险。

    - **避免长生命周期的闭包**:确保闭包不会捕获不必要的组件状态。

    - **手动管理订阅**:如果可能,手动管理 RxJS 订阅的生命周期,确保在组件卸载前取消所有订阅。

    - **测试和调试**:使用内存泄漏检测工具,如 Chrome DevTools 的 Heap Snapshot 比较,来识别和定位内存泄漏。

    - **代码审查**:对相关代码进行彻底的审查,查找可能导致内存泄漏的逻辑错误。

    最后,为了更好地测试和定位内存泄漏,你可以尝试以下方法:

    - **自动化测试**:编写自动化测试来模拟长时间运行的场景。

    - **性能监控**:在开发环境中使用内存分析工具,如 React DevTools 或其他 Javascript 性能监控工具。

    - **模拟长时间运行**:在测试环境中模拟长时间运行的场景,比如使用循环或定时器来频繁触发组件的挂载和卸载。

    - **逐步调试**:逐步执行代码,检查在特定操作后内存的使用情况。

    通过这些方法,你应该能够更准确地定位和解决内存泄漏问题。
    forbreak
        15
    forbreak  
       2024-05-14 16:11:03 +08:00
    大概就是切换 tab 之后,浏览器休眠了。切回来之后积攒的那些一起执行就爆掉了。 解决方案: 监控下切换标签的事件,切走的时候就别订阅了。切回来的处理一次 token 刷新。
    blankmiss
        16
    blankmiss  
       2024-05-14 16:43:32 +08:00
    @wpyfawkes @dulice 不要在 V2EX 发布 AI 生成的内容 站规 有说明
    iosyyy
        17
    iosyyy  
       2024-05-14 16:47:36 +08:00
    @dulice 不要直接引用 ai 内容
    superfatboy
        18
    superfatboy  
       2024-05-14 16:53:02 +08:00
    #5 感觉不用,正常情况下,refresh_token 更新,sub.unsubscribe()肯定会 执行一次
    googleaccount
        19
    googleaccount  
       2024-05-14 16:56:11 +08:00
    ```js
    export function RefreshTokenComp({
    refreshTokenActor,
    }: {
    refreshTokenActor: RequestActor<{
    refreshToken: string;
    }>;
    }) {
    const [access$, updateAccess] = useAccessMgr();
    const access = useObservable(access$) || {};
    const { refresh_token, expireAt } = access;
    let sub:Timer
    const [refreshTokenRequest] = useRequest(refreshTokenActor, {
    onSuccess({ arg }) {
    updateAccess(fromOAuthToken(arg.data));
    },
    });

    useEffect(() => {
    if (!refresh_token) return;
    if (sub) sub.unsubscribe();
    const expiresIn = moment(expireAt).diff(moment(), "s") - 120; // token 过期前二分钟左右刷新
    sub = timer(expiresIn * 1000).subscribe(() => {
    refreshTokenRequest({ refreshToken: refresh_token });
    });
    return () => {
    sub.unsubscribe();
    };
    }, [refresh_token]);

    return null;
    }
    ``` 试试这样
    你这个 refresh_token 每隔两分钟就会变 说明 useEffect 里面的定时器每隔两分钟就会执行一次 执行多了不就爆了没,每次执行前清空一下就好了
    googleaccount
        20
    googleaccount  
       2024-05-14 17:20:09 +08:00
    我没测试上面这些代码,还一个简单粗暴的办法就是把定时器存在 ref 上 就不会出现这些问题了。
    ```js
    const subscriptiOnRef= useRef(null);

    useEffeect(() => {
    ...
    if (subscriptionRef.current) {
    subscriptionRef.current.unsubscribe();
    }
    ...
    subscriptionRef.current = timer()
    ...
    }, [refresh_token]);
    return () => {
    subscriptionRef.current.unsubscribe();
    };
    })
    ```
    googleaccount
        21
    googleaccount  
       2024-05-14 17:22:36 +08:00
    @googleaccount 咋格式呢
    ```Javascript
    const subscriptiOnRef= useRef(null);

    useEffeect(() => {
    ...
    if (subscriptionRef.current) {
    subscriptionRef.current.unsubscribe();
    }
    ...
    subscriptionRef.current = timer()
    ...
    return () => {
    subscriptionRef.current.unsubscribe();
    };
    }, [refresh_token]);
    ```
    leokun
        22
    leokun  
       2024-05-14 18:57:22 +08:00
    这段代码不会出现内存泄漏
    大家不妨设想一下 refresh_token, expireAt 每秒都在改变,这里也不会内存泄漏,内存泄漏应该在别处
    IvanLi127
        23
    IvanLi127  
       2024-05-14 19:03:44 +08:00
    改成每秒刷一次看看内存会不会快速上涨吧,单看这代码没看出太大问题
    rookie2luochao
        24
    rookie2luochao  
    OP
       2024-05-14 22:43:06 +08:00
    @yuuko 待我好好定位一下,搞清楚了踢你
    rookie2luochao
        25
    rookie2luochao  
    OP
       2024-05-14 22:43:45 +08:00
    @vanchKong 我们两这写法思路一样,就是我比你多了 deps 依赖
    rookie2luochao
        26
    rookie2luochao  
    OP
       2024-05-14 22:44:39 +08:00
    @rabbbit 问题应该不在 timer 上,应该是 v 友说的还是在 timer 没清除上面找原因
    rookie2luochao
        27
    rookie2luochao  
    OP
       2024-05-14 22:45:27 +08:00
    @lisxour 好的,你的截图看不到,我觉得也是需要学习一下分析工具了
    rookie2luochao
        28
    rookie2luochao  
    OP
       2024-05-14 22:46:26 +08:00
    @renmu 你说的这个原理我也想过,这算正常机制呢,还是说需要怎么去避免
    rookie2luochao
        29
    rookie2luochao  
    OP
       2024-05-14 22:47:33 +08:00
    @ZENGQH 所以我需要通过是否离开标签页做清除吗?会不会麻烦了
    rookie2luochao
        30
    rookie2luochao  
    OP
       2024-05-14 22:49:01 +08:00
    @googleaccount 换到 ref 上面,确实可能更靠谱
    rookie2luochao
        31
    rookie2luochao  
    OP
       2024-05-14 22:52:08 +08:00
    @leokun 内存泄漏大概率就是这里,我也是直观上看不出来代码有问题,主要还是应该对 react effect 的执行机制没有理解透彻,同事也没看出来,只能说把 deps 写全,我只写一个 token 依赖是因为 token 和 expireAt 是同步变的,所以我就只写了 token 这个依赖,然后用 ref 引用,然后多清除几次 unsubscribe ,但是这只是尝试的解决办法
    vanchKong
        32
    vanchKong  
       2024-05-15 09:20:01 +08:00
    @rookie2luochao 另外,我想说,可以试一下将 timer 用 useState 去定义,我开发 rn 的时候,碰到一种情况,就是 timer 不是用 useState 定义的,然后直接 clearInterval ,但是 timer 依然在执行的情况。
    rookie2luochao
        33
    rookie2luochao  
    OP
       2024-05-15 09:24:51 +08:00
    @vanchKong 嗯嗯,谢谢哈,像 V 友说的用 useRef 更靠谱一点,自己还是需要找到能定位这种内存泄漏的方法,虽然目前我的产品定位内存泄漏时间线,现在有解决办法了,我看不能找到一个简单方法去定位
    realJamespond
        34
    realJamespond  
       2024-05-15 09:43:49 +08:00
    rxjs 初始化在 effect 中不应该加依赖项,依赖项应该用 ref 保存在 rxjs 中引用
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5495 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 29ms UTC 07:17 PVG 15:17 LAX 00:17 JFK 03:17
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86