잡다한 개발/CloverDownloader

[잡다한 개발] - CloverDownloader 1.3.0 버그記

오잎 클로버 2023. 2. 1. 09:20
728x90

문제 발생

작성일 기준(2023-01-31)에 CloverDownloader 의 버전을 업데이트하여 릴리즈하기 앞서
CPU와 메모리의 usage 정도를 파악하는 중에 기존 버전인 1.2.0 은 CPU 사용량이 0에 가까웠는 데 반해
1.3.0은 평균 26%, 다운로드를 할 경우 40%이상을 평균적으로 사용한다는 점을 IntelliJ Profiler를 통해 파악했다.

사진으로 보면 차이가 무척 많이 나는 것을 확인할 수 있다.

위가 1.2.0, 아래가 1.3.0 이다.

아마 1.2.0 에서 1.3.0 을 개발하면서 UI 와 마이너한 버그들을 뜯어고치는 과정에서 CPU와 메모리 관리를
철저히 하지 못한 불찰로 이런 문제가 발생한 것 같다. (추가로 기능들을 여러 추가하기도 했다.)
그래서 CPU와 메모리의 사용량이 전 버전보다 상당한 이유를 어느정도 추측해보았는 데, 다음과 같다.

  • 기존 버전보다 많은 Thread
  • 요청들을 큐로 관리하는 클래스(이하 요청큐)에 의한 메모리 누수
    • 기존 버전에는 요청큐가 존재하지 않았으며, 단일 다운로드만을 고집하였다.
  • static 과 synchronization 의 남발
    • static 은 요청쿨에서 요청을 꺼내서 요청을 보내기 위하거나, util 클래스를 제외한 곳에서는
        일체 사용하지 않았다.
    • synchronization 은 동기화가 정말 필요한 영역에서만 사용하였다. Swing UI와의 충돌을 최소화하기 위해 적용한 부분이다.
  • while 무한루프
    • 아마 가장 높은 원인일 것이다.
    • 요청큐는 daemon-thread 로 무한루프를 통해 동작하고 있다.
    • 그렇기에 메모리를 상당히 사용하고 있을 것이다.

해결 방법

while-무한 루프와 Thread 자주 생성

DownloadCore 클래스는 Daemon-Thread이자 요청큐 클래스이다.
그렇기에 인스턴스를 하나만 생성하고선 while-true를 돌리거나, start 메소드 이후 DownloadCore 클래스에서
무한 루프하도록 하는 방법이 있다.

그렇게 한 결과, CPU와 쓰레드, 메모리가 정상 범위로 내려온 것을 확인할 수 있었다.
대신 다른 문제가 도출되었다.

이때는 MainFrame 에 싱글톤으로 처리했었는 데, 정상적으로 처리가 되지 않았다.
그래서 DownloadCore 클래스의 run메소드에서  while 무한반복을 시켜 처리하도록 하였다.

영상을 다운받고 있는 중에도 아무런 문제없이 잘 유지되고 있다.
잘 유지되고는 있지만 영상 다운로드 관련해서 또다른 문제를 하나 발견했다.
Threads 를 유심히 보면 22/24 인 것이 보일 것이다. 저건 여유분의 Thread가 아니다.
요청하기 전에는 17/17였던 쓰레드 갯수가 지금은 22/24이다. (위 16/16 인 경우에는 IllegalThreadStateException이 발생했다.)

아마 요청 중인 것 때문에 발생하는 쓰레드로 추측이 된다.
(프로세스 관련 쓰레드만 3개가 기본으로 추가되어 작동되며, I/O 쓰레드 2개)
그리고 저 최대치는 현재까지 빈 공간으로 유지되고 있는 쓰레드 수인지 아니면 그냥 최대까지 늘어난 것을 표현한 건지는 자세히 모른다. 만일 전자라면 꽤 큰일이다. 여유분도 여유분이지만, 요청할 때마다 최소 5, 6개씩 늘어날 텐데 그렇다면
아주 큰 문제가 발생할지도 모른다.

또다른 문제 발생

영상을 다운받도록 요청하지 않았음에도 불구하고 17에서 시작하였던 쓰레드의 갯수가 20까지 올라갔었다. 아무래도 내가 모르는 쓰레드의 문제가 있는 것이 아닌가 하는 생각이 든다. 이 문제는 어떤 것이 원인인지 감이 잡하지 않는다.
아마 I/O 쓰레드의 문제가 아닌가 싶다.

문제 접근

현재 I/O 쓰레드는 "StreamGobbler"(console 기록용), "StreamProcessExtractor"(진행상황 등을 파싱용) 총 2개의 클래스가 있다. 이 둘은 Daemon-Thread 들이 아니다. 다운로드 요청이 발생하면 작동하는 쓰레드들이다.
process#waitFor 때문에 상당한 리소스가 발생한 것은 아닌가하는 생각이 들었지만, 만일 그렇다면 block 이 발생하여
시간들 역시 굉장히 많이 잡아먹어야하고, 잘못하다가는 InterruptedException 이 발생할 것이다.
하지만 한 번도 발생해본 적이 없다. 각각 다른 링크인 1시간짜리 영상 총 20개를 다운바도록 했을 때에도 거뜬하게 버티어주었다.

원인

그래서 뭐가 문제인지 이벤트들과 기능들을 천천히 실행해보며 원인을 찾았다.
매우 단순한 것 때문에 문제가 생긴 것이었다. Swing UI Event 들 때문이었다.
Swing UI Event 의 JOptionPane 과 Directory open 때문에 발생하는 것들이 쓰레드들을 1 ~ 2 정도를 만들기 때문에
예상치 못한 쓰레드가 발생하였던 것이다.
(이문제를 찾기 위해 약 40분 가량을 소모했고, 찾았을 때 굉장히 허무했습니다...)

맨위의 비교 사진과 비교했을 때, 확연히 CPU 사용률이 떨어진 것을 볼 수 있다.

1.3.0 버그記 요약

  • Thread가 Daemon 으로 사용할 거고, 무한 반복을 해야하는 경우
        구현체에 반복문을 넣고 continue를 섞어 사용하자.
  • 본인이 작성한 코드 외에 라이브러리나 프레임워크들을 사용하는 경우
        해당 라이브러리나 프레임워크에 발생하는 리소스들 역시 잘 확인하자.
    • 본인의 문제 인지를 확인하고 의심해보자
CloverDownloader 1.3.0은 2월 4일에 github-release 에 올라갈 예정입니다.