새소식

업무경험

macOS 슬립모드에서 돌아올 때 소켓 연결 복원이 되지 않는 문제

  • -

이는 기업용 메신저 개발 당시 자주 겪던 문제 중의 하나인데

맥북을 사용하는 사람이라면 잘 알겠지만 맥북을 덮으면 기본적으로(별다른 설정이나 특별한 앱을 설치해두지 않았다면) 슬립상태가 되고 다시 열면 깨어나는데

이 때 네트워크 환경 등의 문제로 앱이 깨어났지만 미묘한 타이밍 문제로 네트워크를 사용할 수 없는 상태인 경우가 자주 발생했었다.

메신저 특성상 소켓 통신을 사용하고 있었기 때문에 슬립모드에서는 소켓을 끊고 깨어나면 소켓을 다시 연결해주어야 했는데

소켓을 연결할 때는 서버에서 발급한 엑세스 토큰을 사용하고 있었고 운 나쁘게도 슬립에서 돌아올 때 이 토큰이 만료되어 있는 경우에는 토큰을 갱신한 다음 다시 소켓을 연결하도록 되어 있었다.

슬립모드에서 깨어남 -> 소켓 재연결 시도 -> 토큰 만료 -> 토큰 갱신 시도 -> 토큰 갱신 -> 소켓 재연결

 

그런데 슬립모드에서 돌아왔을 때 연결이 복원되지 않는 것이다.

이 케이스는 토큰 만료라는 조건 때문에 재현하기가 쉽지 않았다. 그래서 고민 끝에 개발자 메뉴에 토큰을 강제로 만료시키는 코드를 넣었다.

토큰을 강제로 만료시키는 것도 쉽지는 않았다. 아무 토큰이나 넣으면 토큰 validation 로직에서 걸려서 만료 상황과는 다른 로직으로 빠지게 됐고 jwt를 사용하고 있었는데 토큰을 파싱해서 만료일자만 바꿔주면 앱을 건드리는 순간 REST API들이 호출되면서 자동으로 토큰이 갱신돼 버렸다. 하지만 앱을 건드리지 않고 슬립 모드에 들어갔다 나오는 방법을 찾아내어 문제 상황을 가까스로 재현할 수 있었고 재현이 된다면 디버깅은 시간문제였다.

 

슬립모드 관련된 수많은 문제들이 있었던 것 같지만 기억나는 두 가지 사례가 있다.

 

하나는 슬립모드에서 돌아올 때 앞에서 얘기한 타이밍 문제로 네트워크가 끊긴 상태여서 토큰이 갱신되지 않았고 이후 네트워크가 재연결되면서 다른 REST API 호출에 의해 자동으로 토큰 갱신까지 성공했지만 원래의 소켓 재연결 시도 시퀀스는 이미 종료된 상태였기 때문에 어디에서도 소켓을 재연결하는 시도를 하고 있지 않았던 것이 문제였던 케이스였다. 그래서 REST API 통신은 복구되었지만 소켓 연결만 안되고 있었다. 그래서 네트워크가 연결되었을 때 소켓이 연결되어 있지 않다면 소켓 연결을 재시도 하는 로직을 넣어서 해결했다.

슬립모드에서 깨어남 -> 소켓 재연결 시도 -> 토큰 만료 -> 토큰 갱신 시도 -> 네트워크 끊김 -> 끝(소켓 재연결 안됨)
네트워크 연결됨 -> REST API 호출 시도 -> 토큰 만료 -> 토큰 갱신 시도 -> 토큰 갱신 -> 끝

 

또 하나는 아예 토큰이 갱신되지 않는 케이스였다. 토큰이 만료되면 그 때 호출되는 수많은 REST API가 다 실패하면서 토큰 갱신 요청이 쇄도하기 때문에 토큰 갱신 함수 안에는 이미 토큰 갱신 중이라면 더 이상의 요청은 무시하도록 플래그 처리가 되어 있었는데, 누군가의 실수였는지, 언젠가부터 그 플래그를 갱신 성공하거나 실패하고 나서 끄는 처리를 안해주고 있었던 것이다. 그래서 첫번째 토큰 갱신 성공 이후에는 아무 문제 없이 사용했겠지만 두 번째 갱신할 때 더 이상의 갱신 요청이 모두 무시되어 갱신할 수 없는 문제가 발생했을 것이다. 그리고 그 문제는 공교롭게도 슬립모드 이후에 일어날 확률이 많았을 것이다. 왜냐하면 보통의 맥북 사용 패턴이 업무가 끝나면 덮어뒀다가 다음 날 자고 일어나서 맥북을 열게 되는데 이 때는 6시간 밖에 안되는 엑세스 토큰의 만료시간이 지나 있을 확률이 크기 때문에 이 때 갱신을 시도하는 경우가 많았던 것이다. 이 문제는 당연히 토큰 갱신 작업이 끝난 후 플래그를 끄도록 함으로 해결되었다.

 

소켓 연결과는 관계 없지만 토큰 갱신이 되지 않았던 케이스도 여러 건이 있었는데 그 중 하나를 더 소개한다.

앞서 얘기한 대로 REST API가 호출될 때 토큰이 만료됐으면 자동으로 토큰을 갱신하고 해당 API를 다시 호출하는 로직이 있었는데, 이 때 토큰이 갱신되면 DB에 저장하도록 되어 있었다. DB에 토큰을 저장하는 것은 자동로그인을 위함이었다.

그런데 무언가 관련 로직이 수정되면서 이유는 정확히 기억이 안나지만 토큰이 갱신되었을 때 토큰을 저장하는 조건 중에 현재 로그인 된 사용자의 정보를 메모리에서 조회해서 로그인되어 있는 경우에만 토큰을 DB에 저장하도록 하는 조건이 추가되었다. 아마도 자동로그인과 수동로그인을 명확히 구분하기 위해 사용자 정보를 DB에 저장하느냐와 메모리에만 저장하느냐로 분리하면서 실수가 생겼던 것 같다.

그런데 당연하게도 로그인 과정에서도 여러가지 API들을 호출하게 되고 이 때 토큰 갱신이 일어나게 된다면 현재 로그인 된 사용자의 정보는 메모리에서 찾을 수 없을 것이기 때문에 갱신된 토큰을 DB에 저장하지 못하는 문제가 발생했던 것이다. 해당 사용자는 자동로그인 사용자이기 때문에 DB에 사용자 정보가 있는 상태였다. 따라서 토큰이 갱신되었을 때 사용자 정보를 조회하는 대상을 메모리에서 DB로 변경하니 문제가 해결되었다.

반응형

'업무경험' 카테고리의 다른 글

주도적으로 의견을 내며 리딩했던 경험  (0) 2023.11.17
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.