Skip to content

shareReplay memory leak #5034

Closed
Closed
@gogakoreli

Description

@gogakoreli

Bug Report

Current Behavior
After component gets destroyed, its reference stays alive, because the fact that I subscribe to bigger scoped Observable (for example route.params) and switch it (via switchMap) to the Observable piped with shareReplay operator.

After taking heap snapshots via chrome devtools, I noticed that reference retainers mentioned shareReplay operator. If I use publishReplay and refCount instead of shareReplay problem is fixed, that's why I think shareReplay has an issue.

Please have a look at code on GitHub and screen shots for better understanding the issue. (Code explains issue better than description). You can see code sample below.

Event after reviewing #3336, issue still remains with shareReplay.

Reproduction

  • Repo link: Please have a look at GitHub repo
  • StackBlitz: Same repo source code imported from GitHub Here
export class PersonComponent implements OnInit, OnDestroy {

  public person: any;

  private unsubscribe$ = new Subject();

  constructor(private api: ApiService, private route: ActivatedRoute) { }

  ngOnInit() {
    this.route.params.pipe(
      switchMap(_ => this.api.find()),
      takeUntil(this.unsubscribe$),
    ).subscribe({
      next: (person) => {
        this.person = person;
        console.log(person);
      },
      complete: () => console.log('subscription completed'),
    });
  }

  ngOnDestroy() {
    this.unsubscribe$.next();
    this.unsubscribe$.complete();
    console.log('person component destroyed')
  }
}

@Injectable({ providedIn: 'root' })
export class ApiService {
  private cache = {};

  constructor(private http: HttpClient) {
  }

  public find(id = 1): Observable<any> {
    let res = this.cache[id];
    if (!res) {
      res = this.http.get(`https://swapi.co/api/people/${id}`).pipe(
        shareReplay({ bufferSize: 1, refCount: true }),
      )
      this.cache[id] = res;
    }
    return res;
  }
}

Expected behavior
After person component gets destroyed (for example, via navigation) it should be picked up by garbage collector and no reference of it should exist in the application.

Environment

  • Runtime: Chrome latest
  • RxJS version: 6

Possible Solution
To fix the issue instead of shareReplay use

 publishReplay(1),
 refCount(),

Additional context/Screenshots (Heap Snapshots)

This is what happens when I open the application on the home page, navigate to person page and then back to home page (I click garbage collection several times and take heap snapshot). Person component got destroyed but reference still lives in the heap. Under retainers you can spot shareReplay operator.

image


This is what happens when I navigate between home and person components several times and ending on person component. 2 references of it exists in the application.

image


None of these issues exist when using publishReplay(1), refCount().

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugConfirmed bug

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions