Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TSC crashes on <30 line project #17036

Closed
bcherny opened this issue Jul 8, 2017 · 12 comments · Fixed by #18231
Closed

TSC crashes on <30 line project #17036

bcherny opened this issue Jul 8, 2017 · 12 comments · Fixed by #18231
Assignees
Labels
Bug A bug in TypeScript Fixed A PR has been merged for this issue

Comments

@bcherny
Copy link

bcherny commented Jul 8, 2017

Repro case: https://github.com/bcherny/tsoption/blob/7ae2d5d/index.ts (tree)

Repro steps:

  1. Git pull tsoption@7ae2d5d
  2. Run npm install
  3. Run ./node_modules/.bin/tsc

Node version: 6.9.5
TypeScript version: 2.4.1

Error output:

$ ./node_modules/.bin/tsc

<--- Last few GCs --->

   31467 ms: Mark-sweep 1339.6 (1437.6) -> 1339.6 (1437.6) MB, 1460.1 / 0.0 ms [allocation failure] [GC in old space requested].
   32931 ms: Mark-sweep 1339.6 (1437.6) -> 1339.6 (1437.6) MB, 1463.8 / 0.0 ms [allocation failure] [GC in old space requested].
   34405 ms: Mark-sweep 1339.6 (1437.6) -> 1340.7 (1421.6) MB, 1473.7 / 0.0 ms [last resort gc].
   35885 ms: Mark-sweep 1340.7 (1421.6) -> 1341.8 (1421.6) MB, 1480.1 / 0.0 ms [last resort gc].


<--- JS stacktrace --->

==== JS stack trace =========================================

Security context: 0x11cf6b9cfb51 <JS Object>
    1: getReturnTypeOfSignature [/Users/bcherny/Sites/tsoption/node_modules/typescript/lib/tsc.js:~27029] [pc=0x11e2ac7e930b] (this=0x11cf6b9e6f19 <JS Global Object>,signature=0x35a7fc95c541 <a Signature with map 0x210c46c5c571>)
    2: getReturnTypeOfSignature [/Users/bcherny/Sites/tsoption/node_modules/typescript/lib/tsc.js:~27029] [pc=0x11e2ac7e84ad] (this=0x11cf6b9e6f19 <JS Global Object>,s...

FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory
 1: node::Abort() [/Users/bcherny/.nvm/versions/node/v6.9.5/bin/node]
 2: node::FatalException(v8::Isolate*, v8::Local<v8::Value>, v8::Local<v8::Message>) [/Users/bcherny/.nvm/versions/node/v6.9.5/bin/node]
 3: v8::internal::V8::FatalProcessOutOfMemory(char const*, bool) [/Users/bcherny/.nvm/versions/node/v6.9.5/bin/node]
 4: v8::internal::Factory::NewFillerObject(int, bool, v8::internal::AllocationSpace) [/Users/bcherny/.nvm/versions/node/v6.9.5/bin/node]
 5: v8::internal::Runtime_AllocateInTargetSpace(int, v8::internal::Object**, v8::internal::Isolate*) [/Users/bcherny/.nvm/versions/node/v6.9.5/bin/node]
 6: 0x11e2ac2079a7
Abort trap: 6
@bcherny bcherny changed the title TSC crashes on 1-file project TSC crashes on <30 line project Jul 8, 2017
@HerringtonDarkholme
Copy link
Contributor

Changing type declaration to interface can solve this.

interface MonadNone<T> {
  ap<U>(option: Some<(value: T) => U>): None<U>
  chain<U>(f: (value: T) => Some<U>): None<T>
  chain<U>(f: (value: T) => None<U>): None<T>
}

interface MonadSome<T> {
  ap<U>(option: Some<(value: T) => U>): Some<U>
  chain<U>(f: (value: T) => Some<U>): Some<U>
  chain<U>(f: (value: T) => None<U>): None<T>
}

interface None<T> extends MonadNone<T> {
  flatMap<U>(f: (value: T) => Some<U>): None<T>
  flatMap<U>(f: (value: T) => None<U>): None<T>
}

interface Some<T> extends MonadSome<T> {
  flatMap<U>(f: (value: T) => Some<U>): Some<U>
  flatMap<U>(f: (value: T) => None<U>): None<T>
}

export type Option<T> = Some<T> | None<T>

export function None<T>(): None<T> {}
export function Some<T>(value: T): Option<T> {}

export let Option = <T>(value: T | null) => {
  if (value == null) {
    return None<T>()
  }
  return Some(value)
}

This problem is probably caused by caching. Anonymous object type will not be cached, so some recursive signature like (f: (value: T) => Some<U>) will incur exponential anonymous type instantiation. Thus OOM crash.

@fruchtose
Copy link

This seems like a big problem. TypeScript either needs to enforce a preferred type declaration scheme or support all type declarations equally. It's not enough for the official response to be, "just use interfaces". That makes writing TypeScript a guessing game.

@RyanCavanaugh RyanCavanaugh added the Bug A bug in TypeScript label Jul 25, 2017
@RyanCavanaugh
Copy link
Member

@fruchtose at the risk of being obvious, us crashing is not a way of us telling you not to write a particular piece of code 😉

@fruchtose
Copy link

@RyanCavanaugh, I apologize if my response comes across as harsh. It's an issue that's started to crop up lately in my TS work. While it it's frustrating to have an issue to point to, it's a bit of a relief as well to identify a possible root cause.

@giniedp
Copy link

giniedp commented Jul 26, 2017

My team is also facing the same issue. Devs are getting annoyed, since we can not run incremental builds for TDD any more. Unfortunately it is closed source. But here is what i can tell so far.

  • The error is same as above FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory
  • Occurs with typescript 2.4.x and also 2.3.x
  • We are using webpack
  • checked this guide
    • allowJs is false already
    • noImplicitAny is false already
    • transpileOnly is false already

When i search the project for type declarations, i can find about 134 matches. According to the comments above, those types wont be cached and may lead to out of memory. I tried to declare those types differently.

Some simple types can not be rewritten. Those look like

export type BrandColorName =
  "" |
  "dark" |
  "primary" |
  "secondary" |
  "success" |
  "warning" |
  "danger" |
  "info"

Majority of our types have been changed from this

import { AbstractControl } from "@angular/forms"
export class MyModel {
  // ...
}
export type MyModelForm = { [K in keyof MyModel]: AbstractControl }

to this

import { AbstractControl } from "@angular/forms"
export class MyModel {
  // ...
}
// next line is changed
export interface MyModelForm = { [K: string]: AbstractControl }

unfortunately this did not help. I still can get into the out the of memory crash. I can not say if the changes helped at least a bit, since i have no idea how to measure the compilers workload. I would appreciate any guide or hint how i could do that.

Even if the changes would have helped, i'd still prefer the type declaration like it was before the change since it helps us in 3 ways

  1. allows us very easily to follow a convention where several types must have the same keys
  2. no need to declare same keys across several types, which makes 1 a no brainer
  3. intellisense, which is now obviously missing on the changed types

@HerringtonDarkholme
Copy link
Contributor

HerringtonDarkholme commented Jul 26, 2017

@giniedp Probably your problem is deeper than OP's problem. And changing type alias to interface does not work probably proves it.

It seems that you have a quite large project. My suggestion is:

  1. split your project into smaller sub-project so that you can compile fewer files in one time
  2. deleting your code until TS stops OOM, this might help you find the root cause of crashing
  3. set node's --max_old_space_size. This is the last resort that you can permit node to consume more memory.

Hope this helps.

@bcherny
Copy link
Author

bcherny commented Jul 26, 2017

@giniedp Also see #13538.

@bcherny
Copy link
Author

bcherny commented Jul 26, 2017

@HerringtonDarkholme Confirming that changing every instance of type to interface fixes the issue. However, as that is a workaround, have you guys thought about what it would take to fix the underlying issue?

@giniedp
Copy link

giniedp commented Jul 28, 2017

just to let you know. If i separate the typescript compilation from webpack then the bare tsc compilation runs in 13 seconds and only reaches 500MB of memory usage. Incremental compilation is then about 9 seconds. So that part actually looks good. Webpack is the one who does the heavy lifting while bundling.

@mhegazy mhegazy added this to the TypeScript 2.6 milestone Aug 17, 2017
@sandersn
Copy link
Member

@bcherny Here are some notes from investigating your repro. The way that compilation fails on your repro is as follows:

When type checking export let Option = ..., the return type is inferred as the union of the return statements: None<T> and Some<T>. But return type inference has subtype reduction turned on in order to produce a nicer type in the presence of subtyping (specifically, to avoid Base | Derived1 when Base would do). This means that the compiler checks whether None<T> is a subtype of Some<T>. It's not, but it's close, which means that the compiler has to examine the structure of the whole type to find out why. You could annotate the return type; it cuts off all this work. But in real usage, it would be just as bad to mistakenly assign a value of type None<T> to one of Some<T>, so an annotation isn't a real solution.

Eventually, compilation gets stuck in a loop between None.flatMap and MonadNone.chain. After it checks that the parameters of each are assignable to the other, it has to examine the return types. The return types are None<T>, but T is fresh each time, so it just loops until it hits the depth limit, over and over again, bouncing a little back up before having to check the rest of each freshly generated type.

There is a fix in master that improves relationship checking for generic types, but it doesn't apply to type aliases. However, even extending that fix to type aliases doesn't prevent the oomemory crash. I think that's because the type argument is not a simple type variable. It's (value: U) => U, and right now function types are not cached, so each (value: U) => U is unique per recursion in the relationship check. I believe @ahejlsberg is looking at caching function types; I'll report back once he has something ready to try so that I can combine my fix and see whether together they help.

@ahejlsberg ahejlsberg assigned ahejlsberg and unassigned sandersn Sep 3, 2017
@ahejlsberg ahejlsberg added the Fixed A PR has been merged for this issue label Sep 3, 2017
@bcherny
Copy link
Author

bcherny commented Sep 3, 2017

Not sure if this is covered here or elsewhere already, but a similar error is thrown by this code (though it's a stack overflow, not heap):

interface A<B = A> {
  a: B
}
[0] RangeError: Maximum call stack size exceeded
[0]     at resolveNameHelper (/Users/bcherny/Sites/foo/node_modules/typescript/lib/typescript.js:27493:35)
[0]     at resolveName (/Users/bcherny/Sites/foo/node_modules/typescript/lib/typescript.js:27491:20)
[0]     at resolveEntityName (/Users/bcherny/Sites/foo/node_modules/typescript/lib/typescript.js:28152:26)
[0]     at resolveTypeReferenceName (/Users/bcherny/Sites/foo/node_modules/typescript/lib/typescript.js:32960:20)
[0]     at getTypeFromTypeReference (/Users/bcherny/Sites/foo/node_modules/typescript/lib/typescript.js:33043:30)
[0]     at getTypeFromTypeNode (/Users/bcherny/Sites/foo/node_modules/typescript/lib/typescript.js:33904:28)
[0]     at getDefaultFromTypeParameter (/Users/bcherny/Sites/foo/node_modules/typescript/lib/typescript.js:32189:66)
[0]     at getMinTypeArgumentCount (/Users/bcherny/Sites/foo/node_modules/typescript/lib/typescript.js:32470:26)
[0]     at getTypeFromClassOrInterfaceReference (/Users/bcherny/Sites/foo/node_modules/typescript/lib/typescript.js:32875:44)
[0]     at getTypeReferenceTypeWorker (/Users/bcherny/Sites/foo/node_modules/typescript/lib/typescript.js:32990:24)

@ahejlsberg
Copy link
Member

@bcherny No, that's an unrelated problem, I will open a new issue.

@microsoft microsoft locked and limited conversation to collaborators Jun 14, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Bug A bug in TypeScript Fixed A PR has been merged for this issue
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants