TypeScript: TSC Regression causing crash (Heap OOM) between version 4.8.4 & 4.9.3 in large project

Bug Report

When updating typescript within our project from version 4.7.3 to a more recent version, our project began experiencing OOM issues. Specifically, it is completely unable to build via tsc or even tsc --noEmit. Ours is a somewhat large project, so I couldn’t point to a minimal reproduction (sorry!). I have 16GB of RAM so I doubt its a “classic” memory error, seems more likely something started infinitely using memory during a version upgrade.

<--- Last few GCs --->

[7915:0x5f6b230]    32781 ms: Mark-sweep (reduce) 2041.6 (2082.5) -> 2040.8 (2083.0) MB, 492.6 / 0.0 ms  (average mu = 0.088, current mu = 0.038) allocation failure; scavenge might not succeed
[7915:0x5f6b230]    33342 ms: Mark-sweep (reduce) 2041.9 (2083.0) -> 2041.1 (2083.2) MB, 556.2 / 0.0 ms  (average mu = 0.047, current mu = 0.009) allocation failure; scavenge might not succeed


<--- JS stacktrace --->

FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
 1: 0xb6d470 node::Abort() [node]
 2: 0xa7e0a8  [node]
 3: 0xd46ee0 v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [node]
 4: 0xd47287 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [node]
 5: 0xf24645  [node]
 6: 0xf25548 v8::internal::Heap::RecomputeLimits(v8::internal::GarbageCollector) [node]
 7: 0xf35a53  [node]
 8: 0xf368c8 v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [node]
 9: 0xf1122e v8::internal::HeapAllocator::AllocateRawWithLightRetrySlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [node]
10: 0xf125f7 v8::internal::HeapAllocator::AllocateRawWithRetryOrFailSlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [node]
11: 0xef37ca v8::internal::Factory::NewFillerObject(int, v8::internal::AllocationAlignment, v8::internal::AllocationType, v8::internal::AllocationOrigin) [node]
12: 0x12b6d6f v8::internal::Runtime_AllocateInYoungGeneration(int, unsigned long*, v8::internal::Isolate*) [node]
13: 0x16e89b9  [node]
[1]    7915 abort      tsc --noEmit

🔎 Search Terms

tsc 4.9 4.9.5 Out of Memory Heap Out of Memory Crash Killed Silent Error (Am i doing this right?)

🕗 Version & Regression Information

Error began occuring when updating from typescript 4.7.3 to any 4.9.3^ version. Issue persists into Version 5.1.0-dev.20230303 (nightly at time of testing). Issue is NOT present in v4.8.4, or any prior version we tried (that satisfied our dependencies, anyways.)

  • This is a crash
  • This changed between versions 4.8.4 and 4.9.3

💻 Code

Branch state is: https://github.com/Eranziel/foundryvtt-lancer/tree/oom

Was unable to produce a minimally viable reproduction, as the OOM error gives next to no context as to where the issue occurs or why. Sorry. More than willing to provide more investigative information, but I don’t really know what there is to provide.

🙁 Actual behavior

It crashes on more recent versions for no clear reason.

🙂 Expected behavior

I’d rather it didn’t.

About this issue

  • Original URL
  • State: open
  • Created a year ago
  • Reactions: 4
  • Comments: 49 (14 by maintainers)

Most upvoted comments

I was able to solve the OOM issue for myself with

NODE_OPTIONS=--max-old-space-size=8192 tsc

facing the same problem when upgrading to TS 5+

our tsc build slowly grows its memory usage until it eventually crashes with @whitespine 's error

Have same issue with 5.0.4 along with 5.1.0-beta also using i18next along with react-i18next

Hi, I didn’t manage to create a repo reproducing the error but I have found a fix for the issue in i18next, I will report back there with a PR in 2-3 weeks if everything has worked as expected in the meantime, and link it here so that you can analyse wether it’s something ts could get better at. Basically the issue has to do with values and inferences within conditional types being computed when they shouldn’t (moving a ternary up the type stack and doing some type caching with const fixed the issue (not sure what I say strictly makes sense as I’m a noob in typing jargon but you get the idea)).

https://github.com/i18next/i18next/issues/2138#issuecomment-2036850539

In the meantime here is what seems to do the trick, in case someone wants to have a look https://github.com/i18next/i18next/compare/master...GreenGoTech:i18next:master

We have the same issue as original and it is also with i18next, it’s been blocking us from upgrading the library for almost a year 😕

Typescript compiler 4.8.4 works, I downgraded to build my project

Still getting this issue. I’m getting the following error:

FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory

when running tsc --pretty --noEmit even if I set the space size like the following:

cross-env NODE_OPTIONS=--max-old-space-size=10240 tsc --pretty --noEmit

We cannot possibly keep track of things if all OOM errors are being dumped into one thread. It’s much easier for us to track and ensure all of these problems are fixed when they are split into multiple issues with supporting reproductions. We’ll dedupe them if they are the same.

Also rolling back the i18next package to v21.10.0 and react-i18next to v11.16.9 worked for me.

I have a workaround working for ts@4.9.5. I forked off of @JarekToro’s repo and implemented it:

https://github.com/wrdecurtins/TSC-4.9.5-workaround

This comment on an i18next issue suggested removing resources from CustomTypeOptions to improve performance during tsc compilation, which seems to work well for ts@4.9.5:

https://github.com/i18next/react-i18next/issues/1600#issuecomment-1374277198

You can also redeclare the TFunction interface to make it less complicated.

declare module 'i18next' {
  interface TFunction {
    (key: string, options?: {[key: string]: string}): string
  }
}

These changes reduce the type safety of the i18next libraries, so I wouldn’t recommend using this as anything other than a temporary workaround to get to ts@4.9.5.

Unfortunately this doesn’t seem to do anything to fix the issue for ts@5+.

There is a pretty good vscode i18next extension that can help restore type safety and autofill i18n keys maintained by the lokalise team:

https://github.com/lokalise/i18n-ally

Phew! Ok finally I got it.

Last version that did not crash. 5.0.0-dev.20221103 Which makes the first version that crashed: 5.0.0-dev.20221108

Note that they were still very slow. And still needed NODE_OPTIONS=–max-old-space-size=8192` set.

Small Note

From 5.0.0-dev.20230210 and lower the error message slightly changed from what I posted earlier to

/home/jarek/Development/project/node_modules/typescript/lib/tsc.js:113758
      throw e;
      ^

RangeError: Value undefined out of range for undefined options property undefined
    at Map.set (<anonymous>)
    at getIndexedAccessTypeOrUndefined (/home/jarek/Development/project/node_modules/typescript/lib/tsc.js:56795:28)
    at getIndexedAccessType (/home/jarek/Development/project/node_modules/typescript/lib/tsc.js:56764:12)
    at /home/jarek/Development/project/node_modules/typescript/lib/tsc.js:56687:68
    at map (/home/jarek/Development/project/node_modules/typescript/lib/tsc.js:209:19)
    at distributeIndexOverObjectType (/home/jarek/Development/project/node_modules/typescript/lib/tsc.js:56687:21)
    at getSimplifiedIndexedAccessType (/home/jarek/Development/project/node_modules/typescript/lib/tsc.js:56710:37)
    at getSimplifiedType (/home/jarek/Development/project/node_modules/typescript/lib/tsc.js:56683:55)
    at getNormalizedType (/home/jarek/Development/project/node_modules/typescript/lib/tsc.js:58949:481)
    at isRelatedTo (/home/jarek/Development/project/node_modules/typescript/lib/tsc.js:59262:23)

Uh, hm, well, it’s certainly not supposed to crash anymore. If you have the time, if you can roll it back by date to find when it started crashing, that would be helpful.

Using Version "5.1.0-dev.20230328"

Running NODE_OPTIONS=--max-old-space-size=8192 tsc --noEmit --pretty --diagnostics --extendedDiagnostics --incremental false

Result

/home/jarek/Development/project/node_modules/typescript/lib/tsc.js:114282
      throw e;
      ^

RangeError: Value undefined out of range for undefined options property undefined
    at Map.set (<anonymous>)
    at recursiveTypeRelatedTo (/home/jarek/Development/project/node_modules/typescript/lib/tsc.js:60159:24)
    at isRelatedTo (/home/jarek/Development/project/node_modules/typescript/lib/tsc.js:59590:122)
    at eachTypeRelatedToType (/home/jarek/Development/project/node_modules/typescript/lib/tsc.js:59946:25)
    at unionOrIntersectionRelatedTo (/home/jarek/Development/project/node_modules/typescript/lib/tsc.js:59768:174)
    at isRelatedTo (/home/jarek/Development/project/node_modules/typescript/lib/tsc.js:59590:39)
    at structuredTypeRelatedToWorker (/home/jarek/Development/project/node_modules/typescript/lib/tsc.js:60559:25)
    at structuredTypeRelatedTo (/home/jarek/Development/project/node_modules/typescript/lib/tsc.js:60172:21)
    at recursiveTypeRelatedTo (/home/jarek/Development/project/node_modules/typescript/lib/tsc.js:60142:19)
    at isRelatedTo (/home/jarek/Development/project/node_modules/typescript/lib/tsc.js:59590:122)

Note

This took about 4mins to run before failing

We’ve made some improvements recently that feel like this issue; can you try the nightly build?

Here is what I have gathered from running some test. Test Ran with NODE_OPTIONS=--max-old-space-size=8192 tsc --noEmit --diagnostics --extendedDiagnostics --incremental false Here is 4.8

Files:                        2979
Lines of Library:            33311
Lines of Definitions:       233669
Lines of TypeScript:         59133
Lines of JavaScript:             0
Lines of JSON:                 943
Lines of Other:                  0
Nodes of Library:           148246
Nodes of Definitions:       723896
Nodes of TypeScript:        250983
Nodes of JavaScript:             0
Nodes of JSON:                2256
Nodes of Other:                  0
Identifiers:                382980
Symbols:                    597111
Types:                      216450
Instantiations:            7252666
Memory used:               684086K
Assignability cache size:   202376
Identity cache size:          3213
Subtype cache size:          12303
Strict subtype cache size:    4719
I/O Read time:               0.11s
Parse time:                  1.95s
ResolveModule time:          0.58s
ResolveTypeReference time:   0.01s
Program time:                3.04s
Bind time:                   1.00s
Check time:                 16.47s
printTime time:              0.00s
Emit time:                   0.00s
Total time:                 20.50s

Here is 4.9

Files:                         2980
Lines of Library:             33853
Lines of Definitions:        233669
Lines of TypeScript:          59133
Lines of JavaScript:              0
Lines of JSON:                  943
Lines of Other:                   0
Identifiers:                 384065
Symbols:                     571900
Types:                     18112640
Instantiations:             7055141
Memory used:               5726426K
Assignability cache size:  10914208
Identity cache size:           3215
Subtype cache size:           11958
Strict subtype cache size:     4719
I/O Read time:                0.15s
Parse time:                   1.72s
ResolveModule time:           0.47s
ResolveTypeReference time:    0.01s
Program time:                 2.65s
Bind time:                    0.79s
Check time:                 199.89s
printTime time:               0.00s
Emit time:                    0.00s
Total time:                 203.33s

I also generated a trace image

From looking into the trace. It seems to be stuck in a loop related to the i18next & react-i18next

This is basically the part that loops (if I followed the trace right)

import type { Namespace, TFuncKey } from 'i18next';
import { i18n } from './i18n';

export interface I18nText<N extends Namespace = Namespace> {
  namespace: N;
  keyName: TFuncKey<N>; // <-- TFuncKey seems to be the culprit
}

That lib is for sure complicated typewise. But it worked as it should in 4.8 and breaks compilation in 4.9 and 5.0

Anything else I can provide? @jakebailey

Similar regression on 5.0.2. For me, 4.8.3 is ok.

thanks for the exceptional bug report @whitespine!