@@ -17,9 +17,12 @@ import { Injectable, Inject, InjectFunction, Injector, ProviderScope, Dependency
1717import { SchemaLink } from 'apollo-link-schema' ;
1818import { ApolloClient } from 'apollo-client' ;
1919import { InMemoryCache } from 'apollo-cache-inmemory' ;
20- import { EventEmitter } from 'events' ;
2120import { KeyValueCache } from 'apollo-server-caching' ;
21+ import { iterate } from 'leakage' ;
22+ import { EventEmitter } from 'events' ;
23+ import { writeFile } from 'fs' ;
2224
25+ jest . setTimeout ( 60000 * 10 ) ;
2326describe ( 'GraphQLModule' , ( ) => {
2427 // A
2528 @Injectable ( )
@@ -1140,7 +1143,7 @@ describe('GraphQLModule', () => {
11401143 isDirty : ( root , args , context , info ) => ! ! info . schema [ '__DIRTY__' ] ,
11411144 } ,
11421145 } ,
1143- middleware : ( { schema} ) => { schema [ '__DIRTY__' ] = true ; return { schema } ; } ,
1146+ middleware : ( { schema } ) => { schema [ '__DIRTY__' ] = true ; return { schema } ; } ,
11441147 } ) ;
11451148 const { schema, context } = new GraphQLModule ( {
11461149 imports : [
@@ -1262,7 +1265,7 @@ describe('GraphQLModule', () => {
12621265 @Injectable ( )
12631266 class TestDataSourceAPI {
12641267 cache : KeyValueCache ;
1265- initialize ( { cache } : { cache : KeyValueCache } ) {
1268+ initialize ( { cache } : { cache : KeyValueCache } ) {
12661269 this . cache = cache ;
12671270 }
12681271 }
@@ -1297,7 +1300,7 @@ describe('GraphQLModule', () => {
12971300 class TestDataSourceAPI {
12981301 context : any ;
12991302 cache : KeyValueCache ;
1300- public initialize ( { context, cache } : { context : any , cache : KeyValueCache } ) {
1303+ public initialize ( { context, cache } : { context : any , cache : KeyValueCache } ) {
13011304 this . context = context ;
13021305 this . cache = cache ;
13031306 }
@@ -1467,14 +1470,14 @@ describe('GraphQLModule', () => {
14671470 it ( 'should throw an error if promises are used without schemaAsync' , async ( ) => {
14681471 const MyAsyncModule = new GraphQLModule ( {
14691472 typeDefs : async ( ) => `type Query { test: Boolean }` ,
1470- resolvers : async ( ) => ( { Query : { test : ( ) => true } } ) ,
1473+ resolvers : async ( ) => ( { Query : { test : ( ) => true } } ) ,
14711474 } ) ;
14721475 expect ( ( ) => MyAsyncModule . schema ) . toThrow ( ) ;
14731476 } ) ;
14741477 it ( 'should support promises with schemaAsync' , async ( ) => {
14751478 const { schemaAsync } = new GraphQLModule ( {
14761479 typeDefs : async ( ) => `type Query { test: Boolean }` ,
1477- resolvers : async ( ) => ( { Query : { test : ( ) => true } } ) ,
1480+ resolvers : async ( ) => ( { Query : { test : ( ) => true } } ) ,
14781481 } ) ;
14791482 const result = await execute ( {
14801483 schema : await schemaAsync ,
@@ -1540,60 +1543,233 @@ describe('GraphQLModule', () => {
15401543 expect ( result . data [ 'qux' ] ) . toBe ( 'QUX' ) ;
15411544 } ) ;
15421545 it ( 'should not have _onceFinishListeners on response object' , async ( done ) => {
1543- let counter = 0 ;
1544- @Injectable ( {
1545- scope : ProviderScope . Session ,
1546- } )
1547- class FooProvider implements OnResponse {
1548- onResponse ( ) {
1549- counter ++ ;
1546+ let counter = 0 ;
1547+ @Injectable ( {
1548+ scope : ProviderScope . Session ,
1549+ } )
1550+ class FooProvider implements OnResponse {
1551+ onResponse ( ) {
1552+ counter ++ ;
1553+ }
1554+ getCounter ( ) {
1555+ return counter ;
1556+ }
1557+ }
1558+
1559+ const module = new GraphQLModule ( {
1560+ typeDefs : gql `
1561+ type Query {
1562+ foo: Int
15501563 }
1551- getCounter ( ) {
1552- return counter ;
1564+ ` ,
1565+ resolvers : {
1566+ Query : {
1567+ foo : ( _ , __ , { injector } ) => injector . get ( FooProvider ) . getCounter ( ) ,
1568+ } ,
1569+ } ,
1570+ providers : [
1571+ FooProvider ,
1572+ ] ,
1573+ } ) ;
1574+ const session = createMockSession ( { } ) ;
1575+ const { data } = await execute ( {
1576+ schema : module . schema ,
1577+ contextValue : session ,
1578+ document : gql `query { foo }` ,
1579+ } ) ;
1580+ // Result
1581+ expect ( data . foo ) . toBe ( 0 ) ;
1582+ // Before onResponse
1583+ expect ( counter ) . toBe ( 0 ) ;
1584+ await session . res . emit ( 'finish' ) ;
1585+ // After onResponse
1586+ expect ( counter ) . toBe ( 1 ) ;
1587+ // Check if the listener is triggered again
1588+ session . res . once ( 'finish' , ( ) => {
1589+ setTimeout ( ( ) => {
1590+ expect ( counter ) . toBe ( 1 ) ;
1591+ // Response object must be cleared
1592+ expect ( session . res [ '_onceFinishListeners' ] ) . toBeUndefined ( ) ;
1593+ expect ( module . injector . hasSessionInjector ( session ) ) . toBeFalsy ( ) ;
1594+ expect ( module [ '_sessionContext$Map' ] . has ( session ) ) . toBeFalsy ( ) ;
1595+ done ( ) ;
1596+ } , 1000 ) ;
1597+ } ) ;
1598+ session . res . emit ( 'finish' ) ;
1599+ } ) ;
1600+ it . skip ( 'should not have memory leak over multiple sessions with session-scoped providers' , done => {
1601+
1602+ @Injectable ( {
1603+ scope : ProviderScope . Session ,
1604+ } )
1605+ class AProvider {
1606+ constructor ( private moduleSessionInfo : ModuleSessionInfo ) { }
1607+ getLoadLength ( ) {
1608+ return this . moduleSessionInfo . session . hugeLoad . length ;
1609+ }
1610+ }
1611+ const moduleA = new GraphQLModule ( {
1612+ typeDefs : gql `
1613+ type Query {
1614+ aLoadLength: Int
15531615 }
1616+ ` ,
1617+ resolvers : {
1618+ Query : {
1619+ aLoadLength : ( _ , __ , { injector } ) => injector . get ( AProvider ) . getLoadLength ( ) ,
1620+ } ,
1621+ } ,
1622+ providers : [
1623+ AProvider ,
1624+ ] ,
1625+ } ) ;
1626+ @Injectable ( {
1627+ scope : ProviderScope . Session ,
1628+ } )
1629+ class BProvider {
1630+ constructor ( private moduleSessionInfo : ModuleSessionInfo ) { }
1631+ getLoadLength ( ) {
1632+ return this . moduleSessionInfo . session . hugeLoad . length ;
15541633 }
1634+ getB ( ) {
1635+ return 'B' ;
1636+ }
1637+ }
1638+ const moduleB = new GraphQLModule ( {
1639+ typeDefs : gql `
1640+ type Query {
1641+ aLoadLength: Int
1642+ }
1643+ ` ,
1644+ resolvers : {
1645+ Query : {
1646+ bLoadLength : ( _ , __ , { injector } ) => injector . get ( BProvider ) . getLoadLength ( ) ,
1647+ } ,
1648+ } ,
1649+ providers : [
1650+ BProvider ,
1651+ ] ,
1652+ } ) ;
1653+ const { schema } = new GraphQLModule ( {
1654+ imports : [
1655+ moduleA ,
1656+ moduleB ,
1657+ ] ,
1658+ } ) ;
15551659
1556- const module = new GraphQLModule ( {
1557- typeDefs : gql `
1558- type Query {
1559- foo: Int
1560- }
1561- ` ,
1562- resolvers : {
1563- Query : {
1564- foo : ( _ , __ , { injector } ) => injector . get ( FooProvider ) . getCounter ( ) ,
1565- } ,
1660+ let counter = 0 ;
1661+ iterate . async ( async ( ) => {
1662+ // tslint:disable-next-line: no-console
1663+ console . log ( `Iteration: ${ counter } start` ) ;
1664+ await execute ( {
1665+ schema,
1666+ contextValue : {
1667+ hugeLoad : new Array ( 1000 ) . fill ( 1000 ) ,
15661668 } ,
1567- providers : [
1568- FooProvider ,
1569- ] ,
1669+ document : gql `{ aLoadLength bLoadLength }` ,
15701670 } ) ;
1571- const session = createMockSession ( { } ) ;
1671+ // tslint:disable-next-line: no-console
1672+ console . log ( `Iteration: ${ counter } end` ) ;
1673+ counter ++ ;
1674+ } ) . then ( done ) . catch ( done . fail ) ;
1675+
1676+ } ) ;
1677+ it ( 'should not memory leak over multiple sessions (not collected by GC but emitting finish event) with session-scoped providers' , done => {
1678+
1679+ let counter = 0 ;
1680+ @Injectable ( {
1681+ scope : ProviderScope . Session ,
1682+ } )
1683+ class AProvider {
1684+ aHugeLoad = new Array ( 1000 ) . fill ( 1000 ) ;
1685+
1686+ constructor ( private moduleSessionInfo : ModuleSessionInfo ) {
1687+ counter ++ ;
1688+ }
1689+ getLoadLength ( ) {
1690+ return this . moduleSessionInfo . session . hugeLoad . length ;
1691+ }
1692+ getALoadLength ( ) {
1693+ return this . aHugeLoad . length ;
1694+ }
1695+ }
1696+ const moduleA = new GraphQLModule ( {
1697+ typeDefs : gql `
1698+ type Query {
1699+ aLoadLength: Int
1700+ abLoadLength: Int
1701+ }
1702+ ` ,
1703+ resolvers : {
1704+ Query : {
1705+ aLoadLength : ( _ , __ , { injector } ) => injector . get ( AProvider ) . getALoadLength ( ) ,
1706+ abLoadLength : ( _ , __ , { injector } ) => injector . get ( AProvider ) . getLoadLength ( ) ,
1707+ } ,
1708+ } ,
1709+ providers : [
1710+ AProvider ,
1711+ ] ,
1712+ } ) ;
1713+ @Injectable ( {
1714+ scope : ProviderScope . Session ,
1715+ } )
1716+ class BProvider {
1717+ bHugeLoad = new Array ( 1000 ) . fill ( 1000 ) ;
1718+ constructor ( private moduleSessionInfo : ModuleSessionInfo ) { }
1719+ getLoadLength ( ) {
1720+ return this . moduleSessionInfo . session . hugeLoad . length ;
1721+ }
1722+ getBLoadLength ( ) {
1723+ return this . bHugeLoad . length ;
1724+ }
1725+ }
1726+ const moduleB = new GraphQLModule ( {
1727+ typeDefs : gql `
1728+ type Query {
1729+ bLoadLength: Int
1730+ baLoadLength: Int
1731+ }
1732+ ` ,
1733+ resolvers : {
1734+ Query : {
1735+ bLoadLength : ( _ , __ , { injector } ) => injector . get ( BProvider ) . getBLoadLength ( ) ,
1736+ baLoadLength : ( _ , __ , { injector } ) => injector . get ( BProvider ) . getLoadLength ( ) ,
1737+ } ,
1738+ } ,
1739+ providers : [
1740+ BProvider ,
1741+ ] ,
1742+ } ) ;
1743+ const { schema } = new GraphQLModule ( {
1744+ imports : [
1745+ moduleA ,
1746+ moduleB ,
1747+ ] ,
1748+ } ) ;
1749+ const mockRequests : Array < MockSession < { hugeLoad : number [ ] } > > = [ ] ;
1750+ for ( let i = 0 ; i < 1000 ; i ++ ) {
1751+ mockRequests . push ( createMockSession ( { hugeLoad : new Array ( 1000 ) . fill ( 1000 ) } ) ) ;
1752+ }
1753+ iterate . async ( ( ) => new Promise ( async resolve => {
1754+ // tslint:disable-next-line: no-console
1755+ console . log ( `Iteration started` ) ;
1756+ const mockRequest = mockRequests [ Math . floor ( Math . random ( ) * mockRequests . length ) ] ;
15721757 const { data } = await execute ( {
1573- schema : module . schema ,
1574- contextValue : session ,
1575- document : gql `query { foo }` ,
1758+ schema,
1759+ contextValue : mockRequest ,
1760+ document : gql `{ aLoadLength bLoadLength abLoadLength baLoadLength }` ,
15761761 } ) ;
1577- // Result
1578- expect ( data . foo ) . toBe ( 0 ) ;
1579- // Before onResponse
1580- expect ( counter ) . toBe ( 0 ) ;
1581- session . res . emit ( 'finish' ) ;
1582- // After onResponse
1583- expect ( counter ) . toBe ( 1 ) ;
1584- // Check if the listener is triggered again
1585- session . res . emit ( 'finish' ) ;
1586- expect ( counter ) . toBe ( 1 ) ;
1587- setTimeout ( ( ) => {
1588- try {
1589- // Response object must be cleared
1590- expect ( session . res [ '_onceFinishListeners' ] ) . toBeUndefined ( ) ;
1591- expect ( module . injector . hasSessionInjector ( session ) ) . toBeFalsy ( ) ;
1592- expect ( module [ '_sessionContext$Map' ] . has ( session ) ) . toBeFalsy ( ) ;
1593- done ( ) ;
1594- } catch ( e ) {
1595- done . fail ( e ) ;
1596- }
1597- } , 1000 ) ;
1762+ mockRequest . res . emit ( 'finish' ) ;
1763+ expect ( data . aLoadLength ) . toBe ( 1000 ) ;
1764+ expect ( data . bLoadLength ) . toBe ( 1000 ) ;
1765+ expect ( data . abLoadLength ) . toBe ( 1000 ) ;
1766+ expect ( data . baLoadLength ) . toBe ( 1000 ) ;
1767+ // tslint:disable-next-line: no-console
1768+ console . log ( counter ) ;
1769+ resolve ( ) ;
1770+ } ) ) . then ( ( ) => {
1771+ done ( ) ;
1772+ } ) . catch ( done . fail ) ;
1773+
15981774 } ) ;
15991775} ) ;
0 commit comments