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

Upgrading to 3.2.1 causes out of memory error in watch mode #30732

Closed
Methuselah96 opened this issue Apr 3, 2019 · 7 comments
Closed

Upgrading to 3.2.1 causes out of memory error in watch mode #30732

Methuselah96 opened this issue Apr 3, 2019 · 7 comments
Labels
Bug A bug in TypeScript Crash For flagging bugs which are compiler or service crashes or unclean exits, rather than bad output
Milestone

Comments

@Methuselah96
Copy link

Methuselah96 commented Apr 3, 2019

This compiled successfully using tsc --watch in 3.1.6, but broke in 3.2.1. I traced it down further to 3.2.0-dev.20181113 and most specifically this PR: #28490.

TypeScript Version: 3.4.0-dev.20190403

Search Terms:
OOM 3.2.1

Code

// Type-safe deep updates
// The original object is returned if the deep field's new value is equal to its current value per Object.is.
// These are use-once; don't make two separate updates using part of the same chain.
// Example usage:
// const o = { a: { b: 0, c: 1 }, d: 2 };
// update(o)('a')('c').map(c => c + 2); // Evaluates to { a: { b: 0, c: 3 }, d: 2 }
export const updateIfChanged = <T>(t: T) => {
  const reduce = <U>(u: U, update: (u: U) => T) => {
    const set = (newU: U) => Object.is(u, newU) ? t : update(newU);
    type Key = U extends ReadonlyArray<any> ? number | keyof U : keyof U;
    type Value<K extends Key> = K extends keyof U ? U[K] : U extends ReadonlyArray<infer V> ? V : never;
    return Object.assign(
      <K extends Key>(key: K) =>
        reduce<Value<K>>(u[key as keyof U] as Value<K>, (v: Value<K>) => {
          return update(Object.assign(Array.isArray(u) ? [] : { }, u, { [key]: v }));
        }),
        { map: (updater: (u: U) => U) => set(updater(u)), set });
  };
  return reduce<T>(t, (t: T) => t);
};

Expected behavior:
The code compiles and watches when running tsc --watch.

Actual behavior:
When running tsc --watch it outputs:

<--- Last few GCs --->

[42844:00000191A5C7B950]    49850 ms: Scavenge 1388.4 (1424.0) -> 1387.5 (1424.5) MB, 2.9 / 0.0 ms  (average mu = 0.206, current mu
 = 0.179) allocation failure
[42844:00000191A5C7B950]    49855 ms: Scavenge 1388.4 (1424.5) -> 1387.6 (1425.0) MB, 3.1 / 0.0 ms  (average mu = 0.206, current mu
 = 0.179) allocation failure
[42844:00000191A5C7B950]    49863 ms: Scavenge 1388.5 (1425.0) -> 1387.7 (1425.5) MB, 3.5 / 0.0 ms  (average mu = 0.206, current mu
 = 0.179) allocation failure


<--- JS stacktrace --->

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

    0: ExitFrame [pc: 000003B19215C5C1]
    1: StubFrame [pc: 000003B19210D64B]
    2: ConstructFrame [pc: 000003B19210D1E3]
Security context: 0x020661c9e6e1 <JSObject>
    3: createKeywordTypeNode [000001B8101A26D1] [C:\Users\Nathan\Documents\TypeScript\built\local\tsc.js:~
51212] [pc=000003B1924CE3F4](this=0x0296c36051a9 <Object map = 00000283A02FA809>,kind=132)
    4: typeToTypeNodeHelper(aka typeToTypeNodeHelpe...

FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
 1: 00007FF7A3D1121A v8::internal::GCIdleTimeHandler::GCIdleTimeHandler+4810
 2: 00007FF7A3CEA5B6 node::MakeCallback+4518
 3: 00007FF7A3CEAFA0 node_module_register+2160
 4: 00007FF7A3F7B3EE v8::internal::FatalProcessOutOfMemory+846
 5: 00007FF7A3F7B31F v8::internal::FatalProcessOutOfMemory+639
 6: 00007FF7A44B9304 v8::internal::Heap::MaxHeapGrowingFactor+11476
 7: 00007FF7A44AFA67 v8::internal::ScavengeJob::operator=+25543
 8: 00007FF7A44ADFDC v8::internal::ScavengeJob::operator=+18748
 9: 00007FF7A44B6F57 v8::internal::Heap::MaxHeapGrowingFactor+2343
10: 00007FF7A44B6FD6 v8::internal::Heap::MaxHeapGrowingFactor+2470
11: 00007FF7A4059DD7 v8::internal::Factory::NewFillerObject+55
12: 00007FF7A40F1ABA v8::internal::WasmJs::Install+29530
13: 000003B19215C5C1

Playground Link:
Here's a repo with the source code and tsconfig.json: https://github.com/Methuselah96/typescript-oom.

Related Issues:
None.

@Methuselah96 Methuselah96 changed the title Upgrading to 3.2.1 causes out of memory error Upgrading to 3.2.1 causes out of memory error in watch mode Apr 4, 2019
@RyanCavanaugh RyanCavanaugh added the Bug A bug in TypeScript label Apr 9, 2019
@RyanCavanaugh RyanCavanaugh added the Crash For flagging bugs which are compiler or service crashes or unclean exits, rather than bad output label Apr 9, 2019
@ahejlsberg
Copy link
Member

ahejlsberg commented Apr 10, 2019

This does indeed appear to be caused by #28490. The OOM happes when the --declaration flag is specified and createAnonymousTypeNode takes off doing enormous amounts of work. When I back out the changes in #28490 there's no issue.

@weswigham You added the code in #28490, you may want to take a look.

@weswigham
Copy link
Member

There's a depth > 10 check that can just be lowered - we didn't have any examples that exploded nearly as quickly as this one.

@ahejlsberg
Copy link
Member

It's not quite that simple. Any check except depth !== 0 ends up producing an invalid declaration file. And going back to depth !== 0 is effectively the same as undoing #28490.

I'm going to assign this one back to you.

@ahejlsberg ahejlsberg assigned weswigham and unassigned ahejlsberg Apr 12, 2019
@weswigham
Copy link
Member

ends up producing an invalid declaration file

Ohho? Sounds like we can't actually print declarations for this type structure and either need to reduce accuracy or issue a declaration error. I'll look into it.

@Methuselah96
Copy link
Author

@weswigham Any update on this?

@weswigham
Copy link
Member

Any check except depth !== 0 ends up producing an invalid declaration file.

Two things going on here:

  1. Turns out we just had a bug where since we were using the declaration to cache generated names for type parameters, the generated names for differing instances of the same symbol were the same (since they share a declaration), so they conflicted with one another. This is pretty easy to fix, and makes the simpler example:
export type Key<U> = keyof U;
export type Value<K extends Key<U>, U> = U[K];
export const updateIfChanged = <T>(t: T) => {
    const reduce = <U>(u: U, update: (u: U) => T) => {
        const set = (newU: U) => Object.is(u, newU) ? t : update(newU);
        return Object.assign(
            <K extends Key<U>>(key: K) =>
                reduce<Value<K, U>>(u[key as keyof U] as Value<K, U>, (v: Value<K, U>) => {
                    return update(Object.assign(Array.isArray(u) ? [] : {}, u, { [key]: v }));
                }),
            { map: (updater: (u: U) => U) => set(updater(u)), set });
    };
    return reduce<T>(t, (t: T) => t);
};

emit without producing code with any errors.
2. When we print the declarations, we expand up to depth levels of conditionals if their names aren't reachable. This is usually fine.... except these nesting conditionals have infer types! And they essentially are prone to the problem this code exhibits:

function f<X>(arg: X) {
    type Cond1 = X extends [infer A] ? A : never;
    type Cond2 = X extends [infer A] ? A : never;

    let x: Cond1 = null as any;
    let y: Cond2 = null as any;
    x = y; // is err, should be ok
    y = x; // is err, should be ok
}

(in the example in the OP, we inline the alias into the constraint and the argument, meaning we have two conditionals with differing identities, as in the above, and the keyof one isn't allowed to index the other!) Fixing this is more involved - our logic for relating conditional extends clauses with infer type parameters needs to do something similar to what we do for signatures - we need to "instantiate the source in the context of the target" to unify the type parameters with differing identities.

@ahejlsberg should I fix these separately? The two parts seem different enough that they warrant separate review.

@sandersn
Copy link
Member

This is fixed in TS 4.5 (and probably much earlier).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Crash For flagging bugs which are compiler or service crashes or unclean exits, rather than bad output
Projects
None yet
Development

No branches or pull requests

6 participants