Skip to content

Memory leak createCachedFormatter  #2227

Closed

Description

@tomasPavlacky

🐛 Bug Report

Hello, I noticed Increasing memory.

To Reproduce

import i18next from 'i18next';

i18next.init({
  lng: 'cs', 
  debug: true,
  resources: {
    cs: {
      translation: {
        "intlDateTime": "{{- val, datetime}}",
      }
    }
  }
});

setInterval(() => {
    for (let i= 0; i< 100_000; i++){
      i18next.t('intlDateTime', {val: new Date(), formatParams: { val: { 
year: 'numeric', 
month: 'long', 
day: 'numeric', 
hour: 'numeric', 
minute: 'numeric', 
second: 'numeric' ,
milisecond: 'numeric'
// ,faster: Math.random() // for faster simulation uncomment 
}}})
    }
}, 1);

// Mock with logging
function createCachedFormatter(fn) {
  var cache = {};
  return function invokeFormatter(val, lng, options) {
   console.log(`count of objects: ${Object.keys(cache).length}`, process.memoryUsage().heapUsed / 1024 / 1024);
    
    var key = lng + JSON.stringify(options);
    var formatter = cache[key];
    if (!formatter) {
      formatter = fn(lng, options);
      cache[key] = formatter;
    }
    return formatter(val);
  };
}

Your Environment

  • runtime version: i.e. node 20.12.1
  • i18next version: i.e. 22.5.1
  • os: Mac

Activity

  1. pepakriz commented on Aug 15, 2024

    @pepakriz

    I see the problem is that the cache key is generated from the entire options object, including the date itself. However, I'm unsure how to fix this because I don't know how to distinguish between a placeholder value and an option parameter.

  2. adrai commented on Aug 15, 2024

    @adrai
    Member

    //cc @pwolaq

  3. pepakriz commented on Aug 15, 2024

    @pepakriz

    I'd send a fix (remove cache), but there is already a public API around this feature (addCached), so I don't know what to do.

    Because this mem leak is currently very critical for our use-case, we've introduced this workaround in our app:

    const i18n = ...;
    if (i18n.services.formatter !== undefined) {
        i18n.services.formatter.add('number', (val, lng, opt) => new Intl.NumberFormat(lng, { ...opt }).format(val));
        i18n.services.formatter.add('currency', (val, lng, opt) => new Intl.NumberFormat(lng, {
            ...opt,
            style: 'currency',
        }).format(val));
        i18n.services.formatter.add('datetime', (val, lng, opt) => new Intl.DateTimeFormat(lng, { ...opt }).format(val));
        i18n.services.formatter.add('relativetime', (val, lng, opt) => new Intl.RelativeTimeFormat(lng, { ...opt }).format(val, opt.range || 'day'));
        i18n.services.formatter.add('list', (val, lng, opt) => new Intl.ListFormat(lng, { ...opt }).format(val));
    }
  4. added a commit that references this issue on Aug 15, 2024
  5. adrai commented on Aug 15, 2024

    @adrai
    Member

    please try with v23.12.4 I tried to address that, but this will only work if you don't pass some extra dynamic variables, like faster: Math.random()

  6. pepakriz commented on Aug 15, 2024

    @pepakriz

    @adrai I'm just leaving the computer. But your solution looks fine. I had the same idea in my head, but I was too lazy for searching correct field names :)

    I'll send a confirmation tomorrow.
    Thank you!

  7. pepakriz commented on Aug 15, 2024

    @pepakriz

    If I understand your fix correctly, it doesn't fix situations with more than one placeholder.

  8. added a commit that references this issue on Aug 15, 2024
  9. adrai commented on Aug 15, 2024

    @adrai
    Member

    yes, but v23.12.6 should be even better ;-)

  10. adrai commented on Aug 15, 2024

    @adrai
    Member

    ok, current version 23.12.7 will only address this use case for simple t calls interpolating only 1 of these type of placeholders.... if that does still not work, then you need to keep the workaround until there's a better solution to this.

  11. adrai commented on Apr 12, 2025

    @adrai
    Member

    stale... so closing for now...

  12. OsvaldasRubavicius commented on May 19, 2025

    @OsvaldasRubavicius

    @adrai Ran into this issue as well which cost me some time to troubleshoot. In the end I've used a workaround suggested by @pepakriz .

    Solution contains multiple strings that have more than 1 interpolation in them using formatters so even upgrading to the latest library version did not help. Would be great to see this fixed properly in the library so no workaround would be needed. If it's difficult to implement a fix maybe a flag could be added to config to disable the caching? Our solution doesn't really need that.

  13. adrai commented on May 19, 2025

    @adrai
    Member

    @OsvaldasRubavicius There is no good "fix" for it, feel free to suggest a PR.
    @pwolaq We may need to introduce a new init option that disables the caching of the in-built formats...

  14. OsvaldasRubavicius commented on May 19, 2025

    @OsvaldasRubavicius

    @adrai thanks for a quick response. Having init option would work for us.

  15. adrai commented on May 19, 2025

    @adrai
    Member
    Image
    i18next.init({
      cacheInBuiltFormats: false
    })

    v25.2.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions